当前位置: 首页 > news >正文

WAF防护下SQL注入绕过实战:当select与union被过滤后的渗透测试思路

1. 从一次真实的渗透测试说起:当常规注入手法全部失效

最近在做一个授权测试项目时,遇到了一个让我印象深刻的场景。目标是一个典型的Web应用登录接口,我习惯性地丢了个单引号进去,页面直接返回了一个醒目的红色警告:“检测到非法输入”。换了几种基础的联合查询(Union Select)Payload,无一例外,全部被拦截。这显然不是简单的错误回显被关闭,而是部署了Web应用防火墙(WAF)——它像一道智能闸门,精准地识别并阻断了我的试探。

这让我想起了很多安全从业者,尤其是刚入门的朋友,常有的一个误区:学会了‘ or 1=1--union select 1,2,3,就觉得掌握了SQL注入的精髓。实际上,在如今WAF(无论是云WAF还是硬件WAF)基本成为企业标配的背景下,这些“教科书式”的Payload存活率极低。WAF的核心策略之一,就是针对selectunion这类在SQL注入中扮演“骨架”作用的关键字进行高强度过滤。你的攻击流量在到达数据库之前,就已经被这道关卡无情地丢弃了。

所以,我们今天要深入探讨的,绝不仅仅是几个“绕过Payload”的罗列。而是要从WAF的检测原理出发,理解它为什么能拦截,再基于这些原理,系统地构建我们的绕过思路。核心目标很明确:当selectunion这两个最常用的“武器”被明令禁止时,我们如何利用数据库特性、协议特性、甚至是WAF规则本身的“盲点”,重新打通从注入点到数据泄露的路径。这更像是一场思维的博弈,而不仅仅是技术的堆砌。

2. 理解对手:WAF如何识别与过滤select/union

在思考如何绕过之前,我们必须先站在防守方的角度,弄清楚WAF通常是怎么工作的。虽然不同厂商的WAF规则集(Rule Set)千差万别,但其核心检测逻辑可以归纳为几个层次,理解这些层次,就等于拿到了绕过之门的钥匙。

2.1 基于正则表达式的模式匹配

这是最基础、也最普遍的检测方式。WAF会维护一个庞大的恶意模式库,其中必然包含针对selectunion等关键词的正则表达式。这些正则可能非常“贪婪”,例如:

  • 简单的单词匹配:/\bselect\b/i/\bunion\b/i。这里的\b表示单词边界,i表示不区分大小写。
  • 组合匹配:/union\s+select/i,用于匹配“union select”这个常连用的攻击模式。
  • 上下文感知:可能会结合前后字符,判断关键词是否出现在疑似SQL语句的上下文中(例如,跟在单引号、括号后)。

这种方式的优点是速度快,覆盖广。但缺点也很明显:规则是死的,而HTTP请求和SQL语句的“形态”是活的。只要我们构造的Payload不匹配这些预设的正则模式,就有可能穿透。

2.2 语义分析与语法树解析

更高级的WAF会尝试对输入进行“理解”。它们可能内置一个轻量级的SQL解析器,对输入字符串进行词法分析和语法分析,尝试构建一个抽象的语法树(AST)。如果解析器发现输入中包含了SELECT语句的语法结构,但上下文(比如这是一个登录名的参数)又极不正常,就会判定为攻击。

例如,对于admin‘ union select 1,2,3--,解析器能识别出这是一个由两个SELECT子句通过UNION连接的合法(从语法角度看)SQL语句片段,从而触发告警。这种方式的绕过难度显著增加,因为它不再是简单的字符串匹配,而是触及了“意图”层面。

2.3 规范化与混淆检测

攻击者常用的手段是对Payload进行编码、混淆。成熟的WAF会包含一个“规范化”模块。例如:

  • URL解码:将%55%4E%49%4F%4E(UNION的URL编码)还原为UNION
  • HTML实体解码:将union还原为union
  • 多重编码检测:防止通过多次编码来绕过单次解码。
  • 大小写变异检测SeLeCtuNiOn这种大小写混合,在简单正则下可能失效,但高级规则会进行大小写归一化(转为全大写或全小写)后再匹配。

这个模块的目的,是尽量将攻击者五花八门的“化妆”卸掉,还原出其本来面目,再交给模式匹配或语义分析模块去判断。

