以下内容仅为个人学习分析,本人技术水平有限,理解可能存在疏漏与错误,欢迎大家批评指正,内容仅用于安全学习研究,严禁用于实际攻击测试。
摘要:
本文梳理 WordPress REST API 请求处理流程,分析该接口无法直接盲注的原因。
请求经过 REST 入口、实例初始化、身份认证、路由匹配后,会优先执行参数校验。受 Schema 约束,要求为数组且元素只能是整数 ,非法参数直接返回 400 错误,不会执行业务函数。校验通过后,再通过参数映射转为内部参数,最终生成 SQL 查询。author_exclude``get_items()``WP_Query``author__not_in
防护分为三层:参数映射白名单拦截内部查询键直接传入;Schema 强类型校验过滤 SQL 字符串载荷;校验失败直接终止流程,载荷无法抵达 SQL 拼接阶段。 因此即便存在理论注入风险,外部直接盲注攻击无法成功。author__not_in
为什么不能直接注入
直接传 /wp/v2/posts?author_exclude=注入 → HTTP 400 rest_invalid_param。

第一重防御$parameter_mappings,参数是否 合法
$parameter_mappings 作用
参数映射字典:把REST 对外暴露的 API 参数名,翻译成WP_Query 内部识别的查询键名。
示例:
- API 传参 author_exclude → 映射为内部字段 author__not_in
- API 传参 exclude → 映射为内部字段 post__not_in
传递的参数必须是这里面的有限参数之一,不然表中没有对应字段

第二重防御:参数必须是数组,元素必须是数字
这儿的内容在上一篇文章中有解释,不再赘述

调试目标:GET /?rest_route=/wp/v2/posts&author_exclude=1
在 WordPress 内部的执行流程。本次重点分析:为什么 author_exclude 能进入 WordPress 文章查询流程。
第一步:REST API入口
作用
判断当前请求是不是 REST API 请求。
WordPress 收到:/?rest_route=/wp/v2/posts后,会先判断有没有:rest_route参数。
- if1:不是 API 请求 → return 结束。是 API 请求继续向下。
- if2:API 路由参数类型不对 → 返回 400 错误并终止。类型正确继续。
- 定义,拿到 server 实例,清理路由末尾斜杠。REST_REQUEST=true
- if3:处理后路由为空 → 设置为根路由。/
第二步:获取 REST Server
拿到 WP_REST_Server 单例:
存储全部 REST API 路由表,提供路由注册、请求匹配、权限校验、执行回调、输出 JSON 整套能力
$routes:数组,保存所有注册的 API 路由(路径、请求方法、回调、参数规则)
第三步:构建 WP_REST_Request 对象

创建一个请求对象$request,用来把 HTTP 请求中的所有数据(路径、参数、表单、文件、头部、原始内容)统一打包进去,方便后续的 REST API 处理逻辑从这一个对象里获取所有请求信息,而不是从分散的全局变量中挨个读取。
进入路由分发
调用:dispatch()
check_authentication() 作用:**身份认证检查,校验当前 API 请求是否登录、权限是否合法。**认证失败:返回 对象(错误对象)WP_Error。认证成功:返回非 WP_Error 的值(true/null 等)

认证成功,进入 dispatch:
请求: 获取文章列表,部分接口允许游客访问GET /wp-json/wp/v2/posts
check_authentication() 执行身份检查,这个接口不需要登录,认证校验通过,返回 true,is_wp_error(true) 的返回值为 false,
!is_wp_error(true) → true , if 条件成立 执行 dispatch($request) 。
在路由表匹配 routes /wp/v2/posts,调用对应的回调函数查询文章。返回文章数据,保存接口数据result,后续把文章列表以 JSON 返回浏览器
认证失败,不进 dispatch:
请求: 创建文章,必须管理员登录POST /wp-json/wp/v2/posts
用户没有携带登录 token,未登录发起请求,check_authentication()检测无有效身份,返回错误对象,内容:未授权,不能创建文章WP_Error,
is_wp_error( $result ) 为 true
!is_wp_error( $result ) → false , if 不执行
不会调用 dispatch () ,不会执行创建文章的业务代码,直接把这个 WP_Error 错误输出 JSON 给客户端
第四步:match_request_to_handler() 路由匹配(dispatch 内部)
调用了 match_request_to_handler() 方法,根据 $request 对象中的路径,从路由表中匹配对应的处理函数。
例如:
请求路径:请求方法 GET/wp/v2/posts。

