SQL 注入攻防:绕过注释符过滤的N种方法
文章目录
- 引言
- 注释符是什么
- 注释符在 SQL 注入中的使命
- 开发者如何过滤注释符
- 攻击者绕过注释符过滤的奇技淫巧
- 实战演练
- 防御之道:如何防止注入者绕过注释符
引言
想象一下,你是一名网站安全工程师,刚刚部署了最新的WAF规则,过滤了所有常见的 SQL 注入关键字和符号(如 –, #)。你觉得高枕无忧了,但攻击者依然轻松拿到了数据库权限… 问题出在哪里
很多时候,我们低估了注释符在 SQL 注入中的重要性,以及攻击者在面对过滤时表现出来的“创造力”。本文将带你深入探讨,当注释符被过滤后,攻击者是如何巧妙绕过的
本文将介绍注释符的作用、常见的过滤方式,并重点详解多种绕过注释符过滤的技巧,最终给出真正有效的防御方案
注释符是什么
注释符是编程语言和SQL等查询语言中一种特殊的语法符号。它的核心使命是:告诉编译器或解释器,被它标记的文本内容不是需要执行的代码,而是给人看的说明、笔记或注解
你可以把它理解为代码世界里的 “便利贴”
核心作用与目的
相信学过编程语言的都不陌生
-
提高代码可读性:
在复杂的SQL查询中,开发者可以用注释来解释某段代码的用途、逻辑、作者或修改日期。这让其他阅读代码的人(或未来的自己)能更快地理解代码意图 -
调试和排除代码:
在测试或调试时,如果不想执行某段SQL代码,不需要直接删除它。只需用注释符将其“注释掉”,数据库就会忽略它。这可以方便后续需要时再恢复
SQL中常见的注释符类型
SQL主要支持两种注释方式:
-
单行注释 (Single-line Comments)
-
注释掉从符号开始到行尾的所有内容
-
– (两个连字符,一般用- -+表示,ps:- -中间没有空格,因为显示问题在此加个空格):这是最通用的标准SQL单行注释符。注意: 许多数据库(如Oracle, PostgreSQL, SQL Server)要求 – 后面必须跟一个空格或控制字符(如换行),否则可能无效或报错。正确写法是 – 注释内容
-
#(井号):主要用于 MySQL 及其分支(如MariaDB),不是所有数据库都支持
-
-
多行注释 (Block Comments)
- 注释掉一个连续的代码块,可以跨越多行
- / */:这是通用*的多行注释符,绝大多数数据库都支持。/* 表示注释开始,*/ 表示注释结束
注释符在 SQL 注入中的使命
注释符在 SQL 注入攻击中扮演着“清道夫”和“魔术师”的角色,其使命是巧妙地操纵原始SQL查询的结构,使攻击者注入的恶意代码能够顺利执行,同时“处理”掉后续会引发语法错误的冗余部分
我们可以将它的使命分解为以下几个核心任务:
1. 主要使命:截断查询,消除语法错误
这是注释符最经典和最常见的用途。Web应用程序通常会拼接用户输入来构建SQL查询
拿 less - 1 举例
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
这里 $id 是用户提交的输入
- 攻击者的目标:绕过密码验证,以任何用户(例如管理员 admin)身份登录
- 攻击输入:
- URl:?id=1’ --+
拼接后的最终查询变为:
$sql="SELECT * FROM users WHERE id='?id=1' --+' LIMIT 0,1";
现在,我们来看注释符 –+ 是如何完成它的使命的:
- 闭合引号:注入的 ?id=1’ --+ 中的单引号首先完成了字符串的闭合。原本的 ‘$id’ 变成了 ‘?id=1’ --+’
- 注释符生效:–+ 是单行注释符,它会将其后的所有内容都标记为注释
- 清除冗余:于是,–+ 之后的所有字符 ’ LIMIT 0,1" 都被数据库引擎忽略不计
最终,数据库真正执行的查询只剩下:
SELECT * FROM users WHERE id='?id=1'
这条查询返回的回显就能让攻击者看出破绽
在这里,注释符的使命就是 “清理战场”,将原本会导致语法错误(因为多出了一部分字符串和关键字)的无效查询,变成了一个语法完全正确且符合攻击者意图的有效查询
2. 辅助使命:绕过过滤或特定场景
注释符还有其他巧妙的用途:
a. 内联注入(Inline Injection)
有时注入点不在语句末尾,而是在查询中间。注释符可以用来终止当前子句,然后开始一个新的、完全不同的语句
b. 绕过简单过滤(Less Common)
某些非常初级的防御手段可能会检查常见的关键字如 AND、OR、SELECT,但可能不会严格检查注释符。攻击者可以利用注释符拆分关键字来绕过(但这种绕过于时,现代WAF很难用这招绕过)
开发者如何过滤注释符
核心思想:开发者试图通过输入过滤或黑名单机制,在用户输入拼接到SQL语句之前,移除或转义掉注释符,从而“净化”输入
-
直接字符串替换过滤
防御思路:在最基础的层面,开发者会尝试直接移除或替换用户输入中的注释符
例如:
Less-23
-
正则表达式黑名单过滤
防御思路:使用更复杂的正则表达式来识别和移除注释模式 -
转义特殊字符
防御思路:不直接移除注释符,而是转义引号,使注释符失去上下文
攻击者绕过注释符过滤的奇技淫巧
- 编码绕过技巧
-
原理:应用程序的过滤逻辑可能只检查明文,未进行解码或多次解码
-
URL编码绕过
技巧:使用URL编码表示注释符
-
双重URL编码绕过
技巧:对已经编码的内容再次编码
-
Unicode/UTF-8编码绕过
技巧:使用Unicode表示字符
- 利用空白符与制表符
- 原理:过滤逻辑可能是简单的字符串匹配 “–”,如果在中间插入空白符,可能无法匹配
-
Payload示例:
- - - (中间有空格):admin’ OR 1=1- -+
- %0A– (换行符后跟注释):…%0A-- …
- 注意:需要测试数据库对空白符的解析特性。MySQL 通常允许注释符后跟空白符
-
- 不使用注释符的替代方案
-
原理:最高明的绕过是根本不需要它。思考:为什么要用注释符?是为了处理查询的后半部分。那有没有不用注释符也能让后半部分失效的方法?
- 精心构造Payload平衡引号
技巧:通过精心构造使整个查询语法正确,无需注释掉后续部分
示例Payload:
' or '1'='1' and '1'='1
拼接后:
SELECT * FROM users WHERE username = '' OR '1'='1' AND '1'='1' AND password = 'xxx'
由于AND优先级高于OR,实际等价于:
SELECT * FROM users WHERE (username = '') OR ('1'='1' AND '1'='1' AND password = 'xxx')
- 使用CASE语句
技巧:使用CASE语句构造条件注入,无需注释符
示例Payload:
' OR CASE WHEN (SELECT COUNT(*) FROM users) > 0 THEN 1 ELSE 0 END = 1 AND '1'='1
- 利用过滤逻辑缺陷
- 递归过滤绕过
技巧:如果过滤只执行一次,可以构造使过滤后产生新注释符的Payload
示例Payload:
admin'-- -
过滤–后变为:
admin' -
在某些上下文中,-可能被解释为负号或其它操作符,但仍可能造成注入
- 上下文区分绕过
技巧:利用过滤系统无法区分代码上下文的特点
示例Payload:
admin' AND password = '' OR 1=1--
即使过滤了注释符,前面部分仍可能构成有效注入
实战演练
理论再多,不如实践一番
环境设置: 本示例为 sqli-labs 23
提交参数
?id=1
测试闭合方式,运气很好一下子就试出来了,通过输入 1’ 后出现报错,且报错信息中显示 ‘‘1’’ LIMIT 0,1’,可判断出注入点为单引号闭合
?id=1'
按正常逻辑来说应该添加注释符进一步验证,但这里却出现了报错
?id=1' --+
查看本题源码,发现过滤了注释符
这段代码的含义为:
通过移除 # 和 – 来阻止攻击者注释掉SQL查询的后续部分
所以通过构造Payload平衡引号即可绕过
?id=1' '
构建注入语句即可查询出库名
?id=-1' union select 1,database(),3 '
大家也可以试一下其他方法,例如:
通过 or ‘1’='1 绕过
?id=-1' union select 1,database(),3 or '1'='1
通过老版本的漏洞 ;%00 也行 (注:这不是通用方案,且在现代环境中大多已修复)
?id=-1' union select 1,database(),3 ;%00
我在测试发现 ;%00 也能绕过的时候感到非常新奇,没想到这么经典的绕过技巧在这里也能使用
;%00 成功绕过原理如下:
由于篇幅原因,后续的注入语句就不再展示了,不会的可以看:字符型注入
防御之道:如何防止注入者绕过注释符
通过绕过方法哪里也能看出问题所在
-
不要试图单纯地黑名单过滤:本文展示的绕过技巧已经证明这是徒劳的
-
终极解决方案:
- 预编译语句(Prepared Statements):绝对首选。将SQL语句结构与数据完全分离,从根本上杜绝注入,注释符只会被当作数据内容,而非指令
- 使用严格的输入验证:对于类型确定的输入(如数字ID),强制转换为 int 型
- 使用权威的安全库:如 PHP 的 PDO、Java 的 MyBatis 等
- 最小权限原则:数据库账户不应有高权限,限制其只能访问必要的表和操作