2.4 我的实战观察:WAF规则的“惰性”与“误伤”

在实际测试中,我发现一个有趣的现象:很多WAF的规则集存在“惰性”。它们可能只检测某些特定位置(如参数值)或特定格式(如完整的union select组合)的关键词。同时,过于严格的规则又可能导致“误伤”(False Positive),影响正常业务。因此,管理员往往会在安全性和可用性之间做一个平衡,这就在规则集里留下了缝隙。我们的绕过,很大程度上就是在寻找并利用这些缝隙。

3. 绕过策略一:同义词与功能替代——没有select也能“查”

select被直接过滤时,我们的第一反应不应该是“如何变形select”,而是思考:“在这个数据库环境中,有没有其他方式可以完成数据查询的功能?” 答案是肯定的,这依赖于对特定数据库特性的深入了解。

3.1 利用HANDLER语句(MySQL特定)

这是MySQL中一个较少被提及但功能强大的语句。HANDLER用于直接访问表的存储引擎接口,可以逐行读取数据,完全绕开SQL解析器的SELECT语法。更重要的是,绝大多数WAF的规则集根本不会包含对HANDLER的检测。

基础用法:

HANDLER table_name OPEN; -- 打开一个表的句柄 HANDLER table_name READ FIRST; -- 读取第一行 HANDLER table_name READ NEXT; -- 读取下一行 ... -- 持续读取直到无数据 HANDLER table_name CLOSE; -- 关闭句柄

注入利用场景示例:假设注入点位于id参数,原查询为SELECT * FROM articles WHERE id=‘[INPUT]‘。 我们可以构造:

-1‘; HANDLER users OPEN; HANDLER users READ FIRST;--

执行后,会先因id=-1使原查询无结果,然后执行HANDLER语句读取users表的第一行数据。数据会随着结果集返回,只是字段名可能是默认的。你需要通过多次READ NEXT来遍历数据。

注意HANDLER操作需要对应表的SELECT权限。但它不通过常规的SQL解析路径,因此是绕过SELECT关键字过滤的利器。它的缺点是操作略显繁琐,不适合一次性获取大量数据,但在关键时候能出奇制胜。

3.2 使用PROCEDURE ANALYSE()泄露数据(MySQL)

PROCEDURE ANALYSE()是MySQL用于分析查询结果并给出列优化建议的一个子句。它有一个副作用:会将查询结果中的样本数据作为分析建议的一部分返回。

利用方法:配合子查询,即使外层SELECT被过滤,我们也可以在子查询或特定上下文中使用它。 例如,在可以控制ORDER BYLIMIT后子句的注入点:

... ORDER BY (SELECT 1 FROM (SELECT * FROM users LIMIT 1) AS t1 PROCEDURE ANALYSE())--

PROCEDURE ANALYSE()会尝试分析t1这个临时表(即users表的第一行),并在错误信息或返回结果中,可能包含该行数据的片段。通过分析错误信息或返回内容,可以逐步提取数据。这种方法通常用于基于错误(Error-Based)的注入场景。

3.3 系统视图与函数(跨数据库)

不同的数据库提供了丰富的系统视图和函数来查询元数据,有时这些查询方式可以避开对SELECT的简单过滤(如果过滤得不彻底)。

  • SQL Server: 可以利用sys.tables,sys.columns等视图,但通常还是需要SELECT。更可行的思路是利用xp_cmdshellsp_configure等存储过程进行操作系统命令执行,实现曲线救国,但这已超出纯数据查询范畴。
  • PostgreSQL: 有pg_tables,pg_stat_user_tables等系统视图。
  • Oracle: 有ALL_TABLES,USER_TAB_COLUMNS等。

关键在于:WAF可能只过滤应用程序业务逻辑中常用的SELECT语句,而对访问这些系统视图的特定语句模式检测较弱。你可以尝试将SELECT与这些生僻的系统视图名组合,有时能绕过基于常见表名(如users,admin)的关联规则。

4. 绕过策略二:编码、混淆与分割——让WAF“看不清”

如果功能替代行不通,我们就需要正面突破,对selectunion这两个词本身进行“化妆”,让WAF的检测引擎认不出来。这里的手法非常多样,核心思想是破坏正则匹配模式,同时保证数据库引擎最终能正确解析。

