###### 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/