尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
三道防线同时失效——WordPress batch 端点“校验身份与执行身份分裂”漏洞深度解析
三道防线同时失效——WordPress batch 端点“校验身份与执行身份分裂”漏洞深度解析漏洞类型REST API 路由混淆 → SQL 注入时间盲注影响组件WordPress REST API/batch/v1端点分析基础WordPress 6.9.4 源码本文仅作原理分析与安全学习请勿用于未授权测试。0x00 最有意思的地方没有一道防线写错这个漏洞最值得琢磨的不是绕过了哪个 WAF、哪个过滤函数而是——三道独立的防线一道都没写错却同时失效。因为它们守的都不是那扇门。数据校验的经典假设是“进来的每个参数都会被校验规则覆盖到。”这个漏洞用一套“偷天换日”打破了这个假设校验的时候用 A 路由的参数表点名执行的时候调的却是 B 路由的处理器。注入参数不在任何一张名单上——三道看门狗全部“查无此人”。我把它称为“校验身份与执行身份分裂”。下面从头拆。0x01 预备知识参数校验表是怎么“挂”到请求上的WordPress REST API 的每个路由在注册时register_rest_route都会挂一张参数说明书schemaregister_rest_route(wp/v2,/posts,array(array(methodsWP_REST_Server::READABLE,callbackarray($posts_controller,get_items),args$posts_controller-get_collection_params(),// ← schema 挂在这),));运行时路由匹配成功后这张表会被挂到请求对象上$request-get_attributes()[args]随后的has_valid_params()校验和sanitize_params()清洗都照它逐项执行。以 posts 路由为例author_exclude参数的 schema 是author_excludearray(typearray,itemsarray(typeinteger),// 每个元素必须是整数),记住一个伏笔这张表跟着“路由匹配结果”走——谁匹配的请求就挂谁的表。后面的整个漏洞链都建立在这句话上。0x02 正常情况下防线是有效的直接注入必死先验证防御正常工作的样子。直接请求GET /wp-json/wp/v2/posts?author_excludeSELECT IF(ASCII(SUBSTRING((SELECT user_pass FROM wp_users WHERE user_loginadmin),1,1))30,SLEEP(0.5),0)结果HTTP 400rest_invalid_param。注入串连控制器的面都见不到。失效过程schema 声明typearray校验函数先把字符串按空格/逗号preg_split拆成数组——注入串被拆成十几个碎片items integer触发逐元素校验第一个碎片SELECT就不是数字报错指纹很说明问题author_exclude[0] is not of type integer——[0]证明死在数组元素级递归校验上结论schema 校验本身没有缺陷。想注入就不能走正门。0x03 根因batch 派发器的两段循环对不上/batch/v1端点允许一次 POST 提交多个子请求批量执行处理函数是WP_REST_Server::serve_batch_request_v1()。它的结构是两段 foreach// ── 阶段一校验每个子请求按自己的路由匹配 校验 ──foreach($requestsas$single_request){if(is_wp_error($single_request)){$validation[]$single_request;continue;// ★ 失败项不进 $matches}$match$this-match_request_to_handler($single_request);// 挂自己的参数表$matches[]$match;// ... has_valid_params() sanitize_params() ...}// ── 阶段二执行按下标取 $matches[$i] 派发 ──foreach($requestsas$i$single_request){$match$matches[$i];// ★ 错位点list($route,$handler)$match;$result$this-respond_to_request($single_request,$route,$handler,$error);}问题在哪两个循环的数组长度可能不一致。阶段一中解析失败的子请求会continue跳过不进入$matches——于是$matches比$requests短了一格。阶段二却仍然按下标一一对应地取值全体左移阶段一校验循环谁出错谁出局不进 matches requests[0] 探针 http://: → wp_parse_url 解析失败 → continue requests[1] 受害者 → 按自己路由匹配 → matches[0] requests[2] 供货商 → 按自己路由匹配 → matches[1] ↑ matches 比 requests 短一格 阶段二执行循环按下标取 match requests[1] 受害者 → matches[1] 供货商的 match ★ 偷换发生在这里 requests[2] 供货商 → matches[2] 不存在 → 报错利用已完成无害尾巴于是出现了本文的核心命题——身份分裂校验阶段受害者请求挂着自己的路由参数表被校验执行阶段它却被派发到供货商路由的处理器上执行。校验用的身份和执行用的身份不是同一个。http://:这个看似无意义的 URL 就是“探针”它唯一的作用就是解析失败、制造那个缺口。一个探针 把整条派发流水线往后推一格。0x04 三条铁律逼出 payload 的形状利用不是拍脑袋写的是被三条硬约束逼出来的铁律来源后果① 注入必须走 GETquery string注入点在author_exclude参数请求必须是 GET 集合请求② batch 顶层子请求 method 只有 POST/PUT/PATCH/DELETErequests参数的 schema 枚举GET 不能直接当顶层子请求否则整个 batch 直接 400③ 一次错位只能偷换一次 handler错位机制本身攻击需要两次偷换 → 嵌套两层三条合起来payload 的形状就唯一了把 GET 们藏进一个 POST 的 body 里再套两层 batch。0x05 完整利用链双重偷换{requests:[{method:POST,path:http://:},{method:POST,path:/wp/v2/posts,body:{requests:[{method:GET,path:http://:},{method:GET,path:/wp/v2/categories?author_exclude注入串},{method:GET,path:/wp/v2/posts}]}},{method:POST,path:/batch/v1}]}每一层都是同一个三人组配方探针制造缺口→ 受害者偷到下一个的 handler→ 供货商被偷。外层偷换探针让/wp/v2/posts请求偷到/batch/v1的 handler即serve_batch_request_v1本身——这个“创建文章”的 POST 被劫持成了“批量派发器”藏在它 body 里的内层 requests 才被执行。内层偷换探针让categories请求偷到posts的 handlerget_items——带着author_exclude注入参数的请求被 posts 的代码处理。两个关键追问Q1body 里藏 batchschema 为什么不拦看 body 参数的 schema 定义bodyarray(typeobject,propertiesarray(),// 空的additionalPropertiestrue,// 不深入校验内容),顶层校验只确认“body 是个 object”就放行了根本不往下看。一句话限制卡在“schema 点名”这一关而内层 requests 从没被点过名。Q2内层全是 GET不违反 method 枚举吗枚举限制是顶层requests参数的 schema 校验行为发生在外层派发前。内层 requests 来自 body从未经过那道校验而serve_batch_request_v1构造内层请求时直接采用数组里的 method无二次枚举——内层 GET 畅通无阻。0x06 三道防线逐一失效点现在把三道防线放在一起看每一道自身逻辑都正确防线位置失效原因① 校验has_valid_params()WP_REST_Request迭代categories 的参数表逐项点名——表里只有分类相关的十几个参数author_exclude不在其中循环根本轮不到它② 清洗sanitize_params()同上关键一行if (!isset($attributes[args][$key])) continue;清洗是迭代表、不是迭代输入——表外参数直接continue既不验也不洗却随请求对象原样保留③ SQL 层absint兜底WP_Query 的author__not_in处理块if ( is_array(...) )为假注入串是字符串→ absint 整段跳过 → 原样拼进 SQL缺席参数化查询$where . ... NOT IN ($author__not_in)直接字符串内插本该存在的最后防线根本没有这就是“查无此人”的全部含义数据清洗不是“洗所有进来的数据”而是“照着名单点名点到谁洗谁”。正常请求时author_exclude在 posts 的名单上 → 被拆解 → 死。这次categories 请求身上挂的是 categories 的名单没有这个参数 → 三道看门狗全部放行。不是消毒写错了是注入根本不在任何一张名单上。0x07 落地验证时间盲注注入串最终拼进 SQL 的post_author NOT IN (...)...post_authorNOTIN(SELECTIF(ASCII(SUBSTRING((SELECTuser_passFROMwp_usersWHEREuser_loginadmin),1,1))30,SLEEP(1),0))...验证方式是时间差分管理员密码哈希首字符是$ASCII 36——猜测真值结果3036 30 为真SLEEP 执行响应慢 ≈ 1s4036 40 为假秒回≈ 0.05s一次请求一个比特。对每个字符做 8 次 0~255 二分34 字符的哈希约 279 次请求即可完整提取——证明注入能读取数据库任意数据。0x08 跳出个案这一类漏洞的通用启示这才是研究它的价值。剥掉 WordPress 的外壳这是一个漏洞类凡“校验与执行分离、且两者之间用位置/索引传递关联”的架构都可能出现校验对象与执行对象的错位。它是 TOCTOU 的架构版本也符合 Confused Deputy混淆代理人模型。审计时可以按这个 checklist 找同类问题找“先校验、后执行”分成两段循环的代码检查两个循环靠索引还是唯一键对齐找“失败项 continue 跳过、但不补偿索引”的模式缺口来源找 schema 里properties为空 additionalProperties: true的“透明对象”走私通道找“迭代白名单表”式清洗——表外参数默认放行检查最终拼接点是否参数化纵深防御的最后一环对应的防御原则校验结果与执行对象用唯一键绑定如请求 ID禁止位置索引传递Fail-closed批量请求中出现非法子请求整体拒绝而不是跳过继续body 等容器字段深度定义 schemaadditionalProperties: false不留透明通道清洗改为迭代输入的未知参数默认拒绝而非仅迭代已知表SQL 层参数化永不缺席——本案中前三道全破若NOT IN用了占位符注入依然无法落地0x09 结语回头看整条链骗过枚举body 透明→ 骗过派发双重错位→ 骗过消毒查无此人。没有任何一行防御代码被“打败”它们只是被调换了服务的对象。对防御者而言这个案例提醒我们安全属性必须绑定在不可篡改的身份上而不是位置和顺序上。参考资料WordPress 6.9.4 源码class-wp-rest-server.php/class-wp-rest-request.php/class-wp-rest-posts-controller.php/class-wp-query.php。本文仅供学习交流渗透测试须在授权环境下进行。
RELATED

相关推荐

SQL注入之数据库信息收集学习笔记

SQL注入之数据库信息收集学习笔记

一、什么是数据库信息收集 数据库信息收集,也叫数据库指纹识别,是SQL注入过程中一个非常重要的环节。在成功发现注入点后,攻击者不能直接“一步登天”拿到所有数据,而是需要先了解目标数据库的类型、版本、用户权限、数据库结构等…

📅 2026/10/14 3:17:22
SDD 规范驱动实战:我用 Vibe Coding 开发了一个 AI 网页翻译 Chrome 插件

SDD 规范驱动实战:我用 Vibe Coding 开发了一个 AI 网页翻译 Chrome 插件

阅读英文技术文章时,你是不是也经历过:复制段落 → 丢进翻译工具 → 格式乱掉 → 再粘回笔记软件 → 手动排版。折腾半天,真正阅读的时间还没排版多。我最近用 Vibe Coding SDD(Specification-Driven Development,规范…

📅 2026/10/30 18:32:59
把订单同步源从内网机器切到云端:一次「热备端同步源切换」实战

把订单同步源从内网机器切到云端:一次「热备端同步源切换」实战

> 本文记录了一次生产环境调整的真实过程:热备端的订单匹配工具原本从内网某台机器同步代码和数据,现在要整体切换成从云端系统同步。涉及「改配置 → 备份可回滚 → 干跑验证 → 定时任务自动跑」的完整链路,踩过的坑也写出来。一、背景&…

📅 2026/9/15 14:58:13
MORE NEWS

更多资讯

📰

WebAssembly逆向实战:荔枝网Wasm加密参数分析

1. 荔枝网Wasm加密参数逆向分析背景在当今Web安全领域,越来越多的网站开始采用WebAssembly(Wasm)技术来实现关键业务逻辑的加密保护。荔枝网作为国内知名的音频分享平台,其核心接口参数采用了Wasm加密方案,这给传统的JS逆向工作带来了新的挑战…

📰

输入受限双端队列的特性与应用解析

1. 特异双端队列的特性解析这个特殊的双端队列结构在传统双端队列基础上增加了操作限制,我们可以将其称为"输入受限双端队列"(Input-restricted deque)。它的核心特性体现在三个维度:插入灵活性:支持在头部(HEAD)和尾部(TAIL)两个端…

📰

AI开发者的新操作系统:Skills技术原理与实战指南

1. “skills”不是功能菜单,而是现代AI开发者的新型能力操作系统最近在几个前端技术群和AI工具交流频道里,反复看到有人发截图问:“为什么我装了Claude Code,点开Skills面板却全是灰色?”,“Codex里选了‘W…

📰

OpenRig:基于Node.js与tmux的本地AI工作流调度范式

1. OpenRig 是什么:一个被误读但极具潜力的本地 AI 工作流调度器OpenRig 这个名字在当前中文技术社区里,正经历一场典型的“标签漂移”——它既不是某个广为人知的开源项目官方名称,也不是某家大厂发布的标准化工具,而是在 Node.j…

📰

Claude Skills 本质是 MCP 协议下的可验证能力契约

1. “awesome-claude-skills”不是资源列表,而是一份正在演化的AI Agent能力工程实践地图你搜到的awesome-claude-skills,大概率点开的是 GitHub 上一个 star 数不断上涨的仓库——它名字里带“awesome”,但内核早已脱离传统“Awesome List”…

📰

自考论文写作利器:8款AI工具实测与组合使用指南

1. 自考论文写作的痛点与AI工具的价值作为一名自考过来人,我深知论文写作是让无数自考生头疼的"拦路虎"。时间碎片化、资料获取难、写作效率低是自考生的三大痛点。白天上班晚上学习的状态,让我们很难有大块时间泡图书馆;而学术资源…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