Wordpress SQL注入漏洞(CVE-2022-21661)分析

产品介绍

WordPress是一个以PHP和MySQL为平台的自由开源的博客软件和内容管理系统。WordPress具有插件架构和模板系统。截至2018年4月,排名前1000万的网站中超过30.6%使用WordPress。WordPress是最受欢迎的网站内容管理系统。全球有大约40%的网站(7亿5000个)都是使用WordPress架设网站的。WordPress是目前因特网上最流行的博客系统。WordPress在最著名的网络发布阶段中脱颖而出。如今,它被使用在超过7000万个站点上。

wordpress的结构

1
2
3
4
5
6
7
8
9
10
├─wp-admin
├─wp-content
│ ├─languages
│ ├─plugins
│ ├─themes
├─wp-includes
├─index.php
├─wp-login.php
├─wp-config.php
├─...
  • wp-admin 为后台管理文件夹
  • wp-content 中包含主题配置文件、插件、上传的文件、升级临时文件,该目录为易受攻击的地方
  • wp-includes 则是一些核心代码,包括前台代码也在这里
  • wp-config.php 这个文件告诉WordPress如何去连接数据库
  • wp-login.php 登陆文件

漏洞简介

通过该文章https://www.zerodayinitiative.com/blog/2022/1/18/cve-2021-21661-exposing-database-info-via-wordpress-sql-injection ,我大概对该漏洞情况进行了一些了解。

通常来说,Wordpress会在你所有展现的网页上运行query(查询)功能,查询不同的属性决定了你所看到页面。因此,如果你正在查看一个静态页面,WordPress的运行查询相关ID来显示页面,而如果归档页面被浏览,查询将检索该归档的所有文章。

该版本的wordpress于WP_Query处存在SQL注入漏洞。WP_Query是wordpress定义的一个类,允许开发者编写自定义查询和使用不同的参数展示文章,并可以直接查询wordpress数据库,在核心框架和插件以及主题中广泛使用。需要注意的是,一般情况下普通的用户输入无法触发该漏洞,该漏洞存在于部分调用了该WP_Query类的wordpress插件中。通过控制该类的输入参数,可以达到SQL注入的目的。

补丁分析

我们在github搜索历史发行版本,在tag中找到5.8.2和5.8.3版本进行commit的比对。

我们可以发现,原来对于terms数组的处理仅仅是将其去重,尔后这些数据会被拼接至SQL查询语句中。该clean_query方法的实际作用是在进行SQL查询之前对输入数据进行一个合法性校验。但在<5.8.3的版本时这个函数并不能很好的做到合法性校验。如果将taxonomy参数置空、同时field参数值等于字符串’term_taxonomy_id’,则使得该clean_query方法无法对terms的合法性进行校验。

通过观察补丁,我们发现,已经无法通过设置field参数值绕过对terms合法性的校验,因为在此处会检查field的值是否满足等于’name’或者’slug’,如果故意设置成这样则无法绕过clean_query方法前面对field的检查。否则,将会对terms用wp_parse_id_list方法进行过滤。这个后面分析

漏洞调试环境搭建

在wordpress官网下载5.8.2的wordpress环境。

电脑已经有现成的wampserver+vscode的调试环境,首先在MYSQL新建wordpress数据库,将wordpress初始化好,链接好数据库。打开wp-config.php,将debug字段设置为true,即可开始单步调试wordpress。

漏洞代码分析

搭建好环境后,首先是尝试利用exp进行调试,查看该clean_query方法的利用链。我首先是修改admin-ajax.php的内容手动加入对于WP_Query类的实例化并控制传入的参数进行调试。

admin-ajax.php

在wordpress当中,当用户的页面、插件或者主题发出ajax请求的时候,这个请求将被路由到admin-ajax.php页面,该页面调用php handle然后再返回一个结果。我们可以显式地访问这个页面(即便是在未授权的情况下),通过指定action参数,尝试去调用wordpress中存在的action所对应绑定的函数。

我们可以看一下ajax对于用户操作的不同处理方式。

可以看到wordpress将action分类成为两种:

  • 需要admin权限的操作
  • 不需要admin权限的操作(访客action)

他们的格式分别是:

  • wp_ajax_{$action}
  • wp_ajax_nopriv_{$action}

