SQL 注入是一种常见的通用漏洞,在 CTF 中也是常客。
我对于通用漏洞的理解:和任何框架没有关系,和任何语言没有关系。
曾经做完 sqli-lab 的我非常的天真,想尝试把 SQL 注入漏洞进行系统性的总结归档,我发现,这真的太难太难了。
所以就有了现在这篇文章,采用问答的方式,记录一些自己遇到和收集的 SQL 注入的思想,让自己能在遇到具体问题时进行具体的分析。
1 基本原理
将用户输入的参数当作 SQL 语句执行。
更好的解释:用户输入的参数可以改变 SQL 的语法或语义。
2 防御与利用
攻与防不能独立的看待,需要根据实际的问题去解决。例如:使用 addslashes 能防御 SQL 注入,但是在某些特殊情况又可以进行绕过(如:宽字节注入、数组注入),但是用一些简单的方法又能防御掉这些绕过(如:set UTF-8、类型检查)。
问:如何检测 SQL 注入?
无源码:
- 使用 SQLmap 跑一下。
- 使用常见的闭合方式 fuzz 一下。
- 手动闭合?不推荐,测试不完全。
有源码:
- 中型代码
- 查看 SQL 语句编写是否规范(具体请查看后文的防御方式)。如:
- 查看官方文档是否有自动类型转换,构造数组或其他数据类型绕过。如:AntCTF 8-bit-pub
- 小型代码:搜集 tricks,逐个分析以及测试。
问:怎么绕过 addslashes?
- 利用宽字节注入。(正常运行函数)
- 条件:MySQL 的编码为 GBK。
- 原理:PHP 对字符的编码为 UTF-8,
%27
为'
,%5c
为反斜杠,%df
无字符。当给 PHP 发送%df%27
时,PHP 会将引号进行转义,转义为%df%5c%27
。经过转义后的字符在 UTF-8 下是安全的,但是在 GBK 中%df%5c
会转义为運
字,而引号并没有被转义。 - 修复:(参考:浅析白盒审计中的字符编码及SQL注入-PHITHON)
- 调用一下 mysql_set_charset 函数,设置当前连接的字符集为 GBK。使用 mysql_real_escape_string 进一步过滤。
- 设置 GBK 之后,设置 character_set_client=BINARY。
- 使用 PDO(PHP Data Object)。如果 PHP < 3.6,禁用 PHP 仿真预编译。
- 利用特殊类型进行绕过(不正常运行函数)
- 条件:能控制传入参数的数据类型。$_GET[“a”] 可能为数组。
- 原理:addslashes 只能处理字符串
问:SQL 注入有哪些利用方法?
基本功能:
- 任意账户登陆。(有些转义函数可能无法实现完全可控,例如:antCTF的8-bit-pub)。
- 数据库读取。
进阶功能:
- 系统文件读取。
- 系统文件写入,即写 Webshell。
- 执行命令。
问:过滤关键字如何绕过?
方法1:双写绕过
- 条件:将过滤字符删除,只删除一次(没有进行递归删除)。
- 原理:删除之后的字符串可以形成新的关键字 an
and
d。
方法2:大小写绕过
- 条件:使用正则表达式进行匹配,但是没有使用
/i
- 原理:大部分数据库对大小写不敏感。
问:在数据库的报错信息中,如何显示自己需要的字符串?
2020 年 n1ctf 中 Web-SignIn 一题中用到
- exp()函数
问:在做白盒审计时,如何快速定位是否存在 SQL 注入?
- 全局搜索关键字:select、query、db、sql等。
- 宽字节注入:GBK
- 二次 urldecode 注入:urldecode, rawurldecode
- 查看所使用的库文档,找文档的 escape。
问:常见的 SQL 注入的防御方法有哪些?
- GPC/runtime 函数过滤(部分防御 SQL 注入,具体参考《代码审计》一书)。
- GPC 负责对 GET,POST,Cookie 过滤。
- runtime 负责对文件过滤。
- 过滤函数和类
- addslashes 函数(addslashes 不知道任何有关 MySQL连接 的字符集,转义后更有可能导致错误)
- mysql_real_escape_string 函数
- intval 函数
- PDO 预编译
问:预编译的方法怎么绕过?
方法1:
- 条件:将用户输入的参数直接写入预编译语句。
- 原理:预编译是数据库提供的功能,分为 prepare 和 execute。prepare 是将需要执行的语句(不带数据)发送数据库,数据库进行词法分析、语法分析、语义分析,固定执行的具体流程。execute 将数据发送给数据库,发送的数据只能被作为字符串处理,无法改变 SQL 语句的执行流程。
方法2:
- 条件:模拟预编译。
- 原理:PHP 的模拟预编译的实质是转义,也就是调用
mysql_real_escape_string
函数进行转义。