Products
GG网络技术分享 2025-03-18 16:13 0
WordPress是一种使用PHP语言开发的博客平台,用户可以在支持PHP和MySQL数据库的服务器上架设属于自己的网站。也可以把 WordPress当作一个内容管理系统来使用。
WordPress 在4.7.0版本以及之后版本将REST API插件集成到默认功能之中。REST API为WordPress的使用者提供了一个方便快捷的管理接口。在WordPress 4.7.0版本中,存在着一个越权漏洞,成功的利用这个漏洞,可以绕过管理员权限查看wordpress上所有发布过文章的用户信息列表。
影响版本
● WordPress 4.7.0
漏洞分析
在正式的漏洞分析开始前,先来简单介绍下REST API的使用。官网给出的介绍如下:
具体使用详情请参照REST API Handbook
https://developer.wordpress.org/rest-api/
漏洞利用
先从exploitdb上面提供的poc入手
https://www.exploit-db.com/exploits/41497/
POC看起来比较简单,就是调用了wordpress的rest api接口进行users查询,但是在笔者的环境中,需要对这个poc进行一点小改动,如下图,需要加上一个index.php,否则找不到目录。
接下来看一下代码, 请求首先进入get_items_permissions_check模块进行权限检查
看上面的注释可以大概了解这个函数的功能:当请求users参数时,用来检查请求是否有读的权限,否则爆出WP_Error错误。
这个函数一共有三个if判断,这三个if判断都是由两部分组成的,每一个后半部分都是不可控的current_user_can( ‘list_users’ ),经测试,它的值还为false,所以只能使三个if得可控的前半部分不为真,才能最终绕过判定。
我们var_dump下这三个$request值
有意思的事情发生了,我们什么事情也没做,竟然完美的避开了权限检查的三个判定,接下来进入下个环节get_items函数,检索所有的用户。
上面代码省略了些内容,前半部分省略的是一些相关的参数配置,然后读到注释部分了解到下面参数经过过滤,就要进入WP_User_Query部分了,我们通过给出的链接看一下查询部分的WP_User_Query是怎么解释的。
在跟进WP_User_Query中我们可以看到一个构造方法
在这里可以看到如果$query不为空,则先调用prepare_query,接着再执行$this->query
我们看一下prepare_query是做什么的。
看注释可以了解到,它是准备查询变量的作用,可以看做是对变量的预处理。接下来往后看$this->query;。跟进query函数看看它最终执行了什么。
query函数使用当前变量来执行查询,下面$this->request构造了一个sql查询语句,我们查看下它的值
可以看出这里可以查询的wp_users信息是有约束条件的,这里可以查询的用户必须满足发表过‘publish’类型的文章,并且类型还要是‘post’、‘page’、‘attachment’中的一个
我们分别看一下wp_users表和wp_posts表
wp_posts表
wp_users表
最终通过api返回的用户信息
修补防御
升级wordpress至最新版本。
详情请点击文末“阅读原文”
请点击屏幕右上方“…”
关注绿盟科技公众号
NSFOCUS-weixin
↑↑↑长按二维码,下载绿盟云APP
在WordPress的运行中难免会产生一些错误,我们不希望错误直接阻止了用户对网站的顺畅访问。
这里指的错误并不是 PHP 代码错误。而是在处理一些数据的时候产生的,尤其是处理表单数据,因为不可能所有用户都会完全按照我们的要求提交数据。
比如,在注册用户的时候需要用户填写电子邮件。用户可能会输入不符合电子邮件格式的内容,或者这个电子邮件已经有用户使用,那么就会产生一个错误,导致用户无法顺利注册。
由于错误多种多样,WordPress 使用了一个 WP_Error 类来统一错误的保存方式,有了统一的方式,插件和主题就能更好的读取和添加一些错误,也能简化错误的储存代码。
$errors
用于储存所有的错误。
$error_data
储存错误的额外数据。
这些属性均为类私有(private)。
get_error_codes()
获取所有错误代码。
get_error_code()
获取第一个可用的错误代码,如果没有则返回空。
get_error_messages( $code = ” )
获取一个错误的所有错误消息。
get_error_message( $code = ” )
获取一个错误的第一个错误消息。
get_error_data( $code = ” )
获取错误的额外数据,没有额外数据返回 null.
add( $code, $message, $data = ” )
添加一个错误信息。
add_data( $data, $code = ” )
添加错误的额外数据。
WP_Error( $code = ”, $message = ”, $data = ” ), __construct( $code = ”, $message = ”, $data = ” )
WP_Error 的构造函数,实例化类的时候自动运行,所有的参数都是可选的。可以直接在实例化类的时候添加一个错误。
比如下边这个邮件发送函数,如果发生错误,就会把错误返回出去。任何错误都没产生,而且邮件发送顺利则返回 True.
function tiezhu_send_mail( $email ){ if( empty( $email ) ) return new WP_Error( \'empty_email\', \'邮箱不能为空\' );
if( !is_email( $email ) ) return new WP_Error( \'invalid_email\', \'邮箱格式不正确\' );
if( wp_mail( $email, \'测试邮件\', \'Hello World\' ) ) return true;
return new WP_Error( \'send_error\', \'邮件发送失败\' );
}
下方是一个注册表单的处理函数,里边多次使用了 WP_Error:
function tiezhu_register_user(){ $sanitized_user_login = sanitize_user( $_POST[\'username\'] );
$user_email = apply_filters( \'user_registration_email\', $_POST[\'email\'] );
$register_errors = new WP_Error;
//验证用户名
if( empty( $sanitized_user_login ) ) $register_errors->add( \'empty_username\', __( \'用户名不能为空\', \'Bing\' ) );
elseif( !validate_username( $sanitized_user_login ) ) $register_errors->add( \'invalid_username\', __( \'用户名包含无效字符\', \'Bing\' ) );
elseif( username_exists( $sanitized_user_login ) ) $register_errors->add( \'username_exists\', __( \'用户名已经存在\', \'Bing\' ) );
//验证邮箱
if( empty( $user_email ) ) $register_errors->add( \'empty_email\', __( \'邮箱不能为空\', \'Bing\' ) );
elseif( !is_email( $user_email ) ) $register_errors->add( \'invalid_email\', __( \'邮箱格式不正确\', \'Bing\' ) );
elseif( email_exists( $user_email ) ) $register_errors->add( \'email_exists\', __( \'邮箱已经存在\', \'Bing\' ) );
//注册表单提交事件
do_action( \'register_post\', $sanitized_user_login, $user_email, $register_errors );
//错误过滤器
$register_errors = apply_filters( \'registration_errors\', $register_errors, $sanitized_user_login, $user_email );
//注册用户
if( !$register_errors->get_error_message() ){
$register_new_user = register_new_user( $sanitized_user_login, $user_email );
if( is_wp_error( $register_new_user ) ) return $register_new_user;
return true;
}
return $register_errors;
}
在很多时候我们要判断函数返回的到底是 WP_Error 还是其它内容,因为 WP_Error 是一个类,所以无法通过 True 或 False 直接判断。
WordPress 提供了一个 is_wp_error() 函数,用它我们就能判断变量是否是 WP_Error:
Demand feedback