Hackquest 2015: Megatask.exe solution
Hello everyone!
Вот и закончился ежегодный Hackquest, который проводился с 11 по 17 мая 2015 года. Было множество интересных и трудных заданий, чтобы немного размять свои мозги и настроиться на волну форума Positive Hack Days V. К сожалению, с заданием megatask справилась только команда RDot.org. И о решении этого задания я хочу сегодня вам рассказать. Так сказать помахать немного кулаками после драки. Приступим.
Помним, что тема прошедшего Hackquest’а, была Web. А значит большая часть заданий, так или иначе, была связана с уязвимостями веб-приложений. Вот и megatask затрагивает проблемы безопасности плагинов для самой популярной CMS WordPress, веб-интерфейсов роутеров, а так же получение информации из публичных источников.
Открываем описание задачи и смотрим, что от нас требуется:
[themify_quote]http://megatask.mcdir.ru/
Бесстыжый пользователь SuperHacker1337 оставил обидный коммент в посте Marty McFly, нужно узнать:
1) Имя пользователя
2) Где он живет
3) Как выглядит
На каждом этапе поиска найдем часть флага.
flag = flag1.flag2.flag3
Вторая часть флага — ник в instagram. Но не все так очевидно[/themify_quote]
Перейдем непосредственно к решению задачи.
Первое, что я сделал это немного потискал сам сайт. Обычный WordPress блог с небольшим количеством плагинов и псевдо-тестовыми постами. В одном из этих постов и оставил непотребный комментарий SuperHacker1337.
Те, кто-более менее знает как работает WP, ведет на нем блоги или ломал сайты на этой CMS вкурсе, что IP адреса комментаторов сохраняются в таблице wp_comments. Поэтому, один из самых очевидных способов узнать какую-то информацию о том, кто оставил коммент, это получить доступ к БД и выудить ее из этой таблицы.
Начнем с программы WPScan — самого продуктивного блэк-бокс сканера для WordPress. Вот, что ему удалось разнюхать:
[themify_quote]
…
[+] robots.txt available under: ‘http://megatask.mcdir.ru/robots.txt’
[+] Interesting entry from robots.txt: http://megatask.mcdir.ru/wp-content/*
[+] Interesting entry from robots.txt: http://megatask.mcdir.ru/cgi-bin/*
[+] Interesting entry from robots.txt: http://megatask.mcdir.ru/plugins.log
[+] Interesting entry from robots.txt: http://megatask.mcdir.ru/wpadmin.html
[!] The WordPress ‘http://megatask.mcdir.ru/readme.html’ file exists exposing a version number
[!] Full Path Disclosure (FPD) in: ‘http://megatask.mcdir.ru/wp-includes/rss-functions.php’
…
[+] WordPress version 4.2.2 identified from advanced fingerprinting
[+] WordPress theme in use: modern – v1.4.1
[+] Name: modern – v1.4.1
| Location: http://megatask.mcdir.ru/wp-content/themes/modern/
| Style URL: http://megatask.mcdir.ru/wp-content/themes/modern/style.css
| Description:
[+] Enumerating installed plugins (only vulnerable ones) …
[+] We found 1 plugins:
[+] Name: wordfence – v5.3.12
| Location: http://megatask.mcdir.ru/wp-content/plugins/wordfence/
| Readme: http://megatask.mcdir.ru/wp-content/plugins/wordfence/readme.txt
[/themify_quote]
На момент проведения конкурса и написания этой статьи все найденные плагины и сам WP находились в актуальном состоянии и не содержали критических уязвимостей, которые бы позволяли получить какой-либо доступ к блогу и его базе данных.
Стоит обратить внимание на интересное в файле robots.txt, а именно на строку:
[themify_quote][+] Interesting entry from robots.txt: http://megatask.mcdir.ru/plugins.log[/themify_quote]
Глянем содержимое этого файла повнимательнее. Да это же лог установки плагинов на WP! С указанием дат и мест, откуда плагин устанавливался. Отлично, посмотрим какие плагины были установленны, может быть есть что-то, чего не нашел WPScan. Обратим внимание, что названия плагинов соответсвуют папкам в которых они расположены на сайте.
Через несколько минут нахожу плагин WP-Logging, который был установлен из git-репозитория.
[themify_quote]…
Beginning installation of ‘WP-Logging’ plugin
Successfully installed ‘WP-Logging’ plugin from remote Git repository
…[/themify_quote]
Стоит посмотреть, существует ли еще этот плагин и имеется ли папка .git со служебными файлы репозитория, напимер index. http://megatask.mcdir.ru/wp-content/plugins/WP-Logging/.git/index.
Очень хорошо, файл на месте. Теперь можно попробовать восстановить структуру репозитория и файлов или проверить наличие config файла, чтобы узнать адрес репозитория. Пробуем последний вариант, так как он гораздо быстрее — http://megatask.mcdir.ru/wp-content/plugins/WP-Logging/.git/config:
[themify_quote]…
[remote “origin”]
fetch = +refs/heads/*:refs/remotes/origin/*
url = https://github.com/allyshka/WP-Logging.git
…[/themify_quote]
Проверяем, публичный ли данный репозиторий. Ура-ура, репозиторий публичный, поэтому клонирую его себе для дальнейшего изучения.
Для начала можно глянуть последние коммиты, возможно исправлялись какие-то критические баги. Так же стоит посмотреть, актуальная ли версия плагина установлена на сайте http://megatask.mcdir.ru/wp-content/plugins/WP-Logging/.git/packed-refs.
Версия последняя, хорошо. Тогда начинаем изучать исходники, благо кода не так много. При беглом осмотре бросаются в глаза методы чтения и записи log-файлов и функция file_get_contents(). Рассмотрим повнимательнее эти методы, возможно тут присутствует возможность чтения файлов на сервере.
1 2 3 4 5 6 7 8 9 10 11 12 | 164: function add_entry( $plugin_name, $log, $message, $severity = 1 ) { ... 241: public function getLogDir() ... 250: public function setLogDir($logDir) ... 832: private function get_entries( $limit = 20 ) { ... 936: private function get_logs( $plugin_term, $purge = false ) { ... 965: private function showLogFile($file) { 966: return file_get_contents(dirname(__FILE__).'/logs/'.basename($this->$file)); |
Метод showLogFile читает файл, переданный ему в качестве параметра. Этот метод вызывается при создании экземпляра класса WP_Logger. Видно что, при передаче параметров show и log, в запросе к файлу плагина wp-logger.php, мы можем читать файлы с именем указанным в log:
1 2 3 4 | if(!empty($_GET['show']) && !empty($_GET['log'])) { $this->file = $_GET['log']; print $this->showLogFile($this->file); } |
Теперь рассмотрим повнимательнее метод showLogFile. Он читает файл из папки logs, которая находится в директории с плагином, но выйти из папки нам не дает функция basename(). Однако, в коде допущена банальная синтаксическая ошибка. Поэтому, при выполнении, скрипт пробует прочитать файл с именем свойства класса, которое содержится в переменной $file:
[themify_quote]
basename($this->$file));
[/themify_quote]
Что из этого можно получить? Чтобы это узнать нужно повнимательнее рассмотреть последние коммиты от 13.04.2015 и 14.04.2015:
- https://github.com/allyshka/WP-Logging/commit/6ff96832a3234f257d74dd1670b1ea9bfe346a19 — Added wpdb functionality to logging class
- https://github.com/allyshka/WP-Logging/commit/03780b030f22572e16e2c179c6a63f9a589b367e — Added config file to load
- https://github.com/allyshka/WP-Logging/commit/9e558ff6e283f8662b5f56603fe9274c986fb5d7 — Add wpdb class casting to the main class of WP_Logging
Первый коммит делает класс WP_Logger дочерним от WordPress-класса для работы с базой данных wpdb.
Второй коммит добавляет загрузку файла wp-config.php и всех необходимых для работы ядра wordpress файлов. Таким образом создается подключение к рабочей базе данных.
Третий коммит добавляет функцию cast() — она вызывается при создании класса и копирует все свойства wpdb в текущий экземпляр класса. Включая такие интересные свойства как: dbuser, dbpassword, dbname, dbhost:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | ... /** * Database Username * * @since 2.9.0 * @access protected * @var string */ protected $dbuser; /** * Database Password * * @since 3.1.0 * @access protected * @var string */ protected $dbpassword; /** * Database Name * * @since 3.1.0 * @access protected * @var string */ protected $dbname; /** * Database Host * * @since 3.1.0 * @access protected * @var string */ protected $dbhost; /** * Database Handle * * @since 0.71 * @access protected * @var string ... |
Теперь, благодаря маленькой ошибке в синтаксисе и включенному отображению ошибок, я могу прочитать данные для подключения к БД всего несколькими запросами к файлу плагина:
[themify_quote]Warning: file_get_contents(/home/httpd/vhosts/megatask.mcdir.ru/httpdocs/wp-content/plugins/WP-Logging/logs/a137299_1)…[/themify_quote]
[themify_quote]Warning: file_get_contents(/home/httpd/vhosts/megatask.mcdir.ru/httpdocs/wp-content/plugins/WP-Logging/logs/erhv78sbu4iwGFB)…[/themify_quote]
[themify_quote]Warning: file_get_contents(/home/httpd/vhosts/megatask.mcdir.ru/httpdocs/wp-content/plugins/WP-Logging/logs/a137299.mysql.mchost.ru)…[/themify_quote]
Видим, что сайт расположен на хостинге Макхост (mchost.ru) и там имеется phpMyAdmin для управления базой данных. Я просто загуглил его (mchost phpmyadmin) и, вуаля, вот адрес https://pma.mchost.ru/. Вводим туда добытые данные и попадаем в БД, тут же мы видем таблицу flagThree в которой единственное blob-поле. Похоже, это третья часть нашего флага, поэтому запишем её. flag3 = 28D6CEA3574248
Теперь переходим к таблице wp_comment. Делаем запрос, чтобы найти комментатора.
1 | SELECT * FROM `wp_comments` WHERE `comment_author` = 'SuperHacker1337' |
Тут мы видим IP-адрес (80.78.251.54) и фейковый url (http://fuck.off). Пропингуем этот IP:
1 2 3 4 5 6 7 | root@kali:~# ping -c1 80.78.251.54 PING 80.78.251.54 (80.78.251.54) 56(84) bytes of data. 64 bytes from 80.78.251.54: icmp_req=1 ttl=54 time=16.1 ms --- 80.78.251.54 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 16.151/16.151/16.151/0.000 ms |
Хорошо, хост живой. Теперь запустим сканер nmap по всем портам с определением сервисов.
1 2 3 4 5 6 | Nmap scan report for vm10189.hv8.ru (80.78.251.54) Host is up (0.032s latency). Not shown: 65533 closed ports PORT STATE SERVICE VERSION 22/tcp open ssh (protocol 2.0) 7331/tcp open http Allegro RomPager 4.07 UPnP/1.0 (ZyXEL ZyWALL 2) |
На порту 7331 торчит веб-сервер RomPager, что вкупе с ZyXEL ZyWALL 2 намекает на то, что перед нами веб-интерфейс роутера. Заходим через браузер по адресу http://80.78.251.54:7331/ и видим окно авторизации, которое так же недвусмысленно дает понять, что это Wireless Zyxel ADSL Modem/Router. Верим ему на слово и попробуем несколько самых популярных эксплоитов для роутеров ZyXEL. Для этого я воспользуюсь программой Router Scan v2.51 by Stas’M, которая позволяет проверять роутеры на наличие известных уязвимостей в автоматическом режиме.
Теперь заходим в веб-интерфейс роутера с добытым логином и паролем — admin:EbS7P27
Примечание: уязвимость, с помощью которой программа узнала пароль, это банальное чтение бекапа конфигурационного файла роутера. Это возможно благодаря ошибке в авторизации роутера — http://80.78.251.54:7331/rom-0. Файл запакован по алгоритму Stac LZS, можно найти множество его реализаций на любых языках программирования. Немного больше об этом можете прочитать, например, тут — http://www.hakim.ws/huawei/rom-0/kender.html
Переходим на вкладку Client list и видим список устройств, которые в данный момент подключены к роутеру. Так же можно увидеть названия устройств, что однозначно дает понять, что тот человек, которого мы ищем женщина и зовут ее Дарья. Запомним это.
Немного оглядевшись, я нашел раздел Status->System log, в котором находятся логи. На роутере ведется некий лог geoloction, было бы интересно на него взглянуть. К сожалению, прочитать из веб-интерфейса лог файлы не представляется возможным. Указан только путь по которому они расположены. Ок, тогда попробуем раздобыть читалку файлов.
Еще немного покопавшись, в разделе Maintenance->Diagnostics я наткнулся на возможность выполнить команду snmpwalk.
По всей видимости, параметры, которые поступают из формы в комманду, экранируются. Однако это не панацея и у нас все еще остается возможность добавлять или менять параметры запуска программы snmpwalk. Об этом можно прочитать здесь — http://lab.onsec.ru/2013/03/breaking-escapeshellarg-news.html. Я установил себе snmpwalk и вооружившись командой man начал изучать интересные флаги. Вот что я нашел: можно записывать лог выполнения команды в файл используя ключ -Lf /path/to/file. Это поможет нам залить шелл, если папка веб-сервера доступна для записи. Пробуем ее найти.
Тут я переделал Output format в текстовое поле, чтобы было нагляднее. А также использую флаг дебага -d в Community string чтобы вся информация выводилась в файл. Корень веб-сервера нашли с первой попытки.
Теперь, можно заливать мини-шелл на php и двигаться дальше. Через определенное время, все залитые файлы удаляются, так что не забываем повторять процедуру по новой.
Делаем листинг директории с логами /var/log/ и видим там файл geolocation.log. Читаем его содержимое:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | # flag part one is 1fcf4803e8f1 2015 Apr 14 17:08:13 Connection accept from 04:db:56:ea:e1:2b located around [65.55166101, 57.153955749] area. 2015 Apr 14 07:26:11 Connection accept from 14:99:e2:0b:be:0e located around [65.55195666, 57.153987847] area. 2015 Mar 11 11:54:23 Connection accept from 04:db:56:ea:e1:2b located around [65.551666333, 57.153897833] area. 2015 Mar 11 10:18:15 Connection accept from 14:99:e2:0b:be:0e located around [65.551536667, 57.153962167] area. 2015 Mar 04 14:23:44 Connection accept from 14:99:e2:0b:be:0e located around [65.55195666, 57.153987847] area. 2015 Feb 28 07:16:32 Connection accept from 04:db:56:ea:e1:2b located around [65.55167759, 57.15375174] area. 2015 Feb 26 17:54:02 Connection accept from 04:db:56:ea:e1:2b located around [65.551536667, 57.153962167] area. 2015 Feb 20 13:01:43 Connection accept from 14:99:e2:0b:be:0e located around [65.551445, 57.153788833] area. 2015 Feb 11 08:01:01 Connection accept from 14:99:e2:b1:9a:21 located around [65.551666333, 57.153897833] area. 2015 Feb 02 08:01:01 Connection accept from 04:db:56:ea:e1:2b located around [65.551593396, 57.153954756] area. 2015 Jan 31 09:01:01 Connection accept from 14:99:e2:b1:9a:21 located around [65.55195666, 57.153987847] area. 2015 Jan 25 09:01:01 Connection accept from 04:db:56:ea:e1:2b located around [65.55195666, 57.153987847] area. 2015 Jan 22 09:01:01 Connection accept from 14:99:e2:0b:be:0e located around [65.551593396, 57.153954756] area. 2015 Jan 22 10:01:01 Connection accept from 14:99:e2:0b:be:0e located around [65.551666000, 57.153954000] area. 2015 Jan 13 10:01:01 Connection accept from 04:db:56:ea:e1:2b located around [65.551653396, 57.15395151] area. 2014 Dec 30 10:01:01 Connection accept from 04:db:56:ea:e1:2b located around [65.551483333, 57.153733333] area. 2014 Dec 12 11:01:01 Connection accept from 14:99:e2:b1:9a:21 located around [65.551666000, 57.153954000] area. 2014 Nov 23 11:01:01 Connection accept from 14:99:e2:0b:be:0e located around [65.551536667, 57.153962167] area. 2014 Nov 17 11:01:01 Connection accept from 14:99:e2:0b:be:0e located around [65.551483333, 57.153733333] area. 2014 Nov 09 12:01:01 Connection accept from 14:99:e2:0b:be:0e located around [65.551666333, 57.153897833] area. 2014 Nov 04 12:01:01 Connection accept from 14:99:e2:b1:9a:21 located around [65.551483333, 57.153733333] area. 2014 Oct 20 12:01:01 Connection accept from 14:99:e2:b1:9a:21 located around [65.55167759, 57.15375174] area. 2014 Oct 11 13:01:01 Connection accept from 14:99:e2:0b:be:0e located around [65.55167759, 57.15375174] area. 2014 Oct 07 13:01:01 Connection accept from 04:db:56:ea:e1:2b located around [65.551593396, 57.153954756] area. 2014 Sep 18 13:01:01 Connection accept from 04:db:56:ea:e1:2b located around [65.55195666, 57.153987847] area. 2014 Sep 11 14:01:01 Connection accept from 14:99:e2:b1:9a:21 located around [65.551445, 57.153788833] area. 2014 Aug 24 14:01:01 Connection accept from 14:99:e2:0b:be:0e located around [65.55166101, 57.153955749] area. 2014 Jul 29 14:01:01 Connection accept from 04:db:56:ea:e1:2b located around [65.551445, 57.153788833] area. 2014 Jul 01 15:01:01 Connection accept from 04:db:56:ea:e1:2b located around [65.551536667, 57.153962167] area. 2014 Jun 30 15:01:01 Connection accept from 04:db:56:ea:e1:2b located around [65.551666000, 57.153954000] area. 2014 Jun 06 15:01:01 Connection accept from 14:99:e2:b1:9a:21 located around [65.551536667, 57.153962167] area. |
Теперь у нас есть первая часть флага flag1=1fcf4803e8f1. Так же мы можем видеть список подключений к роутеру и координаты места откуда оно было сделано. Судя по координатам, роутер стоит на одном месте, скорее всего дома у Даши 😉 Посмотрим, где оно находится https://goo.gl/maps/QQZjY
[themify_map address=”65.55166101, 57.153955749″ width=”400px” height=”400px” ]
Так, какая-то маложилая местность на севере России. Возможно, сначала в логе указана долгота, а затем широта. Пробуем поменять местами https://goo.gl/maps/NyBKv
[themify_map address=”57.153955749, 65.55166101″ width=”400px” height=”400px” ]
Вот это уже более похоже на правду — г. Тюмень, жилой дом номер 3 по улице Елецкая.
Теперь у нас есть все данные чтобы попробовать найти фотографию девушки в инстаграмме. Для этого я воспользуюсь одним из сервисов по поиску фотографий в инстаграмм по координатам на карте http://www.gramfeed.com/instagram/map#/57.1539,65.5517/20/-. Не забываем о том, что искомую девушку зовут Дашей, поэтому, ищем что-то похожее. Имеются немало фотографий некой @dashaffox, видимо, это и есть искомый нами человек.
Вернемся к заданию, где сказано, что ник в инстаграм это и есть последняя часть флага. Как мы знаем, флаги — это md5 хэши, которые состоят из символов [0-9a-f]. Но в найденном нике содержатся символы и не подходящие под эту маску. Отбрасываем их и получаем flag2=daaff.
Соединяем все части флага вместе. flag=flag1.flag2.flag3=1fcf4803e8f1daaff28d6cea3574248. Хм, не хватает одного символа до валидного размера хэша. Вообще, можно не парится и просто его сбрутить, вариантов тут не особо много. Но это не наш метод 😉 Повнимательнее посмотрим на ник и на описание таска “…не все так очевидно…”. В логине присутствует буква “о” она очень похожа на цифру “0”, используем и её тоже и пробуем добавить флаг. Вуаля, флаг сработал, значит наше предположение было верным. Итак, наш флаг: flag=1fcf4803e8f1daaff028d6cea3574248. Задание выполнено.
Вот такое незамысловатое решение. Все уязвимости, которые были проэксплуатированны при решении этого таска, встречались мне и на реальных проектах. Некоторые места были довольно мудреными и, возможно, притянутыми за уши. Как вторая часть флага, например. Но, в целом, задание было довольно интересным и актуальным.
Если вам было что-либо непонятно, то я прикладываю подрообное видео с решением данного таска. Оно должно снять все вопросы.
Всем спасибо за внимание. И до новых встреч!
sz
May 20, 2015 @ 5:19 pm
Спасибо большое за обзор, особенно за видео формат.
k
May 25, 2015 @ 11:07 am
Спасибо, прочитал с интересом.
На мой взлгяд, подбор второй части флага это полная шиза, могли бы сделать более логичное задание, например, запихнуть данные в одну из фототграфий аккаунта.