WordPress SQL injection vulnerability and privilege escalation Analysis

Source: Internet
Author: User
Tags wordpress sql injection

WordPress SQL injection vulnerability and privilege escalation Analysis

 

Threat Response Center researcher analyzed Wordpress core features SQL Injection Vulnerability (numbered CVE-2015-5623 and CVE-2015-2213) in detail 0x00 vulnerability overview on twitter saw Wordpress core features SQL injection vulnerability, I want to learn more, and I went deep into the code, and found that there was a big hole in the Old World. Although the injection problem exists, it does not trigger the SQL injection vulnerability through low permissions such as the subscriber as described in his blog. This Wordpress vulnerability series is currently updated in two sections. One is to write an article to the recycle bin by bypassing the subscriber permission, and the other is to write this article to implement the SQL injection vulnerability. The descriptions of these two vulnerabilities are clear in the text written by phithon of TSRC, from the perspective of my analysis, I will introduce the formation and exploitation of these two vulnerabilities and the original content omitted by phithon. 0x01 unauthorized submission Article _ wpnonce acquisition before talking about the excessive permission vulnerability, we need to introduce the _ wpnonce parameter in the Wordpress background. This parameter is mainly used to prevent the token of CSRF attacks. Most of the background sensitive functions generate tokens based on the current user information, function name, and operation object id. Therefore, it is difficult for us to use some functions without token. This CSRF protection mechanism indirectly causes subsequent SQL injection to be triggered when it is difficult to have lower permissions (because the token is not visible). When we talk about the SQL injection vulnerability later, we will discuss this issue in detail. The reason for getting _ wpnonce is as follows: we need a token that allows us to edit and submit an article. The token of this function can be accessed by accessing the background post. php's post-quickdraft-save function can be obtained. Strictly speaking, this method is also an information leakage vulnerability, and the official version has been fixed. Next, let's take a look at the cause of the token leak. Some code is as follows:
case 'post-quickdraft-save':    // Check nonce and capabilities    $nonce = $_REQUEST['_wpnonce'];    $error_msg = false;    // For output of the quickdraft dashboard widget    require_once ABSPATH . 'wp-admin/includes/dashboard.php';    if ( ! wp_verify_nonce( $nonce, 'add-post' ) )        $error_msg = __( 'Unable to submit this form, please refresh and try again.' );    if ( ! current_user_can( 'edit_posts' ) )        $error_msg = __( 'Oops, you don’t have access to add new drafts.' );    if ( $error_msg )        return wp_dashboard_quick_press( $error_msg );

 

From the code above, we can see that when an error occurs in this function, the error message will be printed to the page through the wp_dashboard_quick_press function, and the Code on the page generated by this function contains the following line: PHP wp_nonce_field ('add-post'); 1 wp_nonce_field ('add-post'); generate the add-post function's _ wpnonce, that is, even if we do some operations that are forbidden, this _ wpnonce will also appear on the returned page. Unauthorized submission and conditional competition, as phithon says, verification bypass due to logic disorder in GP usage. Let's take a look at the code of the post. php file to check whether the article exists:
if ( isset( $_GET['post'] ) )    $post_id = $post_ID = (int) $_GET['post'];elseif ( isset( $_POST['post_ID'] ) )    $post_id = $post_ID = (int) $_POST['post_ID'];else    $post_id = $post_ID = 0;global $post_type, $post_type_object, $post;if ( $post_id )    $post = get_post( $post_id );if ( $post ) {    $post_type = $post->post_type;    $post_type_object = get_post_type_object( $post_type );}

 

