长亭百川云 - 文章详情

Wordpress WP_Query类SQL注入漏洞

毕方安全实验室

60

2024-07-13

1.前言

Wordpress是全世界最流行的cms系统,在全球建站系统市场占有量超过四成,在如此大的网站基数下,一个有条件的高危漏洞可能也会影响众多站点。近日,Wordpress官方发布了安全通告,由于不恰当的处理,使用了WP_Query类的插件或者主题可能存在SQL注入漏洞,漏洞编号CVE-2022-21661。WP_Query是Wordpress用于处理复杂请求的一个数据库查询类,在核心框架和多种插件、主题中都有应用,只不过核心框架中的使用不满足漏洞利用条件。

2.漏洞成因

我们从最开始的WP_Query类开始看,定位到文件/wp-includes/class-wp-query.php,在其构造函数__construct()中调用了query()方法,参数为$query。由于中间函数调用的比较繁琐,并且不涉及到具体的利用条件,我们利用如下的的流程来简单说明:

`WP_Query::__construct()->`    `WP_Query::query()-> //设置了类属性query_vars的值,并调用了get_posts()`        `WP_Query::get_posts()-> //当查询的不是针对现有的某个帖子(类型可以是post、page、attachment)时,$this->is_singular为false,会调用到get_sql()方法。`            `WP_Query::get_sql()->`                `WP_Query::get_sql_clauses()->`                    `WP_Query::get_sql_for_query()->`                        `WP_Query::get_sql_for_clause()->`                            `WP_Query::clean_query() //满足特定条件时,未对terms参数做过滤`

在get_sql_for_clause中调用了clean_query()方法来校验查询中的参数值,当满足 $query['field'] == 'term_taxonomy_id' 时,会调用transform_query()方法。

跟进到transform_query()方法,同样的由于$query['field'] == 'term_taxonomy_id'条件成立,并且$resulting_field被赋值为term_taxonomy_id,因此599行条件成立会直接返回空值。

clean_query()方法因此就没有起到校验参数值的作用。返回到get_sql_for_clause()方法,可以看到$clause['terms']值在用逗号连接后,直接拼接到IN语句中,最终导致了SQL注入漏洞的产生。

3.插件复现

在ZDI(Zero Day Initiative)的博客中,以Elementor Custom Skin插件为例进行了分析,我们也以此插件为例来详细介绍Wordpress加载插件的流程以及如何构造对应的payload。复现环境如下:

`Wordpress 5.8.0``Ele Custom Skin 3.1.4`

安装插件并启用后,我们来分析该插件的漏洞触发点/wp-content/plugins/ele-custom-skin/includes/ajax-pagination.php,在get_document_data()方法创建了WP_Query对象。

$this->query属性在构造函数__construct()中进行了初始化,$_POST['query']在json解码后赋值给了$this->query,数据是可控的。因此,get_document_data()满足了SQL注入触发的两个条件。

那么该如何调用get_document_data方法呢?通过搜索发现,init_ajax()方法将get_document_data()注册为action分别为wp_ajax_ecsload、wp_ajax_nopriv_ecsload。

这里就不得不提到wordpress的两个重要方法add_action()、do_action()。

  • add_action

add_action 可以将我们自定义的函数加到特定的 Hook 上去,等待执行。一般来说,我们只需要执行如下命令即可。

add_action("Hook名","函数名")
  • do_action

do_action 是 WordPress 插件机制非常重要的一环,当程序运行到这个函数时,就会将挂载在这个 Hook 上的所有函数执行一遍。这个函数有两个参数,第一个参数是 Hook 的名称,第二个参数则是具体的参数。

do_action("Hook名", "参数")

因此要触发,只需要找到一个入口文件,既可以加载插件,又可以调用特定的action。通过查询资料和代码搜索,我们发现了wp-admin/admin-ajax.php文件。在文件开始,加载了wp-load.php文件。

通过查询资料,我们发现插件加载的流程如下,在wp-settings.php中会加载active状态的插件。

`index.php`    `->wp-blog-header.php`        `->wp-load.php`            `->wp-config.php`                `->wp-settings.php``   ``// wp-setting.php``// Load active plugins.``foreach ( wp_get_active_and_valid_plugins() as $plugin ) {`  `wp_register_plugin_realpath( $plugin );`  `include_once $plugin;``   `  `/**`   `* Fires once a single activated plugin has loaded.`   `*`   `* @since 5.1.0`   `*`   `* @param string $plugin Full path to the plugin's main file.`   `*/`  `do_action( 'plugin_loaded', $plugin );``}``unset( $plugin );`

因此,admin-ajax.php满足了插件加载的条件,随后获取action参数后会检查当前用户有没有登录,当用户登录并且有action调用权限时,会调用wp_ajax_前缀的action;而当用户没有登录时,则会调用wp_ajax_nopriv_前缀的action。

很幸运的是,我们前面提到ajax-pagination.php注册了两个action分别为wp_ajax_ecsload、wp_ajax_nopriv_ecsload,因此在未登录的状态下仍然可以触发SQL注入漏洞。在wp-config.php中将WP_DEBUG置为true方便查看报错,构造如下的payload触发报错注入。

4.修复建议

Wordpress官方已经在5.8.3的代码提交中修复了这个问题(https://github.com/WordPress/WordPress/commit/271b1f60cd3e46548bd8aeb198eb8a923b9b3827),建议用户及时更新。

wp_parse_id_list()方法会对数组的每个元素调用absint()方法转换成非负的int类型,杜绝了SQL注入漏洞的可能。

参考:

https://github.com/WordPress/WordPress/commit/271b1f60cd3e46548bd8aeb198eb8a923b9b3827

https://www.zerodayinitiative.com/blog/2022/1/18/cve-2021-21661-exposing-database-info-via-wordpress-sql-injection

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2