$method = $request->get_method(); // GET/POST
$path = $request->get_route(); // /wp/v2/posts
读取request中的 path=/wp/v2/posts、method=GET,遍历 / 查找this->routes数组
匹配到这条路由记录,拿到对应的处理器:控制器 WP_REST_Posts_Controller、回调方法get_items(),还有参数定义、权限回调等整套信息。返回匹配结果给$matched变量。
has_valid_params()读取该规则完成参数校验


三项校验:
参数类型是否正确
参数取值范围(page≥1、per_page≤100)
参数格式(ID 是否正整数、分类 ID 是否存在)
但是注意此时,还没有执行 get_items()。因为还需要先检查参数。
第五步:获取接口参数规则

作用
定义:这个 REST 接口允许接收哪些参数。
例如:
'author_exclude'=>array(
'type'=>'array',
'items'=>array(
'type'=>'integer'
)
)
参数:
author_exclude
要求:第一层:必须是:array,数组里面:每个元素必须:integer(int)
第六步:schema参数验证
例如:author_exclude\[\]=abc,此时
rest_validate_value_from_schema(
'abc',
array(
'type'=>'integer'
)
)

get_attributes()返回当前 REST 路由的属性信息里面包含:args也就是当前接口允许哪些参数。
attributes\['args'\]\[param]等价$attributes'args''author_exclude'
所以
$value=abc
$args=array(
'type'=>'array',
'items'=>array(
'type'=>'integer')
)
接下来查看到type
如下图在这里再次调用:遍历数组中的每一个元素,然后把每个元素交给 rest_validate_value_from_schema() 再次验证items是否对应, $args'items'也就是现在:array('type'=>'integer')

type的值对应为integer,

进入integer

判断传入的值是不是合法整数,不是则返回false

作用
根据接口定义的 schema 检查参数。
流程:
author_exclude 参数 schema 校验流程
- 参数名称:author_exclude
- type=array:首先校验传入的数据整体类型必须为数组,不是数组则直接校验失败。
- 检查数组每个元素:遍历数组内部所有成员,对数组中每一项执行校验。
- items:items 定义数组内部每一个元素的校验规则。
- type=integer:数组里的每一个元素,都必须是整数类型。
- 整数验证:对数组元素做整数合法性校验;只要数组中任意一项不是整数,整个参数校验失败,返回错误。
验证失败流程
如果参数不符合要求:
场景:传入参数不符合 schema 规则,示例请求参数 author_exclude=abc
- 参数传入 ,开始参数校验流程。author_exclude=abc
- 进入整数校验函数 ,对数组内的元素做整数合法性检查。rest_validate_integer_value_from_schema()
- 传入值不是整数,验证失败。abc
- 生成 错误对象,封装错误信息。WP_Error
- 错误标识为 (参数非法)。rest_invalid_param
- 向上逐层返回错误,REST 接口最终返回 HTTP 400 响应给客户端。
为什么 get_items() 可能没有执行?
正常流程
- 执行 dispatch() 请求分发
- 执行参数验证
- 参数验证通过
- 调用执行业务方法 get_items()
参数错误的流程
- 执行 dispatch() 请求分发
- 参数验证失败
- 直接返回错误响应
原理:WordPress REST 请求在 dispatch() 内部会优先做参数校验;校验一旦失败直接返回错误,后续业务处理函数 get_items () 就不会被调用。
第七步:进入 Posts 查询阶段
只有参数验证通过才进入:

这里开始:REST参数进入:$request
例如:request\['author_exclude'\],然后转换:args'author__not_in'
作用:把 REST API 参数转换成:WordPress内部查询参数:
WordPress在这儿 定义:哪些 REST 参数对应哪些 WP_Query 参数。
开始赋值

