---
# System prepended metadata

title: '[CVE-2022-21661] WordPress Core WP_Query SQL Injection'
tags: [CVE, Research]

---

###### tags: `Research` `CVE`

# [CVE-2022-21661] WordPress Core WP_Query SQL Injection  
  
    
## How to deloy local ?    
  
- Cài đặt xampp trên local  
- Download source code wordpress release 5.8.2
- Giải nén và chép toàn bộ source code vào `htdocs` của xampp
- Mở dịch vụ apache và mysql
- Cung cấp thông tin và cài đặt wordpress


## Bug  
  
Compare 2 version `5.8.3` và `5.8.2` của wordpress, ta thấy   
  
![image](https://user-images.githubusercontent.com/44127534/149652846-445c65cf-d2d5-41fd-bae1-24e0ff7f5afb.png)  
  
file `wp-includes/class-wp-tax-query.php` có sửa một chút ở dòng 559 cụ thể hơn là ở hàm `clean_query()`, theo như mô tả thì lỗ hổng xảy ra ở biến `$query['terms']` không được filter kỹ càng và đưa trực tiếp vào câu lệnh sql => dẫn đến lỗ hổng sql injection.    
  
Hơn nữa `clean_query()` được gọi ở hàm `get_sql_for_clause()` nằm trong file `wp-includes/class-wp-tax-query.php`, và hàm `get_sql_for_clause()` này sẽ trả về câu lệnh sql nên rất có thể đây là sink của lỗ hổng này.  
  
```php
// wp-includes/class-wp-tax-query.php  
... snippet ..   

public function get_sql_for_clause( &$clause, $parent_query ) {
		global $wpdb;
		echo "[+] get_sql_for_clause called\n";
		$sql = array(
			'where' => array(),
			'join'  => array(),
		);

		$join  = '';
		$where = '';

		$this->clean_query( $clause ); // Bug here
    ... snippet .. 
      
    return $sql;
?>
```  
nhưng theo mô tả thì  
  
![image](https://user-images.githubusercontent.com/44127534/149652956-bb024a5e-e72c-4932-ac57-12df199638e4.png)  
  
lỗ hổng này tồn tại ở `WP_Query` class trong khi bug ở class `WP_Tax_Query`, rất có thể là 2 class này chain nhau từ đó dẫn đến lỗ hổng trên.   
  
## Analysis  
  
### Chain     
  
Ta chỉ cần dùng chức năng `Find Usage` của `PHP Storm` để trace code và tìm chain  
  
```  
WP_Query:_contruct()
  WP_Query:query()
    WP_Query:get_posts()
      WP_Tax_query:get_sql()
        WP_Tax_query:get_sql_clauses()
          WP_Tax_query:get_sql_for_query()
            WP_Tax_query:get_sql_for_clause()
```  
  
Từ `WP_Query:_contruct()` nói chính xác là hàm khởi tạo của `WP_Query`, nếu ta có thể control được giá trị của các property của `WP_Query` thì từ đó theo flow code sẽ trigger được lỗ hổng.  
  
### Idea  
  
Vì lỗ hổng này tồn tại trong một số theme và plugin, nên cách của mình ở đây sẽ là upload một plugin chứa function dùng để tạo một instance của `WP_Query` với các trường data được control thông qua `POST` method, sau đó bind nó vào một action để tiện cho việc gọi.  
  
### Entry point  
  
Vì khi chạy wordpress sẽ load hết các plugin đã được active, nên vì thế ta cần tìm nơi để có thể gọi action này, có một endpoint `/wp-admin/admin-ajax.php` có chức năng như một API để phục vụ cho việc truy vấn hay lấy dữ liệu thông qua biến `action` mà ta có thể custom để gọi `action` mà ta muốn.  
  
![image](https://user-images.githubusercontent.com/44127534/149653259-895cea1f-3a99-416b-8961-75b6d505ade5.png)  
    
Tạo một plugin như sau:  
  
```php  
<?php
// Exploit.php
/*
Plugin Name: CVE-2022-21661 Exploit
Description: This plugin was made in order to test ( CVE-2022-21661 )
Version: 1
Author: nhienit
*/

function Exploit() {
	$args = $_POST['payload'];

	$malicous_wp_query = new WP_Query($args);

	return $malicous_wp_query;
}

// User logged require!
add_action("wp_ajax_exploit", "Exploit");
?>
```  
  
Zip file `Exploit.php`    
```bash
zip exploit.php Exploit.php
```  
  
Và upload plugin  
  
![image](https://user-images.githubusercontent.com/44127534/149653324-ee3451fd-5279-4d90-990a-40185a5becfe.png)  
  
Sau khi upload nó sẽ nằm ở `wp-content/`  
  
![image](https://user-images.githubusercontent.com/44127534/149653344-05d1fad8-7d98-4f74-b597-0e2311abca4b.png)
  
Ở đây, mình đã thêm một action cho plugin đó là `wp_ajax_exploit` để gọi hàm `Exploit`, vì sao lại đặt như thế?  
Sơ qua một chút ở file `wp-admin/admin-ajax.php`  
  
![image](https://user-images.githubusercontent.com/44127534/149653416-feca7ef8-01d7-426f-92bb-979b4d591e53.png)
    
Action có 2 loại là `wp_ajax_nopriv` và `wp_ajax`, một cái không yêu cầu authen và một cái yêu cầu authen, format sẽ có dạng như `wp_ajax_<action_name>`.  
`action_name` này được lấy thông qua `url_query` hoặc `body`  
  
![image](https://user-images.githubusercontent.com/44127534/149653492-e35a6fbb-d769-49cc-bd40-acc2469994cd.png)
  
Để gọi thì ta chỉ cần gọi `action=exploit` để trigger function `Exploit`  
  
![image](https://user-images.githubusercontent.com/44127534/149653538-1e4e8bca-8c37-4cb0-8ba7-943a23757201.png)
    
### Exploit  
  
Ở hàm `WP_Query::get_posts()` sẽ gọi đến hàm `$this->parse_tax_query()`
  
![image](https://user-images.githubusercontent.com/44127534/149653913-7a9f3891-ea8b-491a-89b6-ecba6ef90337.png)
  
hàm này chủ yếu sẽ tạo ra một instace của hàm `WP_Tax_Query` và gán vào `$this->tax_query` trong đó hàm constructor của `WP_Tax_Query` sẽ nhận vào là giá trị `query_vars` của class `WP_Query` (mà ta có thể control được thông qua $\_POST['payload']), các đối số này sẽ được parse và làm argument để tạo `WP_Tax_Query` instance.  
  
![image](https://user-images.githubusercontent.com/44127534/149653992-06866216-9509-4822-ac21-752723c296e3.png)
  
```php
// WP_Query::parse_tax_query()  

public function parse_tax_query( &$q ) {
		if ( ! empty( $q['tax_query'] ) && is_array( $q['tax_query'] ) ) {
			$tax_query = $q['tax_query'];
		} else {
			$tax_query = array();
		}

		if ( ! empty( $q['taxonomy'] ) && ! empty( $q['term'] ) ) {
			$tax_query[] = array(
				'taxonomy' => $q['taxonomy'],
				'terms'    => array( $q['term'] ),
				'field'    => 'slug',
			);
		}

	... snippet ...
		
    $this->tax_query = new WP_Tax_Query( $tax_query );
    
		do_action( 'parse_tax_query', $this );
	}
```  
có thể thấy `$q` phải là một mảng chứa key là `tax_query` và `$q['tax_query']` phải là một array => argument của WP_Tax_Query::constructor() là một array  
  
```php
class WP_Tax_Query {
  public $queries = array();
  ... 
     
  public function __construct( $tax_query ) {

		if ( isset( $tax_query['relation'] ) ) {
			$this->relation = $this->sanitize_relation( $tax_query['relation'] );
		} else {
			$this->relation = 'AND';
		}

		$this->queries = $this->sanitize_query( $tax_query );
	}  
  ...snippet...
}  
```
ở contructor của `WP_Tax_Query`, có thuộc tính `queries` và đây sẽ là thứ sẽ đưa vào câu query, vì thế mục đích là ta sẽ chèn payload vào biến này. Như đã thấy thì `$WP_Tax_Query->queries` là một mảng, tuy nhiên giá trị của `queries` sẽ là giá trị trả về của hàm `sanitize_query`, mục đích để filter `$tax_query`.  
  
![image](https://user-images.githubusercontent.com/44127534/149654207-edfc2054-c760-434d-bf31-3e62f6d9e63e.png)
  
hàm `sanitize_query()` sẽ trả về giá trị của `$cleaned_query`, chổ này sẽ loop mảng `$queries` nhưng để đảm bảo `terms` không thay đổi thì ta cần phải pass điều kiện `self::is_first_order_clause( $query )`.  
Để pass thì những `query` con của `queries` chỉ cần thỏa một trong các điều kiện sau:  
  
![image](https://user-images.githubusercontent.com/44127534/149654355-9b1d6160-2c00-466c-80eb-d72031bc2f78.png)
  
Payload như sau:    
```
action=exploit&
_wpnonce=ce659e8ba6&
payload[tax_query][nhienit][operator]=IN&
payload[tax_query][nhienit][terms]=<inject>&
payload[tax_query][nhienit][field]=term_taxonomy_id
```  
  
nhưng vì sao `field` phải bằng `term_taxonomy_id`, vì ở hàm `WP_Tax_Query::clean_query()`  
  
![image](https://user-images.githubusercontent.com/44127534/149654454-640647ee-f2fb-4bea-b328-c53a2163f3e4.png)
  
nếu `field != "term_taxonomy_id"` sẽ throw error do `taxonomy` chúng ta không post biến đó lên hoặc cũng có thể pass điều kiện `! taxonomy_exists( $query['taxonomy'] )` này, nhưng giá trị của `taxonomy` khó xác định được. Pass được câu if đó thì biến `$query['terms']` sẽ được bảo toàn và thực hiện `transform_query()`.  
  
Xong sẽ trở về hàm `get_sql_for_clause()` để thực hiện construct sql query, vì operator mình post lên là `IN` nên sẽ nhảy vào nhánh này  
  
![image](https://user-images.githubusercontent.com/44127534/149654601-cd03b1e0-80bf-49ec-bf8c-8c7bcbd5e236.png)
  
ta thấy `$query['terms']` được sử dụng mà không thực hiện cơ chế filter nào, dẫn đến lỗ hổng SQL injection.  
  
![image](https://user-images.githubusercontent.com/44127534/149654635-36017149-9dde-4d77-a87d-b8303ec69f06.png)
  
## PoC  
  
![image](https://user-images.githubusercontent.com/44127534/149654649-a27dc40f-9e90-4c70-a260-b9588f7d0bbf.png)
  
do mình đã bật debug nên ta có thể thấy được lỗi. Nếu trang web bật chức năng debug ta có thể dễ dàng khai thác lỗi `error-based`  
  
![image](https://user-images.githubusercontent.com/44127534/149654719-04f15c00-518f-4be8-9960-b660ac4d1854.png)
  
  
## References:  
- https://www.buaq.net/go-99941.html  
- https://confidentialteam.github.io/posts/cve-202221661ar/  
- https://www.zerodayinitiative.com/advisories/ZDI-22-020/
