WAF绕过实战:从SQL注入基础到高级混淆技巧

发布时间:2026/7/4 22:39:22
WAF绕过实战:从SQL注入基础到高级混淆技巧 1. 项目概述当SQL注入遇上WAF这道墙在Web安全渗透测试的实战中SQL注入无疑是每一位安全研究员和渗透测试工程师的“老朋友”。然而随着企业安全意识的提升Web应用防火墙WAF的部署已经变得非常普遍。很多时候你明明通过手工或工具探测到了一个潜在的注入点满怀希望地构造了一个经典的‘ or ‘1’’1结果页面返回的不是期待中的数据而是一个冷冰冰的“403 Forbidden”或者“检测到恶意攻击”。那一刻感觉就像一盆冷水浇头WAF就像一堵高墙横亘在你和目标数据之间。这个项目要探讨的正是如何在这堵墙上找到缝隙甚至打开一扇门。它不仅仅是关于“绕过”更是一场关于理解、对抗与精巧构造的思维博弈。WAF绕过不是魔法它建立在对WAF工作原理的深刻理解以及对目标数据库、应用程序架构的精准判断之上。无论是参加CTF比赛还是在真实授权的渗透测试中掌握一套行之有效的WAF绕过手法都能让你从“脚本小子”进阶为能够解决复杂问题的安全专家。接下来我将结合多年的实战经验为你系统性地拆解WAF的拦截逻辑并分享一系列从基础到高级的绕过技巧与真实案例让你在面对WAF时不再束手无策。2. WAF拦截机制深度解析知己知彼百战不殆在尝试绕过之前我们必须先弄清楚对手是如何工作的。WAF并非铁板一块不同厂商、不同配置的WAF其拦截逻辑和强度天差地别。总的来说我们可以从以下几个层面来理解它的工作机制。2.1 基于特征匹配的规则库这是最基础、也最常见的拦截方式。WAF维护着一个庞大的恶意特征规则库里面包含了成千上万条已知攻击的“指纹”。这些指纹可能是关键字如union,select,insert,update,delete,drop,sleep(),benchmark(),load_file()等SQL语句核心命令。特殊字符与组合如单引号‘、双引号“、注释符--,#,/* */以及‘ or ‘1’’1、‘ and 11这类经典的注入模式。函数与操作符如substring(),mid(),ascii(),and,or,xor,|,等。当HTTP请求包括URL参数、POST数据、Cookie、Headers中的内容与这些特征匹配时WAF会直接拦截或记录告警。这种方式的优点是实现简单、速度快但缺点也非常明显过于死板容易被变形绕过。2.2 基于语义/语法分析的高级检测为了应对简单的特征变形更智能的WAF引入了语义分析引擎。它不再仅仅匹配孤立的字符串而是尝试理解参数值在上下文中的“含义”。SQL词法/语法解析WAF会模拟一个简化的SQL解析器检查参数值是否能被解析成一段“合法”的SQL语句片段。例如它会识别1 and 11是一个逻辑表达式而1 union select 1,2,3是一个完整的查询语句组合。上下文关联分析分析参数名如id,name,search与参数值的关联性。例如对于id参数如果其值中出现了union select这显然不符合一个“ID”应有的格式从而触发告警。规范化与解码WAF会对传入的参数进行多层解码如URL解码、Unicode解码、HTML实体解码等将变形后的攻击载荷还原成“标准形式”再进行特征匹配或语义分析。这专门用于对抗编码绕过。2.3 基于行为与流量的异常检测这类检测不关心单次请求的具体内容而是从宏观行为模式上判断是否为攻击。频率限制在短时间内对同一参数进行大量、微小的变化如盲注时逐位猜解字符会触发频率阈值告警。参数篡改检测监控同一会话中某个参数值的异常变化模式。爬虫/Bot行为识别通过请求头如缺失常见的User-Agent、Accept、请求间隔的规律性等判断请求是否来自自动化工具而非真实浏览器。理解了这三层防御我们的绕过思路也就清晰了针对特征匹配我们进行“变形”针对语义分析我们进行“混淆”针对行为检测我们进行“伪装”。3. 基础与中级绕过手法实战拆解这一部分我们将聚焦于应对特征匹配和初级语义分析的WAF这些手法在实战中遇到频率最高也往往是突破的第一道关口。3.1 关键字变形与混淆核心思路是改变攻击载荷的“样子”但保证数据库引擎最终执行时其“含义”不变。1. 大小写混合这是最简单的一招。许多WAF的规则是大小写敏感的但SQL关键字本身是不区分大小写的。原始载荷union select null, database(), null绕过尝试UnIoN SeLeCt null, database(), null实战心得不要只变一个词最好随机混合大小写。但要注意现在很多云WAF如阿里云、腾讯云WAF的规则集已经覆盖了常见的大小写变形所以这招往往需要结合其他手法使用。2. 内联注释干扰在SQL关键字中间插入数据库特有的注释注释内容会被数据库忽略但可能打断WAF的匹配。MySQL示例id1 union/*!50000select*/ 1,2,3/*!50000select*/是MySQL的特有注释语法表示在MySQL版本大于等于5.0.0时才执行其中的select。WAF可能将其视为一个整体字符串而不会识别出内嵌的select。通用注释拆分id1 and/*xyz*/11在and和11之间插入注释可以拆分特征。你可以尝试不同的注释内容长度和字符。注意事项注释的语法因数据库而异。MySQL支持/* */和--注意后面有个空格Oracle和SQL Server主要用--。用错注释符会导致SQL语法错误。3. 等价替换与特殊符号用功能相同但字符不同的操作符或表示法来替换敏感词。逻辑运算符替换and-or-||not-!空白符替换SQL允许使用多种空白符而WAF可能只匹配空格 。用Tab符%09、换行符%0a、回车符%0d替代空格。示例union%0aselect%091,2,3。在某些场景下甚至可以用括号()来替代空格如union(select(1),2,3)但这需要数据库支持且语法正确。十六进制/字符编码将字符串字面量转换为十六进制。例如‘admin’可以写成0x61646d696eMySQL。select ‘admin’可以尝试变形为select 0x61646d696e这有时能绕过对引号的检测。3.2 参数污染与多重传参这个手法利用了应用程序与WAF处理参数时的差异。当同一个参数名在请求中出现多次时WAF和后端应用可能取不同的值。原理假设请求为?id1id2。一些WAF为了性能可能只检查第一个id1的值。而后端应用程序框架如PHP的$_GET[‘id’]在接收多个同名参数时行为可能不同有的取第一个有的取最后一个有的将其转换为数组。常见的是取最后一个值。实战案例探测到注入点?id1但?id1 and 11被WAF拦截。尝试参数污染?id1id1 and 11。如果WAF检查第一个id1安全而放行后端PHP取最后一个值id1 and 11并执行页面返回正常则绕过成功。进一步利用?id1id1 union select 1,version,3。关键点这种方法高度依赖于后端技术栈。它对PHPApache的环境比较有效对于JSP、.NET或PythonDjango/Flask等框架需要具体测试其参数解析行为。在Burp Suite中你可以通过“Send to Intruder”并设置id为两个有效载荷位置一个放普通值一个放注入载荷来快速测试。3.3 编码与双重编码绕过当WAF对输入进行解码检查时我们可以通过多层编码来制造“误会”。1. URL编码单引号‘的URL编码是%27。空格 的URL编码是%20。直接发送?id1%20and%2011可能被WAF解码后识别。2. 双重URL编码这是更有效的技巧。我们对%27再次进行URL编码%编码为%25所以%27双重编码后是%2527。请求?id1%2527WAF处理部分WAF只做一次URL解码看到的是id1%27认为%27只是一个普通的百分号编码字符未识别为单引号于是放行。后端处理应用程序或数据库连接层通常会进行完整的解码最终得到id1‘从而触发SQL错误确认注入点。3. Unicode编码将字符转换为Unicode形式。例如单引号‘可以表示为%u0027或%u02b9变体。示例?id1%u0027。如果WAF没有对Unicode进行规范化解码就可能绕过。重要提醒编码绕过的成功率取决于WAF解码层的深度和顺序。现代云WAF通常有强大的规范化引擎会尝试多种解码方式。因此编码绕过往往需要与其它手法结合并不断变换尝试。4. 高级绕过手法与复杂场景应对当面对具备语义分析能力的中高级WAF时我们需要更精巧的Payload旨在构造出“对人类和数据库解析器来说是一段有意义的SQL但对WAF的解析器来说是混乱或无害的”语句。4.1 语义混淆与非常规语法1. 使用生僻或替代函数/语法替代注释符除了--和#试试;%00Null字节截断在某些环境下有效。替代空格除了空白符可以用/*!*/MySQL、/**/或甚至数学运算?id1and11中的在某些上下文里会被解释为空格。非常规字符串连接MySQL:‘a’ ‘dmin’中间有空格会被连接为‘admin’。Oracle:‘a’||’dmin’。这可以用于拆分被拦截的敏感字符串。2. 利用数据库特性与注释技巧MySQL版本注释/*!50001select*/ 1。这个注释内的语句只在MySQL版本5.0.1时执行。WAF可能不解析版本条件而数据库会。你可以用/*!select*/这种无版本号的它总是执行。内联注释嵌套拆分uni/*任意字符*/on sel/*任意字符*/ect。在关键字中间插入注释是拆分特征的有效手段。注释内容可以随机化以绕过简单的模式匹配。3. 逻辑混淆构造复杂的条件表达式将恶意代码“隐藏”在正常的逻辑判断中。示例?id1 and (select 1 from (select count(*),concat((select database()), floor(rand(0)*2))x from information_schema.tables group by x)a)这个Payload用于MySQL报错注入。它包含了嵌套子查询、聚合函数、随机数等复杂元素WAF的语义分析器可能难以准确判定其恶意意图尤其是当它被拆分成多个参数或与其他混淆手法结合时。4.2 针对特定数据库的进阶技巧1. MySQL利用/*!*/与/*!50000*/如前所述这是MySQL的利器。你可以将整个注入语句包裹起来/*!union*/ /*!select*/ 1,2,3。更高级的用法是结合字符串连接/*!50000concat*/(‘sel’‘ect’)。2. SQL Server利用参数化查询的误解在某些不规范的代码中即使使用了参数化查询如EXEC sp_executesql N‘SELECT * FROM users WHERE id id‘ N‘id int‘ id input如果开发者错误地将用户输入直接拼接进SQL命令字符串而非参数值依然存在注入可能。此时可以利用SQL Server的字符串连接和动态执行。示例输入input 1; EXEC(‘SELECT * FROM sysusers’) --。关键在于闭合原语句并开始新的执行。3. PostgreSQL利用类型转换与操作符重载PostgreSQL有丰富的操作符和类型系统。例如利用::进行类型转换或使用一些不常见的操作符来构造Payload。示例?id1 and ‘1‘::int1会触发类型转换错误可用于报错注入。或者利用||操作符进行字符串连接和混淆。4.3 绕过预编译语句非常规场景真正的预编译语句Prepared Statements使用占位符?并将用户输入始终作为参数处理从理论上讲是无法绕过的。我们这里讨论的是“伪预编译”或开发者的错误实现。场景应用程序使用了类似预编译的语法但拼接方式不安全。错误示例Java伪代码String sql “SELECT * FROM products WHERE category ? AND id “ userInput; // userInput 被直接拼接 PreparedStatement stmt connection.prepareStatement(sql); stmt.setString(1, userCategory); // 只有category被参数化利用userInput参数未被参数化。我们可以注入1 union select 1,2,3。虽然WAF可能检查整个SQL字符串但由于union select出现在一个“参数”位置尽管是拼接的可能绕过某些简单的检测。核心这类绕过的本质不是绕过预编译本身而是利用了开发漏洞和WAF对“参数化查询”上下文的误判。在实战中需要仔细分析报错信息和应用逻辑来寻找这类缝隙。5. 盲注场景下的WAF绕过与效率优化盲注布尔盲注、时间盲注是WAF防护下的常见注入方式因为它不直接回显数据攻击特征相对隐蔽。但盲注本身效率低下且频繁的请求极易触发WAF的频率限制。因此优化至关重要。5.1 降低特征明显度替换敏感函数时间盲注常用的sleep()或benchmark()函数是WAF的重点监控对象。替代方案MySQL使用GET_LOCK(‘key‘ 5)制造延迟。SELECT GET_LOCK(‘test‘ 5)会尝试获取一个名为‘test’的锁持续5秒。这比sleep()更隐蔽。PostgreSQL使用pg_sleep(5)是标准做法但也可尝试generate_series(1,1000000)制造计算延迟。通用延迟技巧构造一个产生大量计算的条件。例如?id1 and (select count(*) from information_schema.columns a information_schema.columns b information_schema.columns c)0。这个笛卡尔积查询会消耗大量时间通过响应时间差异来判断条件真假。5.2 提升猜解效率减少请求次数传统盲注逐字符比较substr(database()1,1)‘a‘一个7位长度的数据库名就需要几十上百次请求。1. 基于ASCII码的二分法/位运算二分法判断字符的ASCII码范围。例如先判断ascii(substr(database()1,1)) 100根据结果将范围减半通常7-8次请求就能确定一个字符。位运算更高效一次请求提取多位信息。Payload示例?id1 and (ascii(substr((select database())1,1)) 4 15) 6解释 4将ASCII码右移4位相当于取高4位。 15二进制1111确保只取这4位。这个Payload一次性能告诉我们目标字符ASCII码的高4位是什么范围0-15。然后再用一次请求取低4位。这样最多2次请求就能确定一个字符效率提升数倍。2. 利用正则表达式regexprlikeMySQL的regexp或rlike可以一次匹配一个模式而不是一个字符。Payload示例?id1 and (select database() regexp ‘^[a-m]‘)。判断数据库名的第一个字符是否在a到m之间。通过精心设计正则表达式可以快速缩小范围。5.3 对抗频率限制请求节奏控制与伪装即使Payload能绕过特征检测高频请求也会被行为规则封杀。设置随机延迟在你的自动化脚本如Python的sqlmap tamper脚本或自定义脚本中在每次请求之间加入随机等待时间如random.uniform(1 3)秒模拟人类操作。切换User-Agent与Referer在请求头中随机使用常见的浏览器UA和合理的Referer让请求看起来更像正常的页面浏览。利用Session/Cookie如果目标网站有会话机制确保你的脚本携带有效的Cookie避免被识别为无状态攻击。分布式代理IP池这是应对IP封锁的终极方案。通过轮换多个代理IP来发送请求将流量分散。但请注意在授权的渗透测试中使用代理IP池需要事先获得客户同意并明确测试源IP范围。6. 实战案例综合剖析让我们通过一个虚构但融合了多种真实场景的案例将上述手法串联起来。目标某电商网站商品详情页URL为http://target.com/product?id123。初步探测id123‘返回数据库错误MySQL但id123‘ and ‘1‘‘1被WAF拦截返回403。第一步信息收集与基础绕过判断WAF类型拦截页面显示“阿里云盾”。这通常意味着它具备较强的语义分析能力。尝试大小写混合id123‘ AnD ‘1‘‘1 依然403。尝试内联注释id123‘ /*abc*/and/*def*/ ‘1‘‘1 成功绕过页面返回正常。说明WAF对注释内的干扰字符处理不严谨。确认注入类型id123‘ /*abc*/and/*def*/ ‘1‘‘2 页面无内容。确认为布尔盲注。第二步构造低特征探测Payload直接使用union select很可能再次触发WAF。我们先尝试用更隐蔽的方式获取信息。获取当前用户利用MySQL的user()函数和注释拆分。Payload:id123‘ /*abc*/and/*def*/ substring(user()1,1)‘r‘通过布尔盲注逐位猜解出用户为rootlocalhost。获取数据库名使用database()函数。Payload:id123‘ /*abc*/and/*def*/ ascii(substring(database()1,1)) 100为了提高效率我们直接编写一个Python脚本采用位运算二分法进行自动化猜解。脚本中每个请求都包含随机注释内容 (/* 随机字符串 */) 并设置了1-2秒的随机延迟。第三步尝试获取表名遇到新拦截当我们尝试查询information_schema.tables时Payloadid123‘ /*abc*/and/*def*/ (select count(*) from information_schema.tables) 0被拦截。WAF可能识别了information_schema这个敏感词。绕过方案等价替换MySQL中information_schema.tables可以用反引号或十六进制编码尝试。但反引号可能也被过滤。使用sys.schema_table_statisticsMySQL 5.6这是一个可以用来替代information_schema获取表信息的系统视图常被WAF规则遗漏。Payload:id123‘ /*abc*/and/*def*/ (select count(*) from sys.schema_table_statistics where table_schemadatabase()) 5成功执行未拦截。进一步获取表名通过布尔盲注结合substring和limit子句从sys.schema_table_statistics中猜解表名。Payload需要精心构造避免出现union、select column_name等明显特征而是用select table_name并嵌套在条件中。第四步最终数据获取获取到表名例如users后需要获取列名和数据。此时可以尝试利用报错注入如果WAF对报错函数如updatexml()extractvalue()的检测不严可以尝试。例如id123‘ and updatexml(1 concat(0x7e (select column_name from information_schema.columns where table_name‘users‘ limit 0,1)) 1)‘。注意这里我们又用回了information_schema但将其放在updatexml函数内部有时能绕过。无列名注入如果无法获取列名可以使用无列名注入技术。例如通过子查询和别名来访问数据id123‘ /*abc*/and/*def*/ (select1from (select 123 union select * from users)a limit 11)‘target_value‘。这需要知道表的列数并且构造复杂。案例总结这个案例展示了混合使用多种手法的过程从注释拆分突破第一层检测到使用替代系统视图绕过关键字过滤再到利用报错函数或高级查询技巧获取数据。整个过程需要耐心、对数据库特性的深入了解以及灵活的Payload构造能力。7. 自动化工具辅助与手工艺术虽然手工构造Payload是精髓但合理利用工具能极大提升效率。1. sqlmap的Tamper脚本sqlmap自带了大量tamper脚本位于tamper/目录专门用于绕过WAF。它们实现了我们讨论的很多技巧。space2comment.py用注释替换空格。between.py用between替换大于号。randomcase.py随机大小写。charencode.pyURL编码。apostrophemask.py用UTF-8全角字符替换单引号。使用方法sqlmap -u “http://target.com/product?id123“ --tamperspace2commentrandomcase自定义tamper你可以编写自己的tamper脚本实现更特殊的绕过逻辑。这是高级玩家的必备技能。2. 手工测试的思维流程工具不是万能的尤其在面对定制化WAF时。手工测试的流程可以概括为探测用单引号、双引号等触发错误判断注入点和数据库类型。试探使用极简的布尔条件如and 11and 12配合最基础的绕过手法如注释拆分确认注入是否可用。识别通过不同Payload的拦截情况推断WAF可能过滤的关键字、函数或符号。构造根据推断设计绕过方案。优先使用等价替换、编码、注释。无效则尝试更复杂的混淆和非常规语法。自动化一旦找到可用的Payload模式将其编写成脚本或配置到sqlmap的tamper中进行自动化数据提取。3. 最重要的心法保持耐心与创造力WAF绕过没有一成不变的银弹。今天有效的方法明天可能就被规则覆盖。核心是保持耐心像解谜一样不断尝试、观察、调整。多关注安全社区的最新绕过技巧理解其原理而非死记硬背Payload。真正的能力是在面对一堵新墙时能自己找出那块松动的砖。