1. 项目概述当SQL注入遇上黑名单在Web安全测试或者CTF比赛中SQL注入是最经典也最考验基本功的漏洞类型。但现实世界和高质量的靶场比如Bugku不会把漏洞赤裸裸地摆在你面前它们往往会设置一些障碍最常见的就是黑名单过滤。所谓黑名单就是开发者预定义了一个“坏词”列表比如union,select,or,and,空格,--等等一旦检测到你的输入中包含这些词就直接拦截或替换掉让你的攻击Payload失效。“Bugku SQL注入实战绕过黑名单的5种骚操作”这个标题精准地指向了安全爱好者、CTF选手以及初入行的渗透测试工程师们最头疼也最想掌握的实战技能点。它不是一个泛泛而谈的SQL注入教程而是聚焦于“绕过”这个对抗性动作提供了五种具体的、可复现的“骚操作”和完整的Payload。这意味着读者拿到这篇文章目标非常明确当常规的注入语句被拦截时我手里还有哪些牌可以打这篇文章的价值就在于它把这些“牌”的玩法、原理和适用场景掰开揉碎了讲给你听。对于新手这能帮你快速突破CTF中常见的过滤关卡对于有一定经验的从业者这能系统化你的绕过思路在面对更复杂的WAFWeb应用防火墙或自定义过滤时能更快地组合出有效的测试向量。接下来我们就深入这五种绕过黑名单的“骚操作”看看它们是如何在“猫鼠游戏”中为攻击者打开缺口的。2. 绕过黑名单的核心思路与原理拆解在开始具体操作之前我们必须先理解绕过黑名单的本质。黑名单过滤是一种“已知威胁”防御模型它基于一个假设我能列举出所有危险的字符串。但SQL语言的灵活性和数据库解析器的特性使得这个假设非常脆弱。绕过黑名单的核心思路可以归结为以下几点2.1 编码与变形让“坏人”穿上“马甲”这是最基础的思路。过滤系统匹配的是字符串的字面形式如果我们能改变这个形式但不改变数据库最终解析的结果就能实现绕过。例如将关键字进行URL编码、十六进制编码、Unicode编码或者使用大小写变换SeLeCt、内联注释/*!select*/等。关键在于数据库的SQL解析器在解释语句时会将这些变形还原成标准的关键字。2.2 等价替换寻找“替身演员”如果union被禁有没有其他方式能达到联合查询的效果如果and被禁有没有其他逻辑运算符如果空格被禁有没有其他字符能起到分隔作用这就是等价替换的思路。它要求我们对SQL语法有更深入的理解知道哪些功能可以由不同的语法结构实现。2.3 利用解析特性在“语法盲区”跳舞数据库解析SQL语句时在某些上下文环境下会对字符有特殊的解释。例如注释符/**/里的内容在某些情况下会被忽略反引号、括号的用法甚至字符串拼接函数都可以被用来“包裹”或“分割”被过滤的关键字从而骗过简单的字符串匹配。2.4 多级组合混合双打单一的绕过技巧可能被更完善的黑名单针对。高水平的绕过往往是多种技巧的组合。比如先对关键字进行编码然后用特殊字符代替空格最后利用数据库特性如H2数据库的%00截断来确保Payload执行。这需要测试者根据目标的过滤强度进行动态调整。理解了这些核心思路我们再看具体的五种“骚操作”就会明白它们不是孤立的奇技淫巧而是这些思路的具体实践。每一种操作都瞄准了黑名单防御的某个薄弱环节。3. 五种绕过黑名单的“骚操作”详解与Payload构造下面我们结合Bugku这类靶场中常见的过滤场景逐一拆解这五种操作。我会为每种操作提供原理说明、典型Payload示例以及关键的注意事项。3.1 操作一内联注释与版本特异性语法这是MySQL数据库下非常经典且强大的一种绕过方式。MySQL支持一种特殊的注释语法/*!50001select*/。这里的50001是一个版本号其含义是当MySQL数据库版本大于或等于5.00.01时注释中的内容会被当作SQL代码执行如果版本低于此号则整个被当作注释忽略。而很多简单的黑名单过滤只会去匹配/*和*/之间的内容是否为关键字却不会解析这种带有版本号的特殊注释。Payload示例-1‘ union /*!50001select*/ 1,2,group_concat(table_name) from /*!50001information_schema*/.tables where table_schemadatabase()--这个Payload假设union,select,information_schema都被过滤了。我们用/*!50001select*/代替了select用/*!50001information_schema*/代替了information_schema。对于过滤系统来说它可能只看到了/*...select...*/认为这是一个注释而放行但MySQL版本5.00.01实际执行时会将其中的select和information_schema作为关键字解析。实操心得版本号选择通常使用50001即5.00.01即可这是为了兼容绝大多数MySQL 5.x及以上版本。你也可以尝试/*!select*/不带版本号在某些环境下也能被解析。位置灵活内联注释几乎可以放在SQL语句的任何位置包括关键字中间如s/*!*/elect这能进一步扰乱基于正则的过滤。注意空格有时过滤会严格检查/*后紧跟的内容适当调整空格可能有助于绕过。3.2 操作二关键字双写与大小写绕过这是一种非常“朴素”但有时有效的绕过方式针对的是编写不严谨的黑名单过滤逻辑。双写绕过假设过滤逻辑是检测到select这个单词就将其替换为空字符串‘’。那么如果我们输入selselectect过滤系统会找到中间的select并将其删除剩下的部分恰好又拼接成了select。Payload示例-1‘ ununionion selselectect 1,2,3--原理ununionion经过替换删除union后变成unionselselectect经过替换删除select后变成select。大小写绕过如果过滤逻辑是简单的、区分大小写的字符串匹配比如用strpos()或indexOf()查找select那么使用大小写混合的形式就能绕过。Payload示例-1‘ UniOn SeLeCt 1,2,3--原理MySQL的SQL关键字是不区分大小写的。UniOn和SeLeCt对数据库解析器来说与union和select完全等价。注意事项双写绕过高度依赖于过滤的实现方式是“查找并替换一次”。如果过滤是“循环查找替换直到不存在”或“直接拒绝请求”则此方法失效。大小写绕过在如今稍具规模的WAF或过滤组件中基本都已防护但在一些自写的、简单的过滤脚本中仍可能遇到。3.3 操作三使用非标准空格与注释符空格在SQL语句中用于分隔关键字和标识符是最常被过滤的字符之一。但除了标准的空格 ASCII 32还有很多字符在SQL解析器眼里也能起到“分隔”作用。常见空格替代符/**/(多行注释)最常用如union/**/select。/*!*/(内联注释)同上。%09(水平制表符Tab)URL编码形式。%0a(换行符LF)%0d(回车符CR)%0b(垂直制表符)%a0(不间断空格在某些编码下)(在URL参数中有时会被解析为空格)括号()在某些语境下可以用于包裹参数避免使用空格例如select(1),(2)。Payload示例-1‘%0aunion%0aselect%0a1,2,3%0afrom%0aadmin--这里用%0a换行符代替了所有空格。对于通过GET/POST传递的参数需要对其进行URL编码。注释符的妙用 除了用于注释/**/还可以作为“可被忽略的填充物”插入到关键字中破坏黑名单的匹配。Payload示例un/**/ion sel/**/ect 1,2,3原理过滤系统可能匹配不到完整的union或select但数据库解析时会忽略注释最终执行union select。3.4 操作四十六进制与URL编码绕过当过滤发生在应用层而解码发生在更深层如数据库驱动或数据库自身时编码绕过就派上了用场。其核心是让攻击Payload在传输和过滤检查时是一个“无害”的编码字符串在最终执行时被解码还原为“有害”的SQL代码。十六进制编码常用于处理字符串值特别是当单引号被过滤时。场景需要注入字符串‘admin‘但单引号被过滤。操作将admin转换为十六进制0x61646d696e。Payload示例... where username0x61646d696e原理MySQL会将十六进制数字自动解释为字符串。这样我们就避免了使用单引号。URL编码主要针对HTTP请求过程中的过滤。WAF或应用代码可能在收到URL编码后的参数前就进行检查也可能在之后。多次编码可能绕过前者。Payload示例union-%75%6e%69%6f%6e(一次URL编码) -%25%37%35%25%36%65%25%36%39%25%36%66%25%36%65(对%75等字符再次编码即双重URL编码)。原理如果过滤检查发生在第一次解码之前它看到的是%75%6e...匹配不到union关键字。但服务器端可能进行两次解码最终还原为union。实操心得编码位置不一定需要编码整个关键字有时只编码关键字符如空格、单引号即可。组合使用可以将十六进制编码与内联注释结合例如/*!50001select*/ 0x61646d696e。注意数据库差异十六进制语法可能因数据库而异MySQL用0xPostgreSQL用x‘...‘。3.5 操作五利用数据库特性与函数拼接这是最高阶的一种绕过方式需要深入了解特定数据库的“怪癖”和丰富的内置函数。字符串拼接绕过关键字检测如果union select被整体视为一个黑名单词组可以尝试将其拆开再用函数拼接。Payload示例MySQL-1‘ and 12 uni‘‘on sel‘‘ect 1,2,3--这里假设在字符串上下文是连接符如在SQL Server中。在MySQL中更常用concat()函数。-1‘ and 12 union concat(sel,ect) 1,2,3--假设concat函数可用且sel和ect不被单独过滤。更实际的用法是拼接出数据库名、表名。利用特定数据库特性MySQL反引号反引号用于引用标识符数据库名、表名、列名。在某些过滤宽松的场景可以尝试selectunionselect虽然这通常不合法但说明了利用特殊符号的思路。SQLite中的||在SQLite中||是字符串连接符可用于拼接关键字。注释符截断在某些旧版本或特定配置的数据库中--、#之后的内容需要换行符才被认作注释结束否则可能被当作语句的一部分执行这可以用于构造畸形语句。Payload示例利用concat和substring-1‘ union select 1,2,concat(table_name) from information_schema.tables where table_schemadatabase() limit 0,1--如果concat也被禁可以尝试concat_ws()或者更底层的方法。这种场景下比拼的就是对数据库函数库的熟悉程度。注意事项这种方法成功率不稳定严重依赖于目标系统的数据库类型、版本、配置以及过滤逻辑的精细程度。它通常是在其他常规方法都失效后的“奇招”需要大量的尝试和模糊测试。4. 实战演练组合Payload构建与测试流程掌握了五种单独的技巧就像学会了五种武术招式。但在实战中敌人不会按套路出牌你需要根据对方的反应返回的错误信息、拦截提示等灵活组合这些招式。下面是一个模拟在Bugku类靶场中的实战测试流程。4.1 信息收集与过滤探测首先你需要判断是否存在注入点以及过滤了什么。基础探测提交id1‘和id1‘ and ‘1‘‘1观察回显差异确认注入点。过滤探测逐步提交包含可能被过滤关键词的Payload观察是被拦截如返回“非法参数”、被转义如单引号变成\‘还是被执行返回数据库错误或正常数据。测试union select测试and 11测试or 11测试空格测试--、#测试information_schema4.2 逐步构造绕过Payload假设我们探测到union,select,空格,information_schema被过滤单引号被转义。第一步绕过单引号和空格由于单引号被转义我们尝试用数字型注入或者利用十六进制。先处理空格用/**/代替。尝试Payload1/**/and/**/11和1/**/and/**/12通过回显差异确认and可用且空格绕过成功。第二步绕过union select尝试内联注释1/**/un/*!50001ion*/ sel/*!50001ect*/ 1,2,3如果不行尝试双写假设过滤是替换式1/**/ununionion/**/selselectect/**/1,2,3还不行尝试大小写混合1/**/UnIoN/**/SeLeCt/**/1,2,3或者尝试用%0a代替/**/1%0aUnIoN%0aSeLeCt%0a1,2,3第三步绕过information_schema这是MySQL获取元数据的主要方式。如果被过滤可以尝试使用内联注释from /*!50001information_schema*/.tables使用sys库MySQL 5.7from sys.schema_table_statistics但需要权限。盲猜表名和列名在CTF中常见或者利用polygonid()等函数进行报错注入从错误信息中带出数据从而完全避开查询information_schema。第四步组合最终Payload假设我们通过%0a绕过空格通过内联注释绕过select和information_schema最终构造出获取表名的Payload-1%0aun/*!50001ion*/%0asel/*!50001ect*/%0a1,2,group_concat(table_name)%0afrom%0a/*!50001information_schema*/.tables%0awhere%0atable_schemadatabase()--将这个Payload提交观察是否成功返回数据库表名。4.3 自动化测试与工具辅助手动构造和测试非常耗时。在实际安全测试中我们会借助工具Sqlmap强大的自动化SQL注入工具。它内置了大量的绕过脚本tamper scripts。针对黑名单你可以使用--tamper参数。例如sqlmap -u “http://target.com/page?id1“ --tamperspace2comment,equaltolikespace2comment会将空格替换为/**/equaltolike会将替换为LIKE这些都能用来绕过简单的过滤。Sqlmap的tamper脚本可以链式调用组合多种绕过技术。自定义脚本对于特定的、奇怪的过滤规则可能需要自己编写Python等脚本按照探测到的过滤逻辑自动生成和测试变异Payload。重要提示在真实授权测试中自动化工具可能产生大量请求务必控制速率并在测试时间窗口内进行。在CTF中则鼓励手动探索以加深理解。5. 防御视角与总结反思作为一名安全从业者只会攻击不懂防御是片面的。通过分析这些绕过技术我们能更好地理解如何构建更安全的代码。5.1 黑名单过滤为什么总是失效根本原因在于“枚举不完整”和“上下文缺失”。SQL注入的变体无穷无尽依赖预定义的黑名单注定会漏报。而且过滤系统往往在应用层进行简单的字符串匹配无法理解SQL语句的语义上下文因此很容易被编码、注释等技巧欺骗。5.2 更有效的防御措施是什么使用参数化查询预编译语句这是防止SQL注入的黄金标准。将SQL语句的结构如SELECT * FROM users WHERE id ?与数据如?对应的值分开发送给数据库。数据库会先编译语句结构再将数据代入从根本上杜绝了数据被解释为代码的可能性。无论输入中包含什么union、select都只会被当作纯数据处理。MyBatis中应使用#{}而非${}就是因为#{}实现了参数化查询而${}是危险的字符串拼接。输入验证与白名单对于已知固定格式的输入如数字ID、固定选项使用白名单验证只允许已知好的值比黑名单拒绝拒绝已知坏的值要可靠得多。例如如果id参数应该是数字就直接用intval()或类似函数转为整数。最小权限原则数据库连接账户不应使用root或dbo等高权限账户。应为其分配仅能满足应用功能所需的最小权限如只有特定表的SELECT权限这样即使发生注入危害也能被限制。Web应用防火墙WAFWAF基于更复杂的规则如语义分析、行为分析和不断更新的威胁情报可以提供一层额外的防护。但它不是银弹也可能被绕过应作为纵深防御的一环而非唯一依赖。框架的安全特性使用现代、成熟的开发框架如Spring Security, Laravel等并正确配置其内置的安全机制可以避免很多低级的安全漏洞。回过头看这“五种骚操作”它们本质上是攻击者与防御者在不同认知层面上的博弈。防御者如果只停留在“字符串匹配”的层面那么攻击者总有办法通过编码、变形、利用特性来绕过。而防御者一旦将防线建立在“语义分离”参数化查询和“最小权限”这些更根本的层面上攻击的成本就会急剧上升。对于学习者和测试者而言深入理解这些绕过技术不仅是为了在CTF中得分更是为了在代码审计和渗透测试中能够更准确地评估系统的脆弱点并为其提供真正有效的加固建议。安全是一个持续对抗的过程而理解攻击是做好防御的第一步。在下次遇到一个看似固若金汤的黑名单时不妨从这五个角度想一想或许就能找到那条隐藏的路径。