遍历:$parameter_mappings
第一次得到:$api_param=author_exclude
同时:$wp_param=author__not_in
args\[wp_param]=request\[api_param];这里直接参数转移。
isset( registered\[api_param] )检查:REST接口有没有注册这个参数。也就是有没有author_exclude这个注册位置。
此处REST允许author_exclude\[\]并且里面必须是整数。

进入 WP_Query
这里负责:根据查询参数生成 SQL。
例如输入:
$args=array(
'author__not_in'=>array(1)
);
WP_Query读取author__not_in,,然后生成:类似:WHERE post_author NOT IN (1)
关键数据流总结
浏览器发起请求:/?rest_route=/wp/v2/posts&author_exclude=1
- 执行 rest_api_loaded(),识别这是 REST 请求。
- 调用 rest_get_server(),获取 REST 服务处理对象。
- 执行 WP_REST_Server::serve_request(),创建 WP_REST_Request 请求对象。
- 调用 dispatch() 进行路由匹配,匹配到路由 /wp/v2/posts。
- 执行参数 schema 验证流程。
- 调用 rest_validate_value_from_schema(),对参数做 schema 规则、数据类型检查。
- 调用 rest_validate_integer_value_from_schema(),校验author_exclude是否为整数类型。
- 参数校验全部通过后,执行 WP_REST_Posts_Controller::get_items()。
- 读取请求对象中的参数 $request'author_exclude'。
- 将 REST 接口参数转换为 WP_Query 查询参数,生成 $args'author__not_in'。
- 将组装好的查询参数传入 WP_Query。
- WP_Query 根据参数拼接 SQL 语句,执行数据库查询。
总结:为什么不能直接使用 /wp/v2/posts?author__not_in=xxx 实施盲注
结合完整的 REST API 数据流可以得出结论:即便author__not_in本身存在潜在 SQL 注入风险,也无法直接通过/wp/v2/posts?author__not_in=xxx完成盲注攻击。WordPress REST API 会在参数传递给WP_Query、拼接 SQL 语句之前执行多层校验,注入载荷无法抵达 SQL 生成环节。
第一重拦截:参数白名单映射机制
REST API 对外公开的参数为author_exclude,author__not_in是仅在内部使用的WP_Query查询键。接口依靠$parameter_mappings映射白名单,将外部传入的author_exclude转换为内部查询参数author__not_in。 如果直接在 URL 提交author__not_in=xxx,该参数不在映射白名单内,会被识别为非法请求参数,直接返回HTTP 400 rest_invalid_param,不会向下走到查询逻辑。
第二重拦截: Schema 强类型校验
即便攻击者使用合法对外参数名author_exclude发起请求,参数 Schema 会强制约束:参数必须为数组类型,数组内部每一项都只能是整数。 若传入author_exclude携带 SQL 注入字符串载荷,会先后经过rest_validate_value_from_schema()做基础 Schema 校验、rest_validate_integer_value_from_schema()校验数组元素是否为整数。字符串类注入载荷会触发类型校验失败,接口依旧返回HTTP 400 rest_invalid_param。
第三重拦截:校验失败直接终止业务流程
在dispatch()调度流程中,has_valid_params()优先执行全部参数校验。只要任意参数校验不通过,REST 框架会直接返回错误响应,业务处理函数 get_items() 不会被调用执行。 注入载荷根本无法进入get_items()内部,也就没有机会赋值给WP_Query查询参数,完全接触不到 SQL 拼接逻辑,时间盲注、布尔盲注都没有触发条件。
最终结论
author__not_in虽具备理论层面的 SQL 注入风险,但 WordPress REST API 依靠参数映射白名单、 Schema 数组与整数强类型校验、校验失败直接终止业务这多层防御,将注入载荷拦截在 SQL 拼接阶段之前。直接传入author__not_in攻击,只会得到 400 参数错误,无法获取注入所需的页面差异、时间延迟等反馈,攻击无法生效。




