SQL注入之联合查询注入简记


在页面的输出我们可以控制时,联合查询注入无疑是一种最为简单容易的注入方法

判断是否存在注入

先以:http://43.247.91.228:84/Less-1/?id=1,为例

id是我们可以控制输入的参数,首先我们需要判断注入是否存在和注入的类型

先给1后面加一个双引号:http://43.247.91.228:84/Less-1/?id=1%22

发现页面输出正常,那么我们再尝试一下单引号:http://43.247.91.228:84/Less-1/?id=1%27

发生报错:

也就是说,我们输入的字符串应该是被单引号包围的,因此附加单引号引发报错,而双引号正常

而报错的原因则在于系统又给后面拼接了一个单引号,造成语法错误,为了执行我们自己的语句,我们需要在语句最后面加一个注释符来消除这一个单引号的影响

常用的注释符如下,其中 /**/ 一般用于绕过空格,而--+ #用于注释剩余语句

需要注意的是,使用#时不能直接在URL中输入#,应该是输入其URL编码%23,否则浏览器不会将其发送给服务器端

本题适用的是--+、%23,而--不适用,其原因在于,--和后面闭合的单引号紧挨在一起,形成--',没能注释后面的单引号,如果使用--+,+会被解释为空格,也就是和--分开了,形成有效的注释,其实我们也可以直接闭合这个单引号,也就是利用--'进行注释也可

添加注释符后输出正常,更加印证了注入的可能性:http://43.247.91.228:84/Less-1/?id=1%27--+

再检查一下是否可以使用and语句:http://43.247.91.228:84/Less-1/?id=1%27%20and%201=1--+

也就是在原有语句后面添加and 1=1,1=1恒为真,因此不会影响输出结果,而如果改成1=2,则恒为假,不会有输出,此题完全满足

成功印证注入可能性

判断注入类型

一般的,我们输入这个地方可能被嵌入的是一个字符串,也有可能是一个数值

如果是字符串,这个地方可能是单引号闭合的,也有可能是双引号闭合的,也有可能是引号+括号闭合的

如果是数值,则不再需要引号

首先需要通过计算判别是不是数值类型,也就是我们传入的id为2-1,如果和id=1时输出结果一样,表明此处是一个数值类型的注入,否则是一个字符串类型的注入

如:http://43.247.91.228:84/Less-2/?id=2-1,就是一个数值型注入,因此在注入此题时我们不需要再闭合引号,甚至不需要再添加注释符号

如果发现是一个字符串类型的注入,则添加一半引号测试什么时候能闭合

如果添加单引号发生报错,则说明应该闭合单引号,添加双引号同理

如果还需要括号来闭合,那么我们一般能在报错信息里找到端倪

例如:http://43.247.91.228:84/Less-3/?id=2%27

我们在单引号添加时发现报错,但是执行and语句失效,在报错信息里可以发现:

因此我们需要用括号来闭合此处的字符串,也就是') payload --+即可

http://43.247.91.228:84/Less-3/?id=2') and 1=2--+

构造and语句成功

当然较为复杂的问题可能需要双括号才能闭合,没有报错信息的提示的话,只能进行猜测了

获取输出结果数

利用联合查询,我们可以先让系统原本的查询语句没有输出结果,再让我们自己的查询语句和系统的查询语句有相同数目的输出结果即可覆盖原来的输出,达到注入的目的

如何得到系统的查询语句有多少个输出结果?由于一般的查询语句都是select * from 表名,因此一定意义上我们只要获得表的列数就可以了

爆列数一般采用order by语句,order by即输出结果按照第几列的数据来排序

如果指定存在的列数,则正常输出,如果指定不存在的列数,则输出报错

对于:http://43.247.91.228:84/Less-1/

我们采用order by语句发现,order by 1-3都可以正常输出,但运行到4时,报错:http://43.247.91.228:84/Less-1/?id=1' order by 4--+

显然的,表只有三列

