从数据库说偏移注入原理
场景
在常见的SQL注入当中,一般是将数据库中的表名、列名猜解出来之后,然后通过查询的方式获取具体的数据值。如SqlMap就是采用这样的注入流程进行数据获取的。但是这样的方式存在一个问题就是当知道数据表的名字,但是列名很长,或者数据库系统比较简单,不能通过查询系统数据库中的信息获取到用户表中的表名时就很难获取到想要的数据信息,如access数据库。(比如其中SQL Server 和MySQL都可以通过查询系统数据库获取到具体的列名)
原理
简单来说偏移注入的原理就是通过连接查询方式将想要获取到的数据携带出来。其中SQL中的连接查询包含一下几种方式:
- INNER JOIN: 如果表中有至少一个匹配,则返回行
- LEFT JOIN: 即使右表中没有匹配,也从左表返回所有的行
- RIGHT JOIN: 即使左表中没有匹配,也从右表返回所有的行
- FULL JOIN: 只要其中一个表中存在匹配,就返回行
几种连接查询方式详细使用说明
原理详情
在注入中其实是在一个表中查询数据,所以以上几种查询方式在注入中查询到的结果都是一样的,所以在这里选择INNER JOIN(和JOIN一样)作为样例。其中用到的数据库表users如下:
用到的userinfo表如下:
首先在数据表userinfo中做一个简单的查询如下:
现在假设在id处存在一个注入,而且我们通过这个注入点获取到了users表名,但是由于表名过长,也没有系统表可以查询出users表的字段名。所以,此时我们可以采用偏移注入的方式,在无需字段名的情况**出数据。
首先由于是连接查询,所以我们首先需要知道当前存在注入点的这个表中存在几个字段,这个地方可以采用order by的方式探测出存在几个字段,order by 8结果如下:
修改为9然后出错:
所以可以知道当前存在注入的SQL语句返回的是8个字段,同时我们知道我们想要获取的数据在表users中,所以就可以采用如下的方式进行联合注入:
但是由于我们不知道字段名,所以无法直接通过联合注入的方式直接查询出数据,此时我们可以将一部分数字换为*,然后不断尝试,找出users表中的字段数,如下:
不再报错,所以可以判断出来users表中的列数为3列,同时也带出了users表中的数据。所以这样的方式可以在无需知道列名的情况下直接获取到数据。虽然现在数据注出来了,但是存在一个问题就是,如果是在现实的系统当中,那么这些字段不一定输出出来。现在假设在前端输出来的只有infouser表中的两个字段,city和from显示样式如下表格。那么在前端输出的就只有以下几组数据:
| 现住址 | 来自于 |
|---|---|
| 北京 | 湖南 |
| 4 | 6 |
| 4 | 6 |
其中并没有我们期望的数据username和password。所以此时我们可以移动*号的位置来尝试将输出的字段进行移动,让其与city和from字段对齐这样就可以将username和password带到前端,如下:
那么此时在前端输出的结果将如下:
| 现住址 | 来自于 |
|---|---|
| 北京 | 湖南 |
| admin | 6 |
| admin123 | 6 |
这样的话就可以将我们期望的数据输出到前端,同样在偏移一位就可以将password字段同样输出到前端,如下:
那么此时的前端数据应该如下:
| 现住址 | 来自于 |
|---|---|
| 北京 | 湖南 |
| 1 | 1234567890 |
| 2 | 123456 |
这其实就是偏移注入的具体原理,不断调整*号的位置尝试将想要的数据字段输出出来。为了不用一位一位的尝试偏移,我们所以就用到了SQL查询里边的一些语句,也就是INNER JOIN,它的作用就是将两个表连接起来的作用。效果如下:
然后就可以将上面UNION后面的用我们的INNER JOIN替换掉,一个INNER JOIN会出现两个重复的返回值,这样能提高能将我们想要的数据输出的前端的概率,如下:
在前端显示的结果如下:
| 现住址 | 来自于 |
|---|---|
| 北京 | 湖南 |
| 1 | 1234567890 |
| 1 | 1234567890 |
| 2 | 123456 |
| 2 | 123456 |
可以看到我们password字段已经能够输出到前端了。但是内容是一个笛卡尔积的形式,此时我们可以添加条件,如下:
在前端显示的结果如下:
| 现住址 | 来自于 |
|---|---|
| 北京 | 湖南 |
| 1 | 1234567890 |
| 2 | 123456 |
这样的话就只保留了我们想要的数据内容。采用这样的方式的话就提高了我们获取到想要字段数据的概率。然后我们可以偏移*号的位置来实现对username字段的输出(由于MySQL语法问题无法直接写为*号,所以我写为a.*和b.*,两者一样)效果如下:
在前端显示的结果如下:
| 现住址 | 来自于 |
|---|---|
| 北京 | 湖南 |
| admin | 1 |
| admin123 | 2 |
这样就能将username自段也带到前端显示。所以使用INNER JOIN 的作用只是提高了我们能快速查出我们想要字段效率。
总结
经过对偏移注入的原理探测,发现其中存在一个问题,就是如果我们期望获取数据的表的表字段数大于或者与我们存在注入的表的字段数相当的时候,就很难使用偏移注入注出数据了。比如在本此示例中,如果users表的字段数和userinfo表的字段数一样,那这样就不能使用INNER JOIN进行查询了。只有在users表中的字段数小于或者等于userinfo表中字段数时才可以。同时如果users表中的字段数在userinfo字段数的1/2和所查询字段总数之间也无法使用INNER JOIN,只能一个一个推。甚至要是users中字段数大于所查询字段总数就不能使用偏移注入了。
所以使用偏移注入的时候是我们想要查询的数据表的列数越少越好,同时存在注入的SQL语句中select的列数越多越好。