4.1 各类编码技术

  1. URL编码:最基础,但多数WAF会解码。可尝试双重URL编码非标准编码

    • union->%75%6e%69%6f%6e(标准URL编码)
    • %75%6e%69%6f%6e->%25%37%35%25%36%65%25%36%39%25%36%66%25%36%65(对%75等符号再次编码)。部分WAF可能只做一次解码。
    • 使用非标准编码,如将u编码为%u0075(Unicode URL编码),但并非所有环境和数据库都支持。
  2. HTML实体编码:在Web上下文中有奇效。

    • union->unionunion(十进制)。如果参数值经过Web框架的HTML解码层,可能会被还原。
  3. 十六进制编码:MySQL等数据库支持十六进制字符串。

    • select->0x73656c656374
    • 在注入中可这样使用:‘ and 1=(updatexml(1,concat(0x7e,(substring((0x73656c65637420757365722829),1,32))),1))--
    • 这里,0x73656c65637420757365722829就是select user()的十六进制形式。WAF的正则很可能无法识别这种形态。
  4. Unicode/宽字符编码:利用数据库对字符集的特殊解析。

    • 在GBK等宽字符集中,(单引号)的编码是%27,如果在其前面加一个ASCII码大于128的字符(如%df),可能形成%df%27,而%df%5c在某些解析中会被当作一个宽字符,从而“吃掉”掉转义符\%5c),导致单引号逃逸。虽然这主要用于引号逃逸,但有时会影响后续关键字的识别上下文。

