WordPress REST API 参数校验机制剖析:为什么 author__not_in 无法直接盲注?

以下内容仅为个人学习分析,本人技术水平有限,理解可能存在疏漏与错误,欢迎大家批评指正,内容仅用于安全学习研究,严禁用于实际攻击测试。

摘要:

本文梳理 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参数。

  1. if1:不是 API 请求 → return 结束。是 API 请求继续向下。
  2. if2:API 路由参数类型不对 → 返回 400 错误并终止。类型正确继续。
  3. 定义,拿到 server 实例,清理路由末尾斜杠。REST_REQUEST=true
  4. 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 校验流程

  1. 参数名称:author_exclude
  2. type=array:首先校验传入的数据整体类型必须为数组,不是数组则直接校验失败。
  3. 检查数组每个元素:遍历数组内部所有成员,对数组中每一项执行校验。
  4. items:items 定义数组内部每一个元素的校验规则。
  5. type=integer:数组里的每一个元素,都必须是整数类型。
  6. 整数验证:对数组元素做整数合法性校验;只要数组中任意一项不是整数,整个参数校验失败,返回错误。

验证失败流程

如果参数不符合要求:

场景:传入参数不符合 schema 规则,示例请求参数 author_exclude=abc

  1. 参数传入 ,开始参数校验流程。author_exclude=abc
  2. 进入整数校验函数 ,对数组内的元素做整数合法性检查。rest_validate_integer_value_from_schema()
  3. 传入值不是整数,验证失败。abc
  4. 生成 错误对象,封装错误信息。WP_Error
  5. 错误标识为 (参数非法)。rest_invalid_param
  6. 向上逐层返回错误,REST 接口最终返回 HTTP 400 响应给客户端。

为什么 get_items() 可能没有执行?

正常流程

  1. 执行 dispatch() 请求分发
  2. 执行参数验证
  3. 参数验证通过
  4. 调用执行业务方法 get_items()

参数错误的流程

  1. 执行 dispatch() 请求分发
  2. 参数验证失败
  3. 直接返回错误响应

原理: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

  1. 执行 rest_api_loaded(),识别这是 REST 请求。
  2. 调用 rest_get_server(),获取 REST 服务处理对象。
  3. 执行 WP_REST_Server::serve_request(),创建 WP_REST_Request 请求对象。
  4. 调用 dispatch() 进行路由匹配,匹配到路由 /wp/v2/posts。
  5. 执行参数 schema 验证流程。
  6. 调用 rest_validate_value_from_schema(),对参数做 schema 规则、数据类型检查。
  7. 调用 rest_validate_integer_value_from_schema(),校验author_exclude是否为整数类型。
  8. 参数校验全部通过后,执行 WP_REST_Posts_Controller::get_items()。
  9. 读取请求对象中的参数 $request'author_exclude'
  10. 将 REST 接口参数转换为 WP_Query 查询参数,生成 $args'author__not_in'
  11. 将组装好的查询参数传入 WP_Query。
  12. 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 参数错误,无法获取注入所需的页面差异、时间延迟等反馈,攻击无法生效。

相关推荐
椿.湫1 小时前
kubenetes pod管理
linux·运维·服务器
kobe_OKOK_1 小时前
ubuntu server 打包成一个压缩文件,并且在另一台电脑中解压缩
服务器·数据库·ubuntu
ITmaster07311 小时前
Vibe Coding 时代:Vue 消失了还是 React 太强?
前端·vue.js·react.js
又是重名了1 小时前
集团项目信创改造3 mysql to dm 插入获取主键
数据库·mysql·dameng
zengjuan10051 小时前
[特殊字符]️ 松鼠备份 | 数据库备份方案即将重磅上线!三大模式组合拳,精准守护SQL Server每一笔数据
数据库·sqlserver·数据库备份·全量备份·增量备份·差异备份·日志和事务备份
WebInfra1 小时前
Rspack 2.2 发布:30+ 项性能优化,拥抱 Solid 2.0
前端·javascript·前端框架
GuoFeng.Wan1 小时前
深入理解经典蓝牙的channel
linux·服务器·网络
朝发如雪1 小时前
快速掌握Linux(1)(Linux文件操作)(标准IO)
linux·运维·服务器
额额额对了1 小时前
Linux 进程管理详解:从概念到实战
java·服务器·前端