对于每次的操作,wordpress通过has_action方法严格检查该action是否存在于wordpress自带的action列表中(包括需要admin权限以及无需admin权限的操作)。因此,用户无法直接指定一个不存在列表中的恶意action,从而一定程度上保证了安全性。

最开始我尝试编写一个最简单的POC去验证漏洞。通常情况下,我认为最简单的方式就是自己注册一个恶意的方法test,在该方法中直接将request的参数作为输入实例化WP_Query函数。在wordpress中,可以通过 add_action()方法将函数hook到指定的action上,使得admin-ajax.php页面收到该action请求后可以调用指定的方法。

这是先知社区一个师傅给出的验证方法,在admin-ajax.php中添加该代码。

这种方式很容易就打通了,这里我就不过多赘述了。

武器化利用

插件安装

原以为武器化利用是最容易的一部,但凡打通了之后后面对于插件的攻击应该是手到擒来,事实上发现我想错了。整一个过程经历了诸多麻烦。

根据zerodayinitiative论坛的说法,他们发现目前存在可以触发该漏洞的最流行的插件叫Elementor custom skin。该插件是wordpress插件elementor的附属插件,作用是自定义一部分页面的样式。

因此,我打算从该插件入手,尝试写一个针对于该插件的漏洞利用脚本。

最开始我直接在wordpress的应用商店中安装了该插件,然而发现该插件报错提示说需要安装elementor和elementor pro才能运行。于是我又安装了elementor插件,但仍旧提示相同的错误。经过查询我才发现,elementor pro实际上是elementor的附属插件,在购买了pro之后仍旧不能卸载elementor插件因为其包含了pro所需要的主体功能,而Elementor custom skin又是需要pro才能够启用的功能。

于是我去第三方平台下载了elementor和elementor pro的安装包,在wordpress进行安装,现在发现Elementor custom skin的功能终于能够启用了。

到此插件算是安装完成。接着我们开始调试。

漏洞复现

安装好插件之后我直接尝试用原先的payload去打。然而失败了,每次打过去页面都只显示一个0。

调试的过程当中,我观察了插件的构造函数,研究了一下wordpress插件中action的载入机制。

一开始我以为每次安装插件的时候会将插件中需要hook的action直接写入到本地保存action的文件,后来发现并不是,除了wordpress核心组件中自带的action,所有插件中的action都是动态载入的。wordpress在打开页面的时候,会有一个plugins loading的环节,动态调用所有插件的构造函数,同时将对应action hook到指定的方法。

经过一番调试我发现了问题所在:我们首先来看一下进入到amin-ajax.php页面中执行的操作。

在171行action变量首先获取到用户url请求中的action参数,分别将其拼接到未登录以及登录状态下的action名称中,然后调用has_action()方法来检查wordpress中是否有该action所对应的函数,如果没有就返回0。那么现在显然是最开始第一步wordpress的插件载入过程中就没有将操作hook到该action上面,于是我们单步调试查看原因。

我们首先查看把add_action()方法的位置:

在init_ajax()方法中,通过add_action将ecsload action hook到了get_document_data方法上,后者就是存在new WP_Query触发SQL注入漏洞的地方。peek一下发现在构造函数的末尾,调用了init方法。

我们进入到该插件的构造函数中,发现16行倘若没有设置POST参数ecs_ajax_settings的话就会直接return不会执行后面的init_ajax()操作。而在之前的调试过程中就是从这里就直接return了。因此我们设置下ecs_ajax_settings为1,就可以到下一步了。

至此我可以判断插件的载入已经没有问题了,可以进行下一步尝试去打通。

首先我们进入到get_docuement_data()方法:

找到实例化对象的地方:

进入到get_post方法:

进入到get_sql方法:

进入到get_sql_clauses方法:

进入get_sql_for_query方法:

进入clean_query方法:

在这里就是绕过合法性检测的地/方:

最终完成注入。

Exploit

以下贴出一个脚本可供使用(dnslog的有bug,奈何一直很忙懒得改了):

https://github.com/QWERTYisme/CVE-2022-21661

运行结果如下,关闭debug模式下可以以时间盲注的方式注表的字段。

参考资料

https://zh.wikipedia.org/wiki/WordPress

https://juejin.cn/post/6844903506520850446

https://developer.wordpress.org/

https://xz.aliyun.com/t/10841

https://stackoverflow.com/questions/27849312/wordpress-pagination-wp-query-max-num-pages-return-0

https://www.wpdaxue.com/mastering-wp_query-an-introduction.html