WordPress REST API 内容注入漏洞分析
0x00 漏洞简述
1. 漏洞简介
在REST API
自动包含在Wordpress4.7
以上的版本,WordPress REST API
提供了一组易于使用的HTTP端点,可以使用户以简单的JSON格式访问网站的数据,包括用户,帖子,分类等。检索或更新数据与发送HTTP请求一样简单。上周,一个由REST API
引起的影响WorePress4.7.0
和4.7.1
版本的漏洞被披露,该漏洞可以导致WordPress所有文章内容可以未经验证被查看,修改,删除,甚至创建新的文章,危害巨大。
2. 漏洞影响版本
- WordPress4.7.0
- WordPress4.7.1
0x01 漏洞复现
Seebug上已经给出详细的复现过程,在复现过程中可以使用已经放出的POC来进行测试。
0x02 漏洞分析
其实漏洞发现者已经给出了较为详细的分析过程,接下来说说自己在参考了上面的分析后的一点想法。
WP REST API
首先来说一下REST API
。
控制器
WP-API
中采用了控制器概念,为表示自愿端点的类提供了标准模式,所有资源端点都扩展WP_REST_Controller
来保证其实现通用方法。
五种请求
之后,WP-API
还有这么几种请求(也可以想成是功能吧):
- HEAD
- GET
- POST
- PUT
- DELETE
以上表示HTTP客户端可能对资源执行的操作类型。
HTTP客户端
WordPress本身在WP_HTTP
类和相关函数中提供了一个HTTP客户端。用于从另一个访问一个WordPress站点。
资源
简单来说,就是文章,页面,评论等。
WP-API
允许HTTP客户端对资源执行CRUD操作(创建,读取,更新,删除,这边只展示和漏洞相关的部分):
-
GET /wp-json/wp/v2/posts
获取帖子的集合:
-
GET /wp-json/wp/v2/posts/1
获取一个ID为1的单独的Post:
可以看到ID为1的文章标题为Hello World,包括文章的路由也有。
路由
路由是用于访问端点的“名称”,在URL中使用(在非法情况下可控,就像这个漏洞一样)。
例如,使用URLhttp://example.com/wp-json/wp/v2/posts/123
:
- 路由(route)是
wp/v2/posts/123
,不包括wp-json
,因为wp-json
是API本身的基本路径。 - 这个路由有三个端点:
- GET触发一个
get_item
方法,将post数据返回给客户端。 - PUT触发一个
update_item
方法,使数据更新,并返回更新的发布数据。 - DELETE触发
delete_item
方法,将现在删除的发布数据返回给客户端。
- GET触发一个
静态追踪
知道了WP-API
的路由信息以及其操作方式,可以根据其运行的思路来看一下具体实现的代码。
我们看一下/wp-includes/rest-api/endpoints/class-wp-rest-post-controller.php
:
根据上面的信息,我们可以知道这是注册controller对象的路由,实现路由中端点方法。
在这里,如果我们向/wp-json/wp/v2/posts/1
发送请求,则ID参数将被设置为1:
同时,注意一下这里:
可以看到在register_rest_route
中对路由进行了正则限制:
也就是防止攻击者恶意构造ID值,但是我们可以发现$_GET
和$_POST
值优先于路由正则表达式生成的值:
这边没有找到ID为123hh
的项目,所以返回rest_invalid
。
现在我们可以忽略路由正则的限制,来传入我们自定义的ID。
接下来在审查各个端点方法中,找到了update_item
这个方法,及其权限检查方法update_item_permissions_check
:
可以看到,此函数通过检查文章是否实际存在,以及我们的用户是否有权限编辑这边文章来验证请求。但是当我们发送一个没有响应文章的ID时,就可以通过权限检查,并允许继续执行对update_item
方法的请求。
具体到代码,就是让$post
为空,就可以通过权限检查,接下来跟进get_post
方法中看一下:
从代码中可以看出,它是用wp_posts
中的get_instance
静态方法来获取文章的,跟进wp_posts
类,位于/wp-includes/class-wp-post.php
中:
public static function get_instance( $post_id ) {
global $wpdb;
if ( ! is_numeric( $post_id ) || $post_id != floor( $post_id ) || ! $post_id ) {
return false;
}
可以看到,当我们传入的ID不是全由数字字符组成的时候,就会返回false,也就是返回一个不存在的文章。从而get_post
方法返回null,从而绕过update_item_permissions_check
的权限检测。
回头再看一下可执行方法upload_item
:
在这边将ID参数装换为一个整数,然后传递给get_post
。而PHP类型转换的时候回出现这样的情况:
所以,也就是说,当攻击者发起/wp-json/wp/v2/posts/1?id=1hhh
请求时,便是发起了对ID为1的文章的请求。下面为利用[exploit-db][2]上的POC来进行测试:
- 新建文章:
- 测试:
- 测试结果:
多想了一下
乍一看,感觉这个洞并没有什么太大的影响,但是仔细想了一下,危害还是很大的。先不说WordPress页面执行php代码的各种插件,还有相当一部分的WordPress文章可以调用短代码的方式来输出特定的内容,以及向日志中添加内容,这是一个思路。
另一个思路就是可以进行对原来文章中的指定超链接进行修改,从而进行钓鱼。
还有一个思路,就是利用WordPress文章中解析html以及JavaScript文件包含的做法,辅助其他方法,进行攻击。
0x03 diff比较
对于该漏洞,关键的修改在/wp-includes/class-wp-post.php
中:
更改了对于$post_id
的参数的传入顺序和判断条件,防止了我们传入数字+字母这样的格式进行绕过。
0x04 修补方案
将WordPress更新到最新版本。
0x05 参考链接
- https://www.seebug.org/vuldb/ssvid-92637
- https://www.exploit-db.com/exploits/41223/
- https://blog.sucuri.net/2017/02/content-injection-vulnerability-wordpress-rest-api.html
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(69)-微信公众平台开发-功能概述
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(68)-微信公众平台开发- 资源环境准备
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(70)-微信公众平台开发-成为开发者
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(70)-微信公众平台开发-成为开发者
- 一口价!3杂7bc.com16万易主
- Silverlight初级教程-动画
- WCF技术剖析之十:调用WCF服务的客户端应该如何进行异常处理
- [原创-总结]WCF技术剖析系列总结篇
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(63)-WebApi与Unity注入
- 分布式高并发下mysql数据库读写分离
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(64)-补充WebApi与Unity注入-配置文件
- 身临其境的建筑体验:3D打印和人工智能相结合的“星形胶质细胞”
- C语言真的太强大了,C几乎无处不在!
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(65)-MVC WebApi 用户验证 (1)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 如果JSP中无法自动提示EL表达式的解决方法(针对普通项目和maven有不同的方法)
- Android通过ExifInterface判断Camera图片方向的方法
- Python PyQt5整理介绍
- django API 中接口的互相调用实例
- Python Numpy中数据的常用保存与读取方法
- python使用梯度下降和牛顿法寻找Rosenbrock函数最小值实例
- Django Admin设置应用程序及模型顺序方法详解
- 在 Pycharm 安装使用black的方法详解
- Android开发手机无线调试的方法
- Android Studio Gradle插件版本与Gradle版本之间的对应关系
- Android Usb设备的监听(Dev)外设端口的判定以及耳机的插拔
- Android自定义View实现简单文字描边功能
- 在脚本中单独使用django的ORM模型详解
- Android5.0之Activity的转场动画的示例
- Django-rest-framework中过滤器的定制实例