4.2 语法混淆技巧

  1. 内联注释(MySQL):/*! ... */是MySQL的特有注释,其中的内容会被MySQL服务器执行,但其他工具可能将其视为注释。

    • /*!50000union*/ select 1,2,3
    • /*!union*/ select 1,2,3
    • 数字50000表示当MySQL版本大于等于5.00.00时才执行其中的语句,可用于绕过一些简单的过滤。
  2. 空白符替代union select中的空白,可以用多种字符替代:

    • 换行符:%0a(LF),%0d(CR)
    • 制表符:%09(TAB)
    • 注释:union/**/select
    • 括号:union(select(1),2,3),在某些上下文(如子查询)中,括号可以充当分隔符。
    • 这些方法可以破坏/\bunion\s+select\b/这类正则。
  3. 关键字分割:将关键字拆分成多个部分,利用数据库的字符串连接功能。

    • MySQL:concat(‘sel‘,‘ect‘)
    • SQL Server: ‘sel‘+‘ect‘
    • 然后通过动态执行(如EXEC(‘...‘)in SQL Server)或放在特定函数中执行。例如在SQL Server中:; EXEC(‘sel‘+‘ect * from users‘)--。这需要SELECT出现在一个字符串中,然后被EXEC执行,绕过了对静态SELECT的匹配。
  4. 大小写随机化与等价符

    • UnIoN SeLeCt
    • MySQL中,反引号`可以包裹标识符,在某些上下文中可以尝试`union`,但通常对关键字无效。更有效的是利用&&||作为ANDOR的替代,但这属于逻辑操作符的绕过。

4.3 我的踩坑记录:混淆的“度”与数据库版本兼容性

我曾经在一个项目里,为了绕过WAF,构造了一个极其复杂的Payload,混合了内联注释、换行符和URL编码。在测试环境(MySQL 5.7)中完美执行并获取了数据。但一到生产环境(MySQL 8.0),同样的Payload却导致了语法错误。排查后发现,是某个内联注释/*! ... */的用法在8.0中语义发生了细微变化。

教训:混淆和编码不是越复杂越好。首先要确保你的Payload在目标数据库版本和具体上下文中是语法正确的。最好的方法是,在本地或可控环境搭建一个与目标尽可能相似的环境(包括数据库类型、版本、Web服务器),先验证Payload的语法正确性,再测试其绕过WAF的能力。否则,你看到的拦截可能是WAF的功劳,也可能是你Payload本身就有语法错误。

5. 绕过策略三:协议与上下文利用——在WAF的盲区起舞

有些绕过手法,跳出了对Payload字符串本身的纠缠,而是利用HTTP协议的特性、应用程序的处理逻辑,或者WAF部署架构上的盲点。

5.1 HTTP参数污染(HPP)

WAF通常只检查单个参数的值。但如果应用程序后端(如PHP的$_GET$_REQUEST,或某些框架的解析方式)在接收到多个同名参数时,处理方式与WAF不同,就可能产生绕过。

场景:参数id存在注入。

  • 正常请求/page.php?id=union select 1,2,3(被WAF拦截)
  • HPP攻击请求/page.php?id=union/*&id=*/select 1,2,3
    • WAF可能只检查第一个id=union/*或最后一个id=*/select 1,2,3,两者单独看都不构成完整的union select,因此放行。
    • 而后端PHP的$_GET[‘id‘]可能会将多个id参数的值用逗号连接,或者只取最后一个值(取决于配置)。如果后端是Tomcat/JSP,默认会取第一个值。你需要了解目标后端的技术栈。
    • 更常见的是利用分隔符:/page.php?id=1/*&id=*/union/*&id=*/select 1,2,3,将完整的Payload拆散到多个同名参数中。

5.2 请求方法转换与内容类型

  1. GET/POST转换:WAF可能对GET请求的参数检测严格,但对POST请求的body体检测宽松,或者反之。如果一个功能点同时支持GET和POST,尝试切换请求方法。
  2. Content-Type混淆
    • Content-Typeapplication/x-www-form-urlencoded改为multipart/form-data。这两种格式解析参数的方式不同。WAF可能对后者的解析支持不完善,导致无法正确提取参数进行检测。
    • 尝试text/plainapplication/json。如果应用程序意外地支持这些格式并解析了参数,而WAF规则没有覆盖,则可能绕过。

5.3 分块传输编码(Chunked Transfer Encoding)

这是针对那些能够“流式”解析HTTP请求体的WAF的一种高级绕过技术。通过使用HTTP/1.1的Transfer-Encoding: chunked头,可以将请求体分块发送。一些WAF为了性能,可能只检查第一个数据块,或者需要等待整个请求体接收完毕才能分析。攻击者可以构造这样的请求:

  • 第一个数据块发送无害的内容。
  • 第二个数据块发送恶意的SQL注入Payload。 如果WAF基于第一个数据块就做出了“放行”的判断,恶意Payload就能抵达后端服务器。

实施这种攻击需要手动构造原始的HTTP请求,通常借助Burp Suite的Repeater工具,并手动编辑HTTP头和数据块。这对攻击者的要求较高,且并非对所有WAF都有效。

5.4 利用应用程序本身的过滤与还原逻辑

这是一种“借力打力”的思路。如果应用程序在接收到参数后,会先进行一些处理(如解码、替换、过滤),然后再拼接到SQL语句中,而WAF检查的是处理前的原始参数,那么就有可能构造一个Payload,使其在WAF看来是无害的,但经过应用程序处理后变成了恶意的。

经典案例:双重URL解码绕过

  1. 应用程序代码逻辑:$id = urldecode($_GET[‘id‘]);(进行了一次URL解码)
  2. WAF检查:原始参数id=%2527(这是%27的URL编码,而%27是单引号)。
  3. 过程:
    • WAF看到id=%2527,解码一次得到%27,认为这是一个编码后的单引号,可能触发拦截(取决于规则)。
    • 但如果WAF只做一次解码,它看到的是%2527,可能不认为这是一个单引号(因为%25%符号),于是放行。
    • 请求到达应用程序,代码执行urldecode(‘%2527‘),第一次解码得到%27,但代码逻辑里只有一次解码吗?不,urldecode函数会对%27再次解码,最终得到(单引号)!
  4. 结果:单引号成功注入。对于关键字也可以类似操作,例如将union编码为%2575%256e...(对%75%6e...再次编码)。

6. 实战串联:一个完整的绕过案例推演

让我们假设一个相对复杂的场景,将上述多种策略串联起来。目标:一个搜索功能,参数keyword存在注入,后端数据库是MySQL 5.7,部署了某云WAF,已知其过滤了unionselect(大小写不敏感),并对常见编码做了检测。

第一步:信息探测

  1. 提交keyword=test‘,返回数据库错误(MySQL),确认注入点及数据库类型。
  2. 提交keyword=test‘ and ‘1‘=‘1keyword=test‘ and ‘1‘=‘2,页面有明显差异,确认为布尔盲注。
  3. 尝试keyword=test‘ union select 1,2,3--,页面返回WAF拦截页面。

第二步:尝试简单混淆

  1. keyword=test‘ /*!50000union*/ /*!50000select*/ 1,2,3--, 依然被拦截。说明WAF可能识别了内联注释,或者检测到了数字1,2,3这种模式。
  2. keyword=test‘ uni/**/on sel/**/ect 1,2,3--, 被拦截。说明/**/注释被正常解析或检测。

第三步:尝试编码与分割

  1. keyword=test‘ and 1=(select 1), 被拦截(因为包含select)。说明对select的检测是独立的。
  2. keyword=test‘ and 1=(sel‘+‘ect 1), 语法错误(MySQL不适用+连接,应用concat)。
  3. 转换思路,放弃union,采用基于布尔的逐位探测,但需要替代select
  4. 构造Payload,利用substringascii函数,但需要从某个表查询数据。这里我们尝试用HANDLER语句作为数据源?不行,HANDLER不返回可直接用于比较的结果集。我们需要一个能返回标量值的“查询”。
  5. 利用PROCEDURE ANALYSE()进行错误注入
    • 先获取表名。假设通过其他信息推断或字典爆破,怀疑存在users表。
    • 构造Payload:keyword=test‘ and updatexml(1, concat(0x7e, (select table_name from information_schema.tables where table_schema=database() limit 0,1)), 1)--
    • 这会被拦截,因为包含select
    • 改造:尝试将select关键字十六进制编码,并嵌入到一个能触发错误执行的函数中。但updatexml第二个参数需要是select语句的执行结果,我们不能直接传递编码后的字符串。
    • 换一种方法:利用exp()函数溢出报错,但报错信息中需要包含查询结果,这又绕不开select

第四步:深入利用数据库特性——无select查询

  1. 回忆MySQL的HANDLER。虽然不能直接用于布尔比较,但可以用于时间盲注
  2. 构造时间延迟Payload:keyword=test‘ and if((substr(user(),1,1)=‘r‘), sleep(5), 0)--, 被拦截(可能检测了sleep或函数组合模式)。
  3. 使用HANDLER配合BENCHMARK或繁重运算实现时间延迟
    • 思路:如果某个条件为真,就执行一个非常耗时的HANDLER操作。
    • HANDLER本身不返回布尔值,无法用在if里。我们需要一个能返回标量值的“条件判断”。
    • 回到原点:布尔盲注的核心是比较。我们能否不通过select获取比较值?比如,利用数据库内置变量或函数?
    • 灵光一现‘ and ‘abc‘=(‘a‘||‘b‘||‘c‘)--在MySQL中,||是逻辑或,不是连接符,所以不行。concat(‘a‘,‘b‘,‘c‘)需要select
    • 利用like%通配符进行模糊匹配?这需要已知数据的一部分。

第五步:迂回策略——联合查询的替代方案既然union select被盯死,我们是否可以不用联合查询,而用其他方式获取数据?对于布尔盲注,我们确实可以不用union,但必须用select来从表中取数据。如果select被过滤,布尔盲注和报错注入的常用Payload都难以构造。 此时,需要考虑是否过滤了其他关键函数,如substring,ascii,mid,left等。如果这些函数可用,我们可以尝试一种极其迂回的方式:

  1. 利用load_file()函数读取文件内容,但这需要绝对路径和文件读取权限。
  2. 利用into outfiledumpfile写Webshell,但这需要写权限和已知Web路径。 这两种方式都严重依赖于环境配置,通用性不强。

第六步:回归基础——细微处见真章在多次尝试复杂绕过失败后,我决定回归最简单的Payload,但在细节上做文章。

  1. 尝试超长数据绕过:有些WAF对单个参数值有长度限制,超过部分不检查。我构造了一个超长的keyword参数,前面是大量填充字符(如几千个A),最后附上‘ union select 1,2,3--。结果:被拦截。说明WAF处理了长参数。
  2. 尝试参数污染(HPP)
    • 请求:/search?keyword=test‘ /*&keyword=*/union/*&keyword=*/select 1,2,3--
    • 观察后端如何处理多个keyword。通过响应差异判断,发现后端似乎只取了第一个keyword的值(test‘ /*)。失败。
  3. 尝试换行符分割:在Burp Suite中,直接修改Raw请求,在unionselect之间插入一个换行符(%0a)。
    GET /search?keyword=test‘ union%0aselect 1,2,3-- HTTP/1.1
    奇迹发生了:页面正常返回,并且显示了23的位置!这意味着,这个WAF用于检测union select的正则规则,很可能没有考虑换行符作为空白符的情况。\s通常匹配空格、制表符等,但某些正则编写时可能用了[ ]+或没有包含\n

最终Payload: 通过使用%0a(换行符)成功分隔了unionselect,绕过了WAF的检测。后续的数据获取就顺理成章了:

  1. keyword=test‘ union%0aselect 1,database(),3--获取数据库名。
  2. keyword=test‘ union%0aselect 1,table_name,3 from information_schema.tables where table_schema=database() limit 0,1--获取表名。
  3. ... 依次获取字段名和数据。

这个案例告诉我们,最复杂的混淆未必是最有效的。有时,一个最简单的协议级字符(如换行符、制表符)就能击穿WAF的规则。关键在于系统地尝试,并且对HTTP协议和正则表达式有基本的理解。在实战中,我通常会准备一个包含各种分隔符、编码变形的测试字典,用Intruder等工具进行模糊测试,效率远比手动尝试高得多。

http://www.jsqmd.com/news/1393490/

相关文章:

  • 国内GEO优化公司大揭秘,谁才是真正的王者? - 品牌测评鉴赏家
  • TVBoxOSC 电视盒子播放器上手指南:一文搞懂视频源配置与核心玩法
  • Elasticsearch Rollup 实战指南:数据预聚合原理、配置与生产运维
  • TypeScript编译通过≠生产稳定:AI SDK V7迁移实战与Node.js运行时陷阱解析
  • 北京西城区足金首饰价值深挖 奢二网赋能高端饰品资产 - 大牌科普时报
  • Word论文公式排版全攻略:居中、编号、引用与自动化技巧
  • 分析 Transformer 注意力:上下文存证,工具算张量
  • pico学术引用指南:如何在论文中正确引用这款经典实时检测框架?
  • 2026佛山品牌首饰回收迎来鉴定新纪元,三重**复核体系重塑行业信任基石 - 榆木脑袋老和尚
  • 单张照片能算出物体的真实尺寸吗?MoGe-2 单目几何估计实战拆解
  • 企业推广获客哪家好:专业选购指南与评估标准 - 汇聚至此
  • ReactLynx vs React:核心差异与迁移策略解析
  • Algotrader API密钥配置:Robinhood与Alpha Vantage认证全攻略
  • C语言scanf连续输入问题:缓冲区机制与实战解决方案
  • 10分钟跑通大麦自动抢票:双端环境搭建到高成功率配置的完整指南
  • 采购环保农药,资质合规怎么查?河北沧州企业资质核实与渠道参考 - 市场沸点
  • 深度剖析docker-postfix工作原理:从启动脚本到邮件转发流程
  • Harness Engineering:AI辅助Android开发的质量保障与工程实践
  • 如何用 DyberPet 打造你的第一个桌面宠物:PySide6 桌宠框架完整上手指南
  • 把安卓手机搬进电脑屏:投屏工具 QtScrcpy 一周上手实录
  • CVE-2026-20349思科防火墙0Day漏洞实战:自查、检测、应急加固与永久修复
  • 在线应用开发平台核心模块设计:用户、认证、RBAC、缓存与系统设置
  • 天道阅读报告第九篇
  • AI3D资产生产怎么从原型走到可用资产?用V2Fun打通生成、绑定和导出 - 生活动态圈
  • 流放之路捡到好装备总怕卖亏?这款免费价格查询工具让我估价快了十倍
  • HumanifyJS 反混淆实战指南:用 LLM 把压缩 JS 还原成人话
  • 云端协同智能系统:多机器人共享大脑的架构设计与实战指南
  • AI 短视频创作零门槛实战:输入一个主题,30 分钟拿到成片
  • Stork Oracle Auto Bot未来路线图:新功能预测与社区发展方向
  • 知识图谱构建实战:从本体设计到LLM自动化抽取