We can see that the post parameter in GET is extracted as the Article id to extract the article information. If there is no GET post parameter, we can use the POST post_ID parameter to extract it. To check whether the user has the edit permission on the article, it is done in edit_post, and the judgment parameter here is extracted from POST:
function edit_post( $post_data = null ) {    global $wpdb;    if ( empty($post_data) )        $post_data = &$_POST;    // Clear out any data in internal vars.    unset( $post_data['filter'] );    $post_ID = (int) $post_data['post_ID'];    $post = get_post( $post_ID );    $post_data['post_type'] = $post->post_type;    $post_data['post_mime_type'] = $post->post_mime_type;    if ( ! empty( $post_data['post_status'] ) ) {        $post_data['post_status'] = sanitize_key( $post_data['post_status'] );        if ( 'inherit' == $post_data['post_status'] ) {            unset( $post_data['post_status'] );        }    }    $ptype = get_post_type_object($post_data['post_type']);    if ( !current_user_can( 'edit_post', $post_ID ) ) {        if ( 'page' == $post_data['post_type'] )            wp_die( __('You are not allowed to edit this page.' ));        else            wp_die( __('You are not allowed to edit this post.' ));    }

 

Let's continue to look at the final if judgment of this Code to determine whether the current user has the permission to edit this article. The final operation of this judgment is performed in the map_meta_cap function:
case 'edit_post':    case 'edit_page':        $post = get_post( $args[0] );        if ( empty( $post ) )            break;        if ( 'revision' == $post->post_type ) {            $post = get_post( $post->post_parent );        }

 

It can be seen that if the article does not exist, the switch will be break, and the $ caps variable will be returned at the end of the function, $ caps has been defined as an empty array at the beginning of the function. That is to say, an empty array is returned here. Next let's move forward and return to the has_cap function that calls map_meta_cap to see the subsequent operations.
public function has_cap( $cap ) {        if ( is_numeric( $cap ) ) {            _deprecated_argument( __FUNCTION__, '2.0', __('Usage of user levels by plugins and themes is deprecated. Use roles and capabilities instead.') );            $cap = $this->translate_level_to_cap( $cap );        }        $args = array_slice( func_get_args(), 1 );        $args = array_merge( array( $cap, $this->ID ), $args );        $caps = call_user_func_array( 'map_meta_cap', $args );        // Multisite super admin has all caps by definition, Unless specifically denied.        if ( is_multisite() && is_super_admin( $this->ID ) ) {            if ( in_array('do_not_allow', $caps) )                return false;            return true;        }        $capabilities = apply_filters( 'user_has_cap', $this->allcaps, $caps, $args, $this );        $capabilities['exist'] = true; // Everyone is allowed to exist        foreach ( (array) $caps as $cap ) {            if ( empty( $capabilities[ $cap ] ) )                return false;        }        return true;    }

 

Check the last foreach. It checks whether each element in $ caps does not exist in $ capabilities. If yes, return false, but $ caps is an empty array, in this way, we can easily get a true value, and the permission verification is successfully bypassed. In this way, we can obtain a message that we can use this defect to update an article that does not exist. Now the problem arises. It makes no sense to update an article that does not exist directly, because an error is certainly returned when the database executes SQL statements. How can we create an article successfully? After the permission is verified, there is a code in post. php before the database executes the operation:
if ( isset( $post_data['tax_input'] ) ) {        foreach ( (array) $post_data['tax_input'] as $taxonomy => $terms ) {            // Hierarchical taxonomy data is already sent as term IDs, so no conversion is necessary.            if ( is_taxonomy_hierarchical( $taxonomy ) ) {                continue;            }            if ( ! is_array( $terms ) ) {                $comma = _x( ',', 'tag delimiter' );                if ( ',' !== $comma ) {                    $terms = str_replace( $comma, ',', $terms );                }                $terms = explode( ',', trim( $terms, " \n\t\r\x0B," ) );            }            $clean_terms = array();            foreach ( $terms as $term ) {                // Empty terms are invalid input.                if ( empty( $term ) ) {                    continue;                }                $_term = get_terms( $taxonomy, array(                    'name' => $term,                    'fields' => 'ids',                    'hide_empty' => false,                ) );

 

If an array parameter named tax_input exists in the POST data, use a comma to cut the values in the parameter, and then perform a select query for each split content. Here we can imagine that, if we fill in the value of ID + 1 for the latest article in post_ID, and add a lot of content in the tax_input parameter, as a result, it does not stop querying by the select statement. Are we inserting an article during this stagnant period (the ID of this article is the latest article ID + 1 ), is the subsequent update meaningful? Next, how can we insert an article? Do you still remember the post-quickdraft-save function? Its function is to save a draft quickly! Here is a shameful picture in the phithon article to let everyone deepen the process of conditional competition: In the debugging process, the most touching thing is conditional competition, the time difference between inserting an article and updating should be well grasped. If it is found too early, it cannot pass the permission test. If it is too late, it makes no sense. In my experience, ** the process of inserting an article should be as long as possible, and the content in tax_input should be as much as possible, so that the article can be inserted more stably after verification and before update. Of course, each user can save only one draft. The suggestion provided by the Vulnerability discoverer is to wait for a week, and the draft will be automatically deleted, while _ wpnonce will be retained for one day. Phithon is recommended to be better. Use two accounts, one account to insert an article, and the other account to update. An unauthorized vulnerability is composed of three small vulnerabilities, namely information leakage, Permission Bypass caused by logical vulnerabilities, and procedural execution time control. 0x02 The revision trick vulnerability discoverer's article first mentions a revision trick, which is used to undertake further attacks after unauthorized writing articles. However, due to the hidden skills of this foreigner, the SQL injection vulnerability triggered by the subscriber account cannot be reproduced according to the content mentioned in his article. Therefore, phithon did not mention this trick in the article. Here I will introduce the principles of this trick for the purpose of restoring the author's ideas. The revisions field is used to record the draft or update release, the revisions field in Wordpress sets post_type to revision in the posts database table for submission and storage. Each revision has a 'Post _ parent' field, specifies the original submission based on this revision. When you try to edit a revision, it uses post_parent for verification instead of revision itself. In this way, if we create a revision based on a post, we can set its status to any State other than "trash, even if the original post state is "trash" using this trick, We can edit this "puppet revision ?) And freely add comments to him, even if his original post has been discarded into the garbage bin. Based on the above overauthorization Write Vulnerability, we can see that the author proposed to use this trick to use the revision of the post version we previously written into the garbage bin) to edit and manipulate comments. Because posts of the type are post, even those submitted by the current subscriber do not have the permission to edit, But revision can be edited. Therefore, as mentioned in the original article, we can use this trick to continue the subsequent operations. Vulnerability cause: this vulnerability is actually a second injection vulnerability. The essence of this vulnerability is that when you restore an article from the recycle bin, you must also review the following comments, however, the Code for restoring comments directly concatenates user controllable content, resulting in the SQL injection vulnerability. The following is the problem code:
function wp_untrash_post_comments( $post = null ) {    global $wpdb;    $post = get_post($post);    if ( empty($post) )        return;    $post_id = $post->ID;    $statuses = get_post_meta($post_id, '_wp_trash_meta_comments_status', true);    if ( empty($statuses) )        return true;    do_action( 'untrash_post_comments', $post_id );    // Restore each comment to its original status.    $group_by_status = array();    foreach ( $statuses as $comment_id => $comment_status )        $group_by_status[$comment_status][] = $comment_id;    foreach ( $group_by_status as $status => $comments ) {        // Sanity check. This shouldn't happen.        if ( 'post-trashed' == $status )            $status = '0';        $comments_in = implode( "', '", $comments );        $wpdb->query( "UPDATE $wpdb->comments SET comment_approved = '$status'         WHERE comment_ID IN ('" . $comments_in . "')" );    }    clean_comment_cache( array_keys($statuses) );    delete_post_meta($post_id, '_wp_trash_meta_comments_status');    do_action( 'untrashed_post_comments', $post_id );}

 

From the code, we can see that the content of the $ status and $ comments variables will be spliced into SQL, and the content of these two variables can be controlled by the user. Because the data imported by the user will be filtered and then stored in the database, and then the data is directly extracted from the database for splicing, therefore, if our attack statements are extracted from the database, the original appearance will be restored-standard secondary injection. The exploitation of vulnerabilities and the reasons for these vulnerabilities are easy: comment on the revision of the written article. The comment state is "inject attack statement". Drop revision into the garbage bin to restore the original revision article. It seems that there is no problem, I don't know whether you remember the _ wpnonce we mentioned above. Yes! This is the pitfall! The author does not mention how to obtain the _ wpnonce vulnerability! This is the most direct result in no way to play o in steps 1st, 2, and 4. I guess the author did not mention this problem, either he did not find it himself. This article is actually just a title party or he has hidden his hand. From the close relationship between the author and the vulnerabilities, I prefer the latter reason, so I have to give him a look at it ~~ Summary the idea of using the blind spots without filtering to read the stored content in the database for secondary injection is direct, but the revision is used to edit the article in the garbage bin. During the analysis, it is found that the revision editing is not any type other than trash as mentioned by the author, the following statuses are available: publish, future, private, inherit, auto-draft, attachment, draft, pedding, and trash, the types of articles we can modify can only be inherit, pedding, and draft. Otherwise, if I can modify the document status to private, perform Step 1 and Step 2 on the foreground. I 've been following this vulnerability on and off for a week. The biggest feeling is that the author is really a pitfall) o 0x03 summary the most profound experience with this vulnerability is that the Wordpress token mechanism protects csrf and also helps defend against other types of attacks, a good mechanism. The logic of GP operations is a problem with Web code, especially when performing some permission verification, it is easy to cause logic confusion caused by GP crossover like Wordpress. Secondary injection is a common problem. If you trust the recorded data too much, you will forget this record.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.