又是一个绕WAF的话题。说实话,“SQL注入绕WAF”这类总结网上并不少,但很多都是把payload罗列一遍,看完容易忘,真正遇到拦的时候还是不知道从哪儿下手。这篇我把自己的思路整理出来,从WAF到底怎么“想”的开始讲,再沿着不同绕过路径一步步拆,最后聊点实战排查习惯。适合刚接触安全测试的人做入门框架,也适合写过一阵子payload但总觉得靠运气的人把思路捋直。
WAF不是神,它只是一段逻辑。这段逻辑和后端数据库对同一段请求的理解必然存在差异,绕过的本质就是抓住这个差异。理解了这一点,你看到的就不再是“一堆奇技淫巧”,而是一条清晰的规避路径。
1. 项目概述:为什么WAF绕过是安全测试的“试金石”
WAF(Web应用防火墙)本质上是放在用户与业务服务器之间的一道过滤层。它的职责是检查流入的HTTP请求,按预设规则匹配出可疑SQL注入、XSS、命令执行等攻击载荷,然后拦截或告警。但问题在于,WAF的判定完全依赖于它的“理解能力”,而任何解析器、规则库和特征库都是有限的。只要后端Web容器、中间件或数据库驱动的解析逻辑与WAF存在差异,攻击代码就有机会穿过过滤层,直达数据库。
很多人以为绕WAF就是记几个“加密变体”“注释符”的套路,比如把union select改写成union/**/select、UNIon SElect,或者在参数中间插一堆%0a。这些技巧有用,但它们只是表层。真正系统的思路要先搞清楚一件事:WAF看到请求里的什么内容才触发拦截。不同WAF的检测逻辑差异极大,有的靠正则匹配关键词,有的做语义分析,有的结合行为建模。只有摸清目标的检测机制,才能针对性构造“它看不懂但数据库能看懂”的语句。
这篇文章我不打算只给你一份payload速查表,而是按我自己的项目经验,把绕WAF拆成三条主线:协议层差异、数据库方言利用、语义层欺骗。顺着这三条线走,能覆盖大多数授权测试场景。如果你是带着防御目的来读的,后半部分我也给出了加固思路,了解攻方手法本来就是做好防御的前提。
2. 核心认知:WAF的检测机制与绕过的底层逻辑
1.1 解析层与规整化的漏洞在哪里
WAF第一件要做的事,其实和后端数据库一样:先拿到HTTP请求,然后把它“读出来”。这里有个极其关键的点:WAF和数据库看到的请求字节,是否完全一致。如果两者读出来的内容不一样,就有绕过空间。WAF将请求体按表单格式解析、按JSON解析、按XML解析,或者干脆只读URI部分(比如只查GET /xxx.php?id=1),这就有区别了。
不少WAF的默认配置只对application/x-www-form-urlencoded做严格规则匹配,对multipart/form-data里的边界处理就相对宽松。攻击者把参数挪到multipart的filename或name里,很多老旧规则就抓瞎了。再比如Chunked Transfer Encoding,有些WAF在网关层不会去解包分块传输的内容,直接把原始字节丢给后端;而后端容器(如Tomcat、PHP)会自动把分块重组回去。两边解析到的参数值不同,是这类绕过最底层的成因之一。
另一个基础是规整化(Normalization)。测试时输入/%2527,WAF可能先做一层URL解码变成/%27,然后再做第二层解码变成/';但如果WAF只做单层解码,而数据库中间层又存在二次解码,那%2527在WAF眼里是%27,在数据库眼里却是单引号'。只要两边解码策略不一致,过滤就等于是个筛子。所以做绕过测试前,第一件事就是摸清WAF的“解码偏好”:到底是先URL解码、再查规则,还是先规整路径、再去重参数。这些细节决定了后续用哪类手法最省事。
2.2 规则匹配层:黑名单、白名单与语义分析的背后逻辑
老一代WAF基本都是“黑名单+正则匹配”,核心就是看有没有出现select|union|and|or|sleep这类敏感词,有没有出现注释符、引号、百分号等异常字符。这种匹配的问题在于:它很难理解“上下文”。比如and可能出现在正常搜索关键词brand里,所以规则引擎通常不会直接匹配整词,而是匹配边界(符号、空格等)。这就给了绕过空间:只要让敏感词“看起来不像敏感词”,正则就匹配不到。
新一代WAF更倾向于“语义分析”,也就是模仿数据库的词法/语法解析器,先对参数值做分词、再做结构判断。它能识别出union select是一个完整的SQL片段,而不只是两个单词。换句话说,判断维度不再是“有没有坏词”,而是“这句话像不像一条SQL语句”。
这带来一个关键结论:正则绕过和语义绕过的思路完全不同。对付正则,核心是“让敏感词的字符序列断裂”,比如用注释符、换行、内联注释;对付语义分析,核心是“让语法结构变模糊但最终在数据库端仍是合法SQL”,比如利用数据库自身的容错机制,像MySQL的/*!50000union*/这种条件注释,WAF的词法分析器如果没实现MySQL特有语法,就会把它当成普通注释丢掉,而MySQL执行时却会展开执行。我在实际测试中发现,很多号称“语义识别”的WAF对MySQL基础语法支持度高,但对某些冷门函数、非标准写法(比如union distinct、order by里的表达式)支持一般,原因就是它们内部并没有完整复刻数据库优化器的所有合法性判断。
还有一点得说清楚:白名单模式。WAF如果配置了“只允许数字参数”,那所有字符串输入都会被拦。但若业务本身需求是id可以传字符串,那白名单往往留了别的路,比如允许id以字母开头配合短横线、下划线等。这些白名单的隐含允许项,就是绕过规则的“躲藏点”。总而言之,规则匹配的方式千千万,本质是拿“WAF的规则集”和“后端的SQL解析集”两个集合取差集,差集越大,能打的空间就越多。
3. 协议层与解析差异:最常见的“免杀”入口
3.1 HTTP参数污染(HPP):多个同名参数带来的歧义
HTTP规范其实允许?id=1&id=2这样同时传两个id。WAF在解析时通常会取第一个或最后一个,而Web中间件在处理时又可能是拼接列表、取第一个、取最后一个,甚至取最后一个非空值。只要WAF和后端对“同名多参数”的取舍不一致,就能越过WAF直接打到数据库。
举个例子:如果WAF取第一个id=1进行规则检查,而后端框架(比如某些老版本ASP.NET或特定PHP应用)取第二个id=1 union select 1,2,3,那攻击payload就完全绕过过滤规则了。反过来也一样,WAF取第二个做检查,后端取第一个,那就在第一个参数里注入。
HPP的变形还有很多:有把参数放在不同数据段里的,比如POST body里放一个id,GET query里再放一个同名id;也有用分号、逗号把参数名做拼接的,像id;name=1在某些服务下会被解释成id=1和name=...。我用这种手法测过不少授权站点,成功率取决于框架的具体实现:Tomcat、Apache、PHP-FPM、Node.js的行为都不完全一样。所以如果项目里遇到WAF拦截,第一反应别急着换payload,先花10分钟做一个“同名参数优先级”探测,往往能找到白给的口子。
3.2 编码穿越:双重编码、Unicode变换、畸形UTF-8
编码绕过的核心逻辑前面提了一句:WAF解码一次,后端解码两次或者更多次。比如%2527经过WAF层可能变成%27,但后端Web容器做一次URL解码变成',同时数据库驱动再做一层Unicode转义或编码转换,最终就拿到了单引号。测试时要重点看WAF到底对参数值做了几次解码,老版本WAF对%u0027这类Unicode编码也经常直接透传,而后端如果恰好是宽字符集环境,就可能被解释成单引号。
还有一种畸形编码:%c0%27,属于无效UTF-8序列。在严格模式下数据库驱动会报错拒绝,但某些宽松配置的数据库连接会把%c0转成0xc0之后加上0x27,最终拼出一个合法字符,等同于注入。这类手法对字符集设置(如连接字符集gbk)很敏感,每条路由都要实测。编码绕过永远优先于语法变形,毕竟结构完全不变,只是“换了层皮”,对判断能力不强的WAF极有效。
需要特别提醒的是,测试编码绕过时要带着“编码链路”的思维:浏览器到CDN/WAF、再到Web中间件、再到数据库驱动、最后到数据库字符集。任何一层的编码假设不一致,都可能导致绕过。我见过一个案例,安全测试人员花了两天时间在猜测WAF规则,结果最后发现只是中间件和WAF对+号处理不一致——WAF把+当空格,数据库把+当加号,于是一条简单的and 1=1就绕过了所有过滤。
3.3 分块传输与multipart:绕过内容型规则
针对POST类的SQL注入,目前很多WAF的规则“默认”放在参数值上,但如果请求本身就是multipart/form-data,部分WAF只解析第一个name字段,或者对filename字段不设置完整规则,于是payload可以塞进filename="1'.sql"这类位置。后端拿到文件名后如果拼进SQL,等于变相绕过了WAF对常规参数值的检控。
分块传输(Transfer-Encoding: chunked)也有类似效果。WAF通常先做TCP流重组再分析HTTP内容,如果它在重组后没有正确解析chunked格式,直接就会漏掉后续chunk中的注入载荷;后端服务器则会在读取HTTP body时自动解包,把多个chunk拼接起来交给应用层。由于两边对chunk边界处理逻辑不同,完整payload往往能分成数段,每段都不触发WAF规则,合起来却是危险语句。
实操时需要关注这几个点:请求头里是否允许自定义Transfer-Encoding;Content-Type是否支持multipart/form-data;后端是否完全忽略body中的其他字段只认某一个字段。这类绕过属于“协议级绕过”,与具体SQL语法无关,所以通用性很强。我用chunked分块打通过一次授权环境,当时WAF只检测Content-Length字段,完全没管分块内容,请求体和正常状态码都直接透传了。当然,这类环境现在越来越少,云WAF大多会把这些协议细节补全,但在内网自建WAF或老旧网关设备上,仍然值得尝试。
4. 语法绕过技术:从基础到高阶的完整手账
4.1 空格与注释符的替代:从/**/到%0a再到!
在所有SQL注入绕过技术里,空格过滤是出现频率最高的一关。WAF规则往往会要求union select必须用空格分隔,否则就算“可疑”;因此我们要做的也很直白:把空格换成数据库能接受的其他分隔符。MySQL里常见的替代有/**/、%0a(换行)、%09(Tab)、%0b、%0c、%0d,甚至可以用括号来直接改变语法结构,比如(select(1))这种写法完全不需要空格。
不同数据库的空格替代方式有差异。Oracle支持/**/和换行,但不支持%0b;MSSQL对%0a、%0b、%0c的支持广泛;PostgreSQL则能容纳多种空白字符。实际测试时不要一次性把全部空格替换成同一种符,建议逐字符验证:先测试union/**/select,再看union%09select,再试union%0aselect,逐一确定哪些分隔符在当前WAF规则集里被放行。
注释符本身也是个工具箱。/**/在多个地方可代替空格,还能把关键字“切成两半”,比如sel/**/ect。MySQL里有个特殊的存在:/*!50000union*/这种可执行注释,数据库会把它当成union关键字执行。很多WAF并不认识这种注释,规则集里只过滤了普通/* */注释,于是绕过极其稳定。CTF靶场里这种技巧很常见,但真正到了演练环境,因为多数WAF会专门处理MySQL条件注释,所以我通常会观察不同版本的WAF规则是否覆盖了/*!50000这类前缀,再决定是否沿用。
4.2 大小写混合与关键字的等价变换
大小写混合是绕正则最基础的手法,因为老式WAF规则集常常只检查union、select,却忽略大小写变体。推荐的做法是:Union SeLect、uNiOn sElEcT。但这招对基于语义分析的WAF基本无效,因为语义分析在分词前会先把所有字符转成小写,再识别SQL结构。所以它只能作为“试探”用途,如果直接过了,就说明WAF规则层做得很糙;如果被拦截,就考虑关键字变形。
关键字的等价变换思路比大小写更高级。AND可以换成&&,OR可以换成||,NOT可以换成!;等于号=可以换LIKE、IN、REGEXP、STRCMP();SELECT <列> FROM可以被各种函数替代,比如SELECT (SELECT ...),或者用VALUES、TABLE语句。这些等价变换真正有价值的地方在于,它们能把整个语句的“词汇表”替换成WAF规则集里不存在的函数,从而绕正则匹配。但难点在于要让等价写法在目标数据库的版本里真正可用——比如MySQL 8.0对||的语义默认是逻辑或,但在某些SQL模式里可能变成字符串拼接,这种差异直接影响语句结果。
4.3 字符串处理技巧:从CHAR()到CONCAT()再到N''
当WAF拦截了字符串字面量(比如'admin'或'1'='1'),或者完全过滤了引号,后面就很难直接输入字符串常量。常见替代方案是把字符串拆成ASCII码,再用函数生成字符串:MySQL里用CHAR(97,100,109,105,110)产生'admin';MSSQL用CHAR(97)+CHAR(100)+...;Oracle用CHR()函数。这种方式对需要对比字符串、输出字符串、拼接表名/列名的场景很好用,也不容易触发关键字正则。
还有一种做法是用CONCAT系列函数动态构造字符串,比如CONCAT(CHAR(115),CHAR(101),'lect')。或者使用十六进制字面量,MySQL支持0x61646D696E直接代表字符串admin(十六进制值只要合法即可传给查询参数),这在过滤引号的环境里非常有效。需要注意:十六进制字面量在MySQL里默认是二进制串,如果目标查询需要UTF-8文本,可能需要配合CONVERT()转换,否则类型不匹配会报错;在PostgreSQL里不能用0x直接表示字符串,得用E'...'转义字符串或U&'...'。
如果目标数据库是MSSQL,还会发现N'...'(Unicode字符串前缀)也能派上用场。比如N'1'='1',有些WAF只匹配'1'='1'这种ASCII模式,看到N前缀后直接跳过。字符串处理这块很杂,但逻辑清晰:只要让字符串常量“不以明文出现”,同时保证数据库端能还原语义,就能躲过绝大多数黑名单。
4.4 数据库方言差异:MySQL、MSSQL、Oracle、PostgreSQL的绕法差异
不同数据库的SQL方言差异非常大,一套payload写好了不代表能跨库通用。这是“绕WAF”里最容易踩坑的地方:很多测试人员只学MySQL,换个目标数据库就测不出来。下面把我常用的方向整理一下。
MySQL:内联注释/*!50000*/、反引号标识符、--注释需要空格结尾、#注释符、BENCHMARK()、SLEEP()、EXTRACTVALUE()、UPDATEXML()、GROUP_CONCAT()。MySQL里用1真的代表布尔真,用0代表假;@变量、:=赋值也可以用来绕过部分关键字。
MSSQL:WAITFOR DELAY '0:0:5'用于时间盲注、STUFF()和FOR XML PATH做字符串拼接、OPENROWSET()读文件、EXEC()动态执行。MSSQL支持批量语句分隔,可以直接在一条请求里用分号追加exec xp_cmdshell;WAF往往对单条语句做了完整规则匹配,但对多条混在一起、后面跟着“不常见关键字”的payload反而检查不足。
Oracle:字符串用||拼接、DUAL表、ROWNUM分页、SYS_CONTEXT()获取信息。Oracle对注释支持--和/* */,不识别#;分号在JDBC里通常是分隔多条语句的,但在OCI模式可能被忽略。用UTL_HTTP.REQUEST()做带外数据回传的场景也很经典。
PostgreSQL:pg_sleep(5)、||字符串拼接、::类型转换(例如1::int)、ILIKE大小写不敏感匹配、CASE WHEN表达式、array_agg()聚合。PostgreSQL对引号和标识符的处理比较严格,但E'\x27'这类转义字符串在过滤单引号时是很好的绕过点。
这些差异决定了我们要先做“版本与类型的指纹识别”,再选payload。如果不知道目标用的是什么数据库,可以先测报错信息、时间延迟特征、注释符兼容性,把数据库类型摸清后再套用对应技巧。绕WAF不要一上来就背payload模板,而是先建立“数据库画像”,再定制语句。
5. 高级注入场景:盲注与报错注入中的绕过实战
5.1 布尔盲注与时间盲注的组合策略
布尔盲注(按页面差异判断真/假)和时间盲注(按响应延迟判断真/假)是SQL注入中输出不明显时的常用手段。但WAF对这两种方式的规则拦截也不一样。很多WAF会专门检测if(、sleep(、benchmark(、waitfor delay这类明显的时间延迟函数。于是绕过思路可以转向让“延迟或不延迟”的判断逻辑不包含敏感函数。
时间盲注的替代方案很多。MySQL里可以构造BENCHMARK(10000000,MD5(1)),也可以利用大表笛卡尔积制造延迟,比如SELECT count(*) FROM information_schema.columns A, information_schema.columns B这种重型查询。MSSQL里不一定非用WAITFOR DELAY,可以DECLARE @x int; SELECT @x = COUNT(*) FROM sys.all_objects A CROSS JOIN sys.all_objects B;,让SQL执行引擎自己耗时。PostgreSQL可用pg_sleep(),或用一个很大的generate_series()交叉连接拉高CPU时间。
布尔盲注的过滤点则通常在and、or、if、case when。可以尝试把条件逻辑“拆开”:用1 LIKE 1代替1=1,用IN嵌套代替=,用STRCMP()返回0/1,用(SELECT 1 FROM dual WHERE 1=1)这种子查询返回值判断。还要注意WAF对请求长度的限制:盲注payload一旦过长就会被拦,所以要注意精简,能用一个字符就别用三个,能用BETWEEN就别写>=和<=。
5.2 报错注入在WAF场景下的过滤变形
报错注入是最“吵”的一种方式,因为服务器会返回SQL报错内容,里面往往包含我们想要的数据库信息。像MySQL里的extractvalue(1,concat(0x7e,(select version())))、updatexml(1,concat(0x7e,(select user())),1),在WAF规则里这两个函数名基本都在黑名单上。绕过的价值在于:如果能换成等价函数,就能快速拿到数据。
一个常见思路是找WAF没覆盖的功能函数。MySQL里报错函数不止两个,还有GTID_SUBSET()、GTID_SUBTRACT()、NAME_CONST();PostgreSQL里的CAST(... AS int)、to_number('x')都会生成明确报错。MSSQL里如果存储过程错误信息被回显,也可以用CTE配合RAISERROR制造报错。关键是先探测哪些函数能触发报错,再选WAF规则里漏掉的那个。
还可以对报错注入的“载荷结构”做变形。比如extractvalue(1,concat(0x7e,(select version())))可以写成extractvalue(1,concat_ws(0x7e,version())),多换一个函数名就能过不少黑名单;或者把concat换掉,用make_set、lpad等函数制造类似效果。报错注入的绕过本质是“换函数+换参数拼接方式”,变形空间很大,但要一个个试。
5.3 绕过“拦截union select”的关键字规则
union select属于WAF的“重灾区”规则,几乎任何正规WAF都会检查这两个词同时出现。于是核心问题变成:如何在语义不变的前提下,让“union”和“select”看起来不像它们自己。
第一个思路是改变结构:union后面不一定非要直接跟select,可以先来一个子查询,比如union (select 1,2,3)。或者在不同位置穿插注释、括号、子查询:union all select(部分WAF只匹配union select,没匹配union all select);union distinct select也常被漏掉。MySQL里还能用union(select...),括号后跟select的写法非常规,却能被识别,很多老正则棋差一着。
第二个思路是去掉union关键字本身,改用UNION之外的多句拼接能力。MySQL能用;分隔多条语句(不是每条连接都允许,取决于驱动),MSSQL则比较常见。还有利用子查询嵌套,比如(select 1) union (select 2),和上面插括号的方式可以混用。
第三个思路是从信息收集角度入手:如果后端返回结果里能看到所有列,可以优先尝试只替换select关键字本身,比如SeLeCt、/*!50000select*/;如果WAF的正则是大小写不敏感的,则继续换成等价关键字,用HANDLER读取数据(MySQL里HANDLER table OPEN、HANDLER table READ FIRST可以替代部分SELECT功能),或Oracle里用TABLE集合函数做子查询。
6. 实战流程与排查技巧:从探测到稳定利用的完整基线
6.1 第一步:摸清WAF的“脾气”(版本、类型、规则强度)
很多测试新手一上来就对着参数猛打payload,这是低效的。正确的顺序是先把目标磨清楚:WAF是云WAF还是硬件盒子,规则库是强校验还是弱校验,拦截粒度是“整包丢弃”还是“返回403/重置”,拦截通知是“特征关键字告警”还是“行为基线告警”。这些信息可以通过几次无害探测获取,比如提交id=1'看响应状态码,提交id=1 union select看是否被拦截,提交id=1%27看编码是否生效,提交id=1(正常参数)看有无JS挑战或指纹。
每次探测都要做好记录。我习惯用一个简单表格记录“输入构造→响应状态码→页面关键词→是否被拦”,几轮下来基本能判断当前WAF的拦截风格。有些云WAF会在检测到恶意请求时返回一段专门的人机验证页面,有些老设备则直接丢包或RST,这从TCP层面就能观察出来,所以有条件的话抓包看TCP握手和连接重置状态也很有用。规则强度则可以通过“同时提交多个敏感词”和“单个敏感词”的响应差异来辨认:如果单个select就触发告警,说明规则很敏锐;如果必须拼成完整语句才触发,那说明有语义分析或更宽泛的评分机制。
6.2 第二步:通过“无害探测”定位过滤规则
“无害探测”并不是随便试,而是有意识地收集规则画像。最推荐的流程是:先测单个常见关键字(select、union、and),再测编码变换(%27、%2527、%0a),再测注释符(/**/、/*!50000*/、--、#),接着测函数名(sleep、extractvalue),最后测协议畸形(multipart、chunked)。每一次都以“是否返回拦截状态码”作为观察点,记录哪些组合被放行、那些组合被拦截,逐渐勾勒出当前的规则允许边界。
这里有个重要的“隔离变量”原则:一次只改变一个变量。比如测试union,就单独提交id=1/**/union/**/select;如果这一步被拦,第一时间怀疑空格替代被拦,而不是整个payload都有问题。要是想判断WAF是不是按“行为”来拦截,还可以在正常请求里故意多插入几个/*注释*/,看看WAF会不会单独拦截注释符过多的情况——很多WAF确实有“注释长度过大”或“注释次数过多”的防绕过策略。
6.3 参数点分析与数据库指纹识别
参数点分析要回答三个问题:输入点在哪里(GET、POST、Cookie、Header、UploadFileName)?输入点最终拼进SQL的哪个位置(数值直接拼接、字符串带引号、like子句、order by后面)?数据库类型和对应版本是什么?这些信息决定了后续payload的写法。比如数值型注入可以直接id=1 and 1=1,字符串型注入要先闭合引号,order by后面无法用union select,只能使用基于条件的报错或盲注。数据库指纹可以用时间延迟(SLEEP、WAITFOR DELAY、pg_sleep)、报错信息、特定注释(/*!50000仅MySQL支持)来判断。
一个容易被忽略的点是:WAF拦截的位置是整个请求,而不是只有参数名。有些接口的Cookie、User-Agent甚至Referer会被拼进SQL,很多WAF对头部字段的检测强度低于参数,这同样是可利用的输入点。参数点分析时,要把所有可控制的输入都列出来,逐个测试它们是否进入SQL,再决定优先级。
6.4 动态调整载荷模块化
当绕过思路比较多时,建议把payload做成一个“模块化模板”,而不是手写一堆乱码。我的常见做法是:基础探测payload、闭合payload、布尔盲注payload、时间盲注payload、报错注入payload、带外注入payload。每个模板里保留占位符(注入位置、猜测的表名字段、使用的函数名),针对不同WAF和数据库再替换相应变量。这样能显著减少每次测试的重复劳动,也可以方便做自动化脚本。模块化还有个好处:当WAF升级规则时,你只需要替换全局的函数列表或关键字表达式,而不必重新手写整套payload。
6.5 常见排查问题与处理速查表
下面是测试过程中我反复踩过的一些坑,整理成速查表,实用性很高。
| 现象 | 常见原因 | 排查方向 |
|---|---|---|
union select被拦,但/union/select可通过 | WAF规则未正则兼容注释替换 | 尝试%0a、%09、!等空位替代 |
and 1=1被拦,&& 1=1可通过 | 规则只匹配and单关键词 | 换成运算符等价写法 |
sleep(5)被拦,benchmark可通过 | 规则集针对特定敏感函数 | 换同类延迟函数或利用重型查询键表 |
| 全部payload都返回403 | WAF开启严格模式,或规则匹配很前沿 | 优先考虑协议类绕过(HPP、分块、multipart) |
| GET注入全被拦,POST能过 | WAF只检测GET参数 | 尝试跨方法传输参数(GET->POST POST->GET) |
| 数据库中能执行,但页面无回显 | 后端禁用了错误输出或语句结果不回显 | 改用盲注或带外通道 |
| WAF对同一payload时拦时放 | 存在CDN或多个WAF节点配置不一致 | 换地域节点/固定IP重试,确认一致性 |
6.6 遇到“语法报错”不可怕,关键是观察报错内容
实际测试时,payload经常把网站干出500报错,页面崩溃,或者直接跳转404。别慌,我们依然可以从报错时间、响应码、页面结构中获得重要线索:如果提交id=1 and 1=1页面正常,而id=1 and 1=2页面差异明显,说明布尔型有效;如果两次响应完全相同,但有一次在固定时间后返回,说明时间型通路存在;如果响应里面直接打印了SQL片段,那么既说明存在报错注入,也能直接看到被拼接进去的字段结构。
还有一点要养成习惯:每次构造绕过payload时,心里要有“最坏预期”。这个参数会不会导致批量数据被删改?会不会触发后台任务?会不会影响生产库?所以建议只在明确授权的测试环境里操作,并且优先使用条件受限的时长延迟或页面差异来判断,而不是直接把xp_cmdshell、INTO OUTFILE这类高权限语句跑在业务库上。
7. 防御视角的复盘:给加固与修复提供参考
7.1 为什么单纯加黑名单永远当不了“铜墙铁壁”
从漏洞利用的角度拆完一轮,再从防守侧看,会得出一个几乎必然的结论:只靠黑名单永远不够。黑名单的问题在于规则集总是有限的,攻击者只要认真枚举测试,总能找到差集。防御的正确方向应该是三层防御:第一层是参数化查询,让SQL结构在代码层就固化成模板,输入只能作为数据,永远无法改变语法结构;第二层是输入校验+白名单,对参数类型、长度、格式做约束;第三层才是WAF兜底,用来拦截剩余的异常流量。参数化查询做得到位的话,后面两层压力会小很多。
7.2 对WAF规则集的加固建议
如果必须依赖WAF,建议加固时做到几件事。一是把业务侧所有可能进入SQL的字段都枚举出来,设置成“仅允许安全字符”,而不是靠WAF自己猜。二是打开日志,定期回看拦截记录,把“绕过尝试”的payload提取出来,补进规则白名单/黑名单里。三是不同数据库的版本差异要配置对应的语法解码器,不要用一套通用规则走天下。四是关注新发布的数据库版本特性,因为攻击者总是优先拿新语法绕过老规则,规则库更新速度太慢的话,天然会落后。
7.3 自动化扫描与人工结合的建议
防御侧想验证WAF规则是否有效,单靠手动测试效率太低。建议把自动化扫描器(覆盖面广、速度快的类型)和人工构造payload结合:先用扫描器把常见注入点扫一遍,产出可疑攻击类型和绕过初步报告;再对报告里的关键点做人工验证,尝试HPP、编码穿越、函数变形等扫描器不容易覆盖的“高级形态”。自动化能帮我们发现“规则盲区”,人工则能深入“规则盲区”里挖更大的洞。最理想的效果是把一次安全评估落地成一个“WAF规则更新包”,然后立刻复测,形成闭环。
8. 写在最后:关于“SQL注入绕WAF”的一些个人经验
8.1 绕WAF不是炫技,本质是评估系统健壮性
“绕WAF总结”这个标题听起来很攻击性,但放在真实业务和安全工程的视角下,它更应该被理解为一项能力:你能不能站在攻击者角度,用最小的成本发现防护方案中的缝隙。我在授权测试里见过不少WAF部署看似严格,但业务一旦发生升级、新功能上线、参数增多,规则覆盖就跟不上,漏洞就这么出现在缝隙里。测绕过的过程,本质上是在帮防守方找出自己“不知道不知道”的盲区。
8.2 保持系统性方法论的通用价值
我一直在强调“先摸清WAF和解说环境,再做构造”,是因为这种方法论离开WAF之后同样适用:写代码要理解底层解析,做运维要理解容错机制,排查故障要先做隔离变量。所谓“懂绕”的核心,不是背了多少payload,而是你能否从“解析差异”“语法边界”“规则覆盖差集”这三个维度去检查一个系统。方向对了,招数是永远用不完的。
8.3 最后分享一条小技巧
在绕WAF时,我最常用的一条经验是:不要试图构造一条“完美的不被拦截payload”,而是构造一条“第一眼看起来无害,但在最后一个环节才显出恶意”的payload。很多测试人员喜欢在请求里塞满union select、sleep(5)这类特征值,那样只会触发最明显的规则。真正稳定的做法是把关键动作“藏”在数据库能接受的非常规写法里,比如利用数据库自身容错、注释符展开、参数拼接、字符集转换,让WAF在前置环节没有表现出“危险”的理由。这条经验可以帮你少踩很多坑,尤其是在面对自研WAF和网关设备时。