因此我们尝试利用联合查询来覆盖数据,也就是先将前一个查询的id变得非常大没有结果输出,然后再在闭合了的括号后面追加一个查询,payload如下

其中1的数目是列数:http://43.247.91.228:84/Less-1/?id=10000' union select 1,1,1 --+

可以看到,系统输出变成了:

因此输出成功被我们覆盖,联合查询成功,后续我们只需要将1更换为我们的查询语句即可

如果此时发现仍旧报错:

则说明输出结果的数目没有找对,再尝试一下其他数目直到正常输出即可

获取数据库名

对于一般的联合查询注入,我们一般利用的是数据库的索引表来进行的

首先看一下数据库,直接用database()命令即可

构造payload:

对于:http://43.247.91.228:84/Less-1/?id=10000' union select(database(),1,1) --+

发现页面输出没有变化,因此第一个参数很可能没有被输出,因此我们将payload更换到第二列里面:http://43.247.91.228:84/Less-1/?id=10000' union select 1,database(),1 --+

得到输出:

因此数据库名为security

获取表名

获取表名,我们用的一般是information_schema数据库下的tables这个表,这里面包含了当前数据库中所有的表名

为了避免爆出 information_schema数据库 中的表名,我们一般还要附加参数限制:where table_schema=database(),也可以将database()替换为需要爆表的数据库名来定向获取指定数据库中的表

需要注意的是, table_schema=后面的数据一般都是先进行Hex编码再提交,这样可以避免出错和避免使用引号,例如上面的security数据库名,进行Hex编码后为7365637572697479,再加上0x表示十六进制,故为0x7365637572697479,故我们构造payload如下:

得到页面回复如下:

因此 security 数据库中共有emails,referers,uagents,users四张表

获取字段名

知道了表,由于我们输出的位置有限,要获取数据,还需要有字段名

获取字段名,我们用的一般是information_schema数据库下的 columns 这个表,这里面包含了当前数据库中所有的字段名

同样的,我们限制参数 table_name 来获得指定表的字段名,同样需要对表名进行Hex编码

例如我们想看看上面那张users表里面有啥,我们就运行下面的查询:

或者:

得到页面回复:

因此users表里面共有id,username,password三个字段

获取数据

做了一系列准备,我们终于要读数据啦

知道了数据库名字段名和表名,我们直接用下面的查询语句就可以读数据

如果查询的表在同一数据库下,不需要指定数据库名也可

对于上面的题,我们构造payload:

得到页面回复:

我们就读出了所有用户名,需要读其他参数的时候同理

提权获取Webshell

我们可以运行查询语句,也就可以使用select语句来写入外部文件,同样也就可以写入我们的一句话木马

但是需要Mysql数据库有相关的权限

payload如下:

绕过限制

1.绕过空格:

一般使用注释符号实现,即将所有空格替换为/**/

也可以考虑使用括号进行绕过:select(user())from dual where(1=1)and(2=2)

2.group_concat 绕过

如果查询中不允许使用 group_concat ,那我们可以在查询语句末尾附加limit 1,2这样的语句来一条一条输出我们要查询的内容

3.id数值过滤绕过

如果PHP端过滤了不合法的ID,可以在数值型注入时考虑使用加法绕过

4.输出数据过滤绕过

如果系统在输出数据时过滤敏感字符,操作方法一般有:

①base64编码后输出

在查询结果后附加to_base64函数即可

②倒序输出

在查询结果后附加reverse函数即可

③字符串截取输出

5.引号过滤绕过

也就是在传输表名等参数时先使用十六进制编码

6.逗号绕过

7.比较符号绕过

8.and绕过

9.绕过注释符号

10.绕过union过滤

也可尝试混用大小写绕过

利用注释符隔开绕过

11.绕过=过滤

12.利用编码绕过

13.双关键字绕过

对于一些替换指定字符的WAF,可以采取双关键字绕过:

WAF替换关键词后,反而成为正确查询语句


一如既往 万事胜意