What is SQL injection
SQL 注入攻击包括通过输入数据从客户端到应用程序插入或注入SQL 查询。 成功的 SQL 注入漏洞可以从数据库中读取敏感数据、修改数据库数据(插入/更新/删除)、对数据库执行管理作(例如关闭 DBMS)、恢复 DBMS 文件系统上存在的给定文件的内容,并在某些情况下向作系统发出命令。 SQL 注入攻击是一种代码注入攻击,其中 SQL 命令被注入到数据平面输入中,以影响预定义 SQL 命令的执行。
你完全可以从 OWASP 详细了解 SQL injection:
在上述的描述中,我们简单提及到 某些情况下向作系统发出命令。但是相关的原理似乎很简单,通过 SQL 对文件操作的 API 去写入篡改敏感文件(配置文件)或者写入一些 Web Shell 之类。
因此,基本上想要利用 SQL 拿下 shell 需要满足几个条件:
- 拥有 root/administrator 权限。
- 相关的配置允许文件操作 API 使用。
- 知道被操作文件路径
这些条件在 MySQL 中很难满足。一般 SQL 拿 shell 发生在 MSSQL (SQL Server)。
我不是很喜欢去给 SQL injection 做一个细致的分类。正如我老师说的,真细究起来 SQL injection 的种类多得不得了。我个人觉得不能被种类的繁多给吸引走太多目光。 毕竟知道哪里为什么可能回事注入点?又该怎么利用?如何绕过?才是重中之重。
例如,你如果想要了解为什么 cookie 为什么可以作为注入点。你完全可以从开发的视角来理解:
How to exploit
感觉 SQL injection,或者 Code injection 这个大类的难点并不在注入代码。而是如何绕过过滤器、WAF却能够正常执行。正如 and 1=1
或者 XSS Attack 中的 <script>
基本都会被当代 WAF 给拦截。属于是你敢注入就敢 ban。
Using encode
最简单的绕过方式就是编码。例如,我们想要替换空格。在 web 环境中,我们完全可以使用 URL 编码: %20
、%09
、%0a
、%0b
、%0c
、%0d
、%a0
、%00
。 自然还有一些其他的方式,例如:/**/
。
除此之外,我们还可以利用 HEX 编码。如果你不理解这是为什么,不妨亲自试试如下 sql 语句的结果:
select '1' = 0x31;
然后,你可以再试试下面这个语句:
select '1a' = 0x3161;
这些小实验或许足够给到我们一些思路,其中最经典的典型就是:
select length(hex(password)) from users WHERE username = 'target-user-name';
Chunked the payload
并不是所有的web服务都支持分块传输功能。
我们知道在传输大量数据的时候 TCP 会采用切片传输。而 HTTP、HTTPS 也会使用分块传输,毕竟二者都依赖于TCP。好巧不巧的是,HTTP和HTTPS的分块传输是由TCP完成的。
其中,报文会使用 Transfer-Encoding: chunked
来标记启用了分块传输。或许你还可能会看到类似 Trailer: Content-MD5
的字样, 我觉得这可能是校验传输内容的手段。当然这个字段是可选的而非必要的,因此我们可以搁置不管。而更为具体的报文细节,我们用一张图来展示:

值得一提的是,在描述分块大小的时候不仅采用了 16 进制来表示,而且后续还可以利用 ;
来注释。这也就导致你可能会见到类似如下的报文内容:
Transfer-Encoding: chunked
4;注释abcd3;zhushi1111111111111111abc0
这样的数据包也更加具备迷惑性。但似乎在几年前 WAF 开发者就注意到了这点。WAF 应该会陆陆续续地加强对此类包的检测。更为详细的内容,你完全可以借阅:
using CAST or CASE-WHEN syntax
SQL injection并不是一个轻松活。除了绕过防御之外,我们还需判断/猜测对方的数据表的类型。在对方服务器会返回报错情况或者可以盲注的前提下,我们完全可以试试 case 语法:
1=CAST((SeLeCt <column name> from <table name>) AS SIGNED)--
如果上述 SQL 语句不能够不能执行转换,会出现 “xxx must be xxx” 的类似语句。但在 mysql 8.0 中会返回 0 或 1。
SELECT 1=CAST((SELECT username FROM users LIMIT 1) AS SIGNED);# +--------------------------------------------------------+# | 1=CAST((SELECT username FROM users LIMIT 1) AS SIGNED) |# +--------------------------------------------------------+# | 0 |# +--------------------------------------------------------+
注意,上述语句中的 LIMIT 1 有一定讲究,读者可以自行实验理解。
此外在多条件判断的情况下,CASE-WHEN 语法比 IF 语法香一万倍。这两个语法应该也是 CTF 中较为常见的手段。反正我问 AI 直接怀疑我在打 CTF 靶机。 废话少说,我们还可以利用这一个语句稍微复杂化一下。
SELECT 1=CAST( CASE WHEN true THEN (SELECT username FROM users LIMIT 1) ELSE 456 END as SIGNED) as result;
下面我们给出一些靶机的实例:


How to prevent SQL injection
最熟为人知的防御方式是预编译(参数化编程)。事实上,预编译并不是万能的。如果你参与开发过一些 web platform,你会发现在一些情况下 SQL 查询语句无法单纯地使用预编译实现。 最为常见的例子就是 Order by 语法。我们知道 Order by 可以支持如下两种语法形式:
# case 1:select * from users Order by 1 desc;# case 2:select * from users Order by username desc;
而面对如下语法就会产生报错:
-- case 1:select * from users Order by '1' desc;-- case 2:select * from users Order by 'username' desc;
而预编译能够提供的加入方式 setString()
方法会自动携带 '
。如是在 Order by 语法中,自带的预编译方法完全不能满足要求。因此,时至今日的开发还是采用拼接。
综上所述,学会设置黑白名单、过滤器(正则过滤等)还是很重要的。如果你想有点了解,可以移步到如下陈列的文章: