目录

SQL-注入攻防绕过注释符过滤的N种方法

SQL 注入攻防:绕过注释符过滤的N种方法

引言

想象一下,你是一名网站安全工程师,刚刚部署了最新的WAF规则,过滤了所有常见的 SQL 注入关键字和符号(如 , #)。你觉得高枕无忧了,但攻击者依然轻松拿到了数据库权限… 问题出在哪里

很多时候,我们低估了注释符在 SQL 注入中的重要性,以及攻击者在面对过滤时表现出来的“创造力”。本文将带你深入探讨,当注释符被过滤后,攻击者是如何巧妙绕过的

本文将介绍注释符的作用、常见的过滤方式,并重点详解多种绕过注释符过滤的技巧,最终给出真正有效的防御方案


注释符是什么

注释符是编程语言和SQL等查询语言中一种特殊的语法符号。它的核心使命是:告诉编译器或解释器,被它标记的文本内容不是需要执行的代码,而是给人看的说明、笔记或注解

你可以把它理解为代码世界里的 “便利贴”


核心作用与目的

相信学过编程语言的都不陌生

  1. 提高代码可读性
    在复杂的SQL查询中,开发者可以用注释来解释某段代码的用途、逻辑、作者或修改日期。这让其他阅读代码的人(或未来的自己)能更快地理解代码意图
  2. 调试和排除代码
    在测试或调试时,如果不想执行某段SQL代码,不需要直接删除它。只需用注释符将其“注释掉”,数据库就会忽略它。这可以方便后续需要时再恢复

SQL中常见的注释符类型

SQL主要支持两种注释方式:

  1. 单行注释 (Single-line Comments)

    • 注释掉从符号开始到行尾的所有内容
    • 两个连字符,一般用- -+表示,ps:- -中间没有空格,因为显示问题在此加个空格):这是最通用的标准SQL单行注释符。注意: 许多数据库(如Oracle, PostgreSQL, SQL Server)要求 后面必须跟一个空格或控制字符(如换行),否则可能无效或报错。正确写法是 – 注释内容
      https://i-blog.csdnimg.cn/direct/9e348083da4e446885ab056ba4eb7c4f.png
    • #(井号):主要用于 MySQL 及其分支(如MariaDB),不是所有数据库都支持
      https://i-blog.csdnimg.cn/direct/b13ba28691c24424b8c5c6412483b749.png
  2. 多行注释 (Block Comments)

    • 注释掉一个连续的代码块,可以跨越多行
    • / */:这是通用*的多行注释符,绝大多数数据库都支持。/* 表示注释开始,*/ 表示注释结束
      https://i-blog.csdnimg.cn/direct/b5307014bc944a998a54ce2f9b778e75.png

注释符在 SQL 注入中的使命

注释符在 SQL 注入攻击中扮演着“清道夫”和“魔术师”的角色,其使命是巧妙地操纵原始SQL查询的结构,使攻击者注入的恶意代码能够顺利执行,同时“处理”掉后续会引发语法错误的冗余部分

我们可以将它的使命分解为以下几个核心任务:

1. 主要使命:截断查询,消除语法错误
这是注释符最经典和最常见的用途。Web应用程序通常会拼接用户输入来构建SQL查询

拿 less - 1 举例
https://i-blog.csdnimg.cn/direct/3cfcad7c24a141f8bdd35deb3de9ebdf.png

$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";

现在,我们来看注释符 –+ 是如何完成它的使命的:

  1. 闭合引号:注入的 ?id=1’ –+ 中的单引号首先完成了字符串的闭合。原本的 ‘$id’ 变成了 ‘?id=1’ –+’
  2. 注释符生效–+ 是单行注释符,它会将其后的所有内容都标记为注释
  3. 清除冗余:于是,–+ 之后的所有字符 ’ LIMIT 0,1" 都被数据库引擎忽略不计

最终,数据库真正执行的查询只剩下:

SELECT * FROM users WHERE id='?id=1'

这条查询返回的回显就能让攻击者看出破绽

在这里,注释符的使命就是 “清理战场”,将原本会导致语法错误(因为多出了一部分字符串和关键字)的无效查询,变成了一个语法完全正确且符合攻击者意图的有效查询

2. 辅助使命:绕过过滤或特定场景

注释符还有其他巧妙的用途:

a. 内联注入(Inline Injection)
有时注入点不在语句末尾,而是在查询中间。注释符可以用来终止当前子句,然后开始一个新的、完全不同的语句

b. 绕过简单过滤(Less Common)
某些非常初级的防御手段可能会检查常见的关键字如 ANDORSELECT,但可能不会严格检查注释符。攻击者可以利用注释符拆分关键字来绕过(但这种绕过于时,现代WAF很难用这招绕过)


开发者如何过滤注释符

核心思想:开发者试图通过输入过滤黑名单机制,在用户输入拼接到SQL语句之前,移除或转义掉注释符,从而“净化”输入


  1. 直接字符串替换过滤
    防御思路:在最基础的层面,开发者会尝试直接移除或替换用户输入中的注释符
    例如:
    Less-23
    https://i-blog.csdnimg.cn/direct/1e13ce0949a64331ad405792ce9a6c9b.png
  2. 正则表达式黑名单过滤
    防御思路:使用更复杂的正则表达式来识别和移除注释模式
  3. 转义特殊字符
    防御思路:不直接移除注释符,而是转义引号,使注释符失去上下文

攻击者绕过注释符过滤的奇技淫巧

  1. 编码绕过技巧
  • 原理:应用程序的过滤逻辑可能只检查明文,未进行解码或多次解码
  • URL编码绕过
    技巧:使用URL编码表示注释符

https://i-blog.csdnimg.cn/direct/405fc0f50daa45f681baa6907d76d8eb.png

  • 双重URL编码绕过
    技巧:对已经编码的内容再次编码
    https://i-blog.csdnimg.cn/direct/1ee99d831eba4cd69ab33c4cd5e6f515.png
  • Unicode/UTF-8编码绕过
    技巧:使用Unicode表示字符
    https://i-blog.csdnimg.cn/direct/dbb643b263234ab582a638408db03d0c.png
  1. 利用空白符与制表符
  • 原理:过滤逻辑可能是简单的字符串匹配 “–”,如果在中间插入空白符,可能无法匹配
    • Payload示例:

      • - - (中间有空格):admin’ OR 1=1- -+
      • %0A– (换行符后跟注释):…%0A– …
      • 注意:需要测试数据库对空白符的解析特性。MySQL 通常允许注释符后跟空白符
  1. 不使用注释符的替代方案
  • 原理:最高明的绕过是根本不需要它。思考:为什么要用注释符?是为了处理查询的后半部分。那有没有不用注释符也能让后半部分失效的方法?

    • 精心构造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
  1. 利用过滤逻辑缺陷
  • 递归过滤绕过

技巧:如果过滤只执行一次,可以构造使过滤后产生新注释符的Payload

示例Payload:

admin'-- -

过滤–后变为:

admin' -

在某些上下文中,-可能被解释为负号或其它操作符,但仍可能造成注入

  • 上下文区分绕过

技巧:利用过滤系统无法区分代码上下文的特点

示例Payload:

admin' AND password = '' OR 1=1--

即使过滤了注释符,前面部分仍可能构成有效注入


实战演练

理论再多,不如实践一番
环境设置: 本示例为 sqli-labs 23
https://i-blog.csdnimg.cn/direct/995e9fcec91d4ab0be456a52b7ff7f7e.png

提交参数
https://i-blog.csdnimg.cn/direct/798eee25486d46fd827207e683cf1d05.png

?id=1

测试闭合方式,运气很好一下子就试出来了,通过输入 1’ 后出现报错,且报错信息中显示 ‘‘1’’ LIMIT 0,1’,可判断出注入点为单引号闭合
https://i-blog.csdnimg.cn/direct/a551011818aa4d4f8f335ad387df106c.png

?id=1'

按正常逻辑来说应该添加注释符进一步验证,但这里却出现了报错
https://i-blog.csdnimg.cn/direct/ce241dc5be944313b743dbad1d30d49f.png

?id=1' --+

查看本题源码,发现过滤了注释符
https://i-blog.csdnimg.cn/direct/1e13ce0949a64331ad405792ce9a6c9b.png
这段代码的含义为:
通过移除 # 来阻止攻击者注释掉SQL查询的后续部分

所以通过构造Payload平衡引号即可绕过
https://i-blog.csdnimg.cn/direct/4f12838b4caa4d5994bc78c3772c1d89.png

?id=1' '

构建注入语句即可查询出库名
https://i-blog.csdnimg.cn/direct/fca97f0f5f9f4af0b4c86e458e55ac6c.png

?id=-1' union select 1,database(),3 '

大家也可以试一下其他方法,例如:

通过 or ‘1’=‘1 绕过

https://i-blog.csdnimg.cn/direct/ed463e5bd132469480fc40fa79fe3310.png

?id=-1' union select 1,database(),3 or '1'='1

通过老版本的漏洞 ;%00 也行 (注:这不是通用方案,且在现代环境中大多已修复)

https://i-blog.csdnimg.cn/direct/fa4c2ca212444025bb4f740b51e798b8.png

?id=-1' union select 1,database(),3 ;%00

我在测试发现 ;%00 也能绕过的时候感到非常新奇,没想到这么经典的绕过技巧在这里也能使用

;%00 成功绕过原理如下:
https://i-blog.csdnimg.cn/direct/f2b1dddcef3743c096c1fbcdc2c024c6.png

由于篇幅原因,后续的注入语句就不再展示了,不会的可以看:


防御之道:如何防止注入者绕过注释符

通过绕过方法哪里也能看出问题所在

  • 不要试图单纯地黑名单过滤:本文展示的绕过技巧已经证明这是徒劳的

  • 终极解决方案

    1. 预编译语句(Prepared Statements)绝对首选。将SQL语句结构与数据完全分离,从根本上杜绝注入,注释符只会被当作数据内容,而非指令
    2. 使用严格的输入验证:对于类型确定的输入(如数字ID),强制转换为 int 型
    3. 使用权威的安全库:如 PHP 的 PDO、Java 的 MyBatis 等
    4. 最小权限原则:数据库账户不应有高权限,限制其只能访问必要的表和操作