SQLMap Tamper脚本:绕过WAF的Payload编码与混淆技术详解
1. 项目概述:为什么我们需要Tamper脚本?
在渗透测试或者安全研究的过程中,你肯定遇到过这种情况:用SQLMap对一个看起来有注入点的URL一顿操作猛如虎,结果返回的要么是“404 Not Found”,要么是“检测到非法请求”,甚至直接被目标网站的WAF(Web应用防火墙)给拉黑了。这时候,很多朋友的第一反应是“这站没注入”,或者“SQLMap不行了”。其实,问题很可能出在Payload上——你发送的SQL语句太“标准”、太“干净”了,就像穿着正装去参加化装舞会,一眼就被保安认出来并请了出去。
SQLMap自带的Tamper脚本,就是为你准备的一整套“伪装道具”。它的核心作用,是在SQLMap生成的标准Payload基础上,进行一层或多层的编码、混淆、变形,从而绕过各种基于正则匹配或关键字过滤的防护机制。你可以把它理解为一个“Payload化妆师”,把UNION SELECT这样的标准语句,变成UnIoN/**/SeLeCt或者%55%4e%49%4f%4e%20%53%45%4c%45%43%54,让WAF的规则引擎“脸盲”,认不出它的真面目。
我见过太多安全从业者,包括一些有经验的朋友,对Tamper脚本的使用还停留在“--tamper=space2comment”这种基础阶段,或者干脆把所有脚本都堆上去碰运气。这不仅效率低下,而且容易触发更高级别的防护规则。这篇指南的目的,就是带你从“知道有这么个东西”,到“理解它怎么工作”,再到“能根据实际情况组合或编写自己的Tamper脚本”,真正把Tamper脚本用活、用好。
2. Tamper脚本的核心原理与工作机制
要玩转Tamper脚本,首先得明白SQLMap的注入流程和Tamper脚本在其中的位置。这能帮你理解为什么有时候脚本不生效,以及如何更精准地使用它们。
2.1 SQLMap的Payload生成与处理流程
SQLMap的注入过程可以简化为以下几个核心步骤:
- 探测与指纹识别:SQLMap首先会发送一些无害的探测请求,判断目标是否存在注入点、数据库类型、版本等信息。
- Payload库匹配:根据识别出的数据库类型(如MySQL、MSSQL、Oracle),SQLMap会从其庞大的内置Payload库中,选取适合当前注入类型(布尔盲注、时间盲注、报错注入、联合查询等)的模板。
- Payload渲染:将模板中的占位符(如
[COLUMN]、[RANDNUM])替换为实际的值,生成一个标准的、语法正确的SQL注入Payload。 - Tamper脚本处理(关键环节):如果用户指定了
--tamper参数,上一步生成的标准Payload就会依次传递给指定的Tamper脚本进行处理。每个脚本都是一个独立的Python模块,其核心是一个tamper(payload, **kwargs)函数,它接收原始Payload,经过一系列字符串操作(如替换、编码、插入)后,返回变形后的Payload。 - 请求发送与结果判断:将变形后的Payload嵌入到HTTP请求中(可能是GET参数、POST数据、Cookie、HTTP头等)发送给目标,然后根据返回的HTTP响应内容、状态码、时间延迟等,判断注入是否成功。
注意:Tamper脚本的处理是单向且链式的。假设你指定了
--tamper “scriptA,scriptB”,那么Payload会先经过scriptA处理,其结果再交给scriptB处理。脚本的顺序非常重要,错误的顺序可能导致Payload变得无效或更易被检测。
2.2 Tamper脚本的两种主要绕过思路
所有Tamper脚本的“魔法”,都围绕着两种核心思路展开:
思路一:绕过关键字过滤这是最常见的情况。WAF或应用程序的自定义过滤器会维护一个“黑名单”,里面是SELECT、UNION、AND、OR、空格、引号等敏感词汇。Tamper脚本的任务就是用“障眼法”让这些词汇逃过检查。
- 大小写变形:
randomcase.py脚本将SELECT随机变为SeLeCt或SELEct。对于仅进行简单大小写匹配的过滤器有效。 - 关键字分割:
space2comment.py把空格变成/**/,randomcomments.py在关键字中间插入/**/,变成SEL/**/ECT。这利用了SQL解析器会忽略注释,而字符串匹配规则可能要求连续匹配的特性。 - 等价替换:
equaltolike.py把=换成LIKE;greatest.py把>换成GREATEST()函数。这需要目标SQL上下文支持这些等价写法。 - 编码混淆:
base64encode.py、charencode.py对整个或部分Payload进行URL编码。这依赖于目标应用程序在将参数传递给SQL执行引擎前,是否进行了URL解码。如果解码发生在WAF检查之后,就能成功绕过。
思路二:绕过特定WAF的已知规则一些脚本是针对历史上某些WAF产品(如ModSecurity、WebKnight、F5 BIG-IP)的特定规则缺陷而编写的。它们更像是“特制武器”。
- 利用解析差异:
apostrophemask.py用UTF-8全角引号‘代替ASCII半角引号'。有些WAF的字符规范化处理可能漏掉全角字符,而数据库却可能识别。 - 添加“糖衣”:
sp_password.py在Payload末尾添加sp_password。这是针对MSSQL的,因为MSSQL Server自身在记录日志时会对包含sp_password的语句进行模糊处理,某些WAF可能模仿了这一行为而选择忽略。 - 内联注释:
versionedmorekeywords.py、modsecurityversioned.py利用MySQL的特性,在关键字前添加版本特定的注释,如/*!50000SELECT*/。MySQL 5.0.0会执行注释中的内容,而一些WAF可能不会解析这种复杂注释。
理解这些原理后,你就不会盲目堆砌脚本,而是能像医生开药一样,先“诊断”(手工测试过滤了什么),再“下处方”(选择合适的Tamper脚本)。
3. 从入门到精通:Tamper脚本实战使用全流程
知道了原理,我们进入实战环节。我将用一个模拟的、存在基础WAF的测试环境来演示完整流程。假设目标URL是:http://test.vuln.com/product.php?id=1
3.1 第一步:手工探测与过滤分析(关键!)
在祭出SQLMap之前,手工探测是必不可少的。这能帮你快速了解WAF的“脾气”。你需要一个能拦截和重放HTTP请求的工具,比如Burp Suite。
探测基础注入点:
- 发送
id=1,正常返回。 - 发送
id=1',如果返回错误(如500内部服务器错误)或WAF拦截页面,说明单引号被应用程序处理且可能触发SQL错误,但也可能触发了WAF。 - 发送
id=1 AND 1=1,如果被拦截,说明AND、=或空格被过滤。 - 发送
id=1 AND 1=2,同样观察是否被拦截。
- 发送
识别被过滤的关键字符/词: 这是选择Tamper脚本的依据。你需要系统地测试常见注入元素。
- 空格:尝试
id=1%201(URL编码的空格)。如果%20被拦截,尝试+、/**/、%0a(换行)、%0d(回车)、%09(制表符)。 - 等号:尝试
id=1 LIKE 1。 - 引号:尝试
id=1%27(单引号)、id=1%22(双引号)。看看是否可以用%bf%27(unmagicquotes.py的原理)或十六进制编码绕过。 - 关键字:分别测试
SELECT、UNION、FROM、WHERE、OR、AND。可以尝试大小写变体(SeLeCt)、内联注释(/*!50000SELECT*/)、拆分(SEL/**/ECT)。 - 组合测试:有时单个字符没事,组合起来就被拦。例如,单独
SELECT和FROM可能没事,但SELECT ... FROM这个模式就被拦了。
- 空格:尝试
实操心得:我习惯在Burp Repeater里建一个列表,把要测试的Payload逐个发送,根据返回的HTTP状态码(如403、200)、响应长度和内容(是否有“Blocked”、“Forbidden”等关键词)来判断。记录下哪些被放行,哪些被拦截。这个过程就像在绘制WAF的“安全地图”。
3.2 第二步:选择合适的Tamper脚本
基于手工探测的结果,我们可以有针对性地选择脚本。下面是一个快速选型指南:
| 过滤/拦截情况 | 可能有效的Tamper脚本 | 原理简述 |
|---|---|---|
| 空格被过滤 | space2comment.py | 空格 ->/**/ |
space2plus.py | 空格 ->+ | |
space2hash.py | 空格 ->#随机字符串%0A | |
space2mssqlblank.py | 空格 -> 其他空白符(MSSQL) | |
等号=被过滤 | equaltolike.py | =->LIKE |
greatest.py | >->GREATEST() | |
between.py | =->BETWEEN AND,>->NOT BETWEEN 0 AND | |
单引号'被过滤 | apostrophemask.py | '->%EF%BC%87(UTF-8全角单引号) |
charencode.py/chardoubleencode.py | 对整个Payload进行URL编码 | |
unmagicquotes.py | '->%bf%27(宽字节注入) | |
| 关键字被过滤 | randomcase.py | 随机大小写变换 |
versionedmorekeywords.py | 在关键字前加MySQL版本注释 | |
randomcomments.py | 在关键字中插入/**/ | |
| 疑似特定WAF | modsecurityversioned.py | 针对ModSecurity的绕过 |
bluecoat.py | 针对BlueCoat SGOS的绕过 | |
| 想增加混淆 | base64encode.py | Base64编码整个Payload |
appendnullbyte.py | 在Payload末尾加空字节%00 |
重要提示:
--identify-waf参数可以帮助识别WAF类型,但并非100%准确。手工探测结合经验判断更可靠。
3.3 第三步:组合使用与调试
单一脚本往往不够,需要组合使用。组合的原则是:先处理字符级过滤,再处理关键字级过滤;先进行编码混淆,再进行结构变形。
假设我们探测发现:空格、等号、UNION和SELECT被过滤。
一个可能的组合尝试是:
sqlmap -u "http://test.vuln.com/product.php?id=1" --tamper="space2comment,equaltolike,randomcase" -v 3space2comment:先把所有空格变成/**/。equaltolike:再把其中的等号=变成LIKE。randomcase:最后对整体Payload进行随机大小写变换。
使用-v 3(详细等级3)可以让SQLMap输出它发送的每一个Payload,这是调试Tamper脚本组合是否有效的黄金手段。你可以在终端输出里看到类似这样的变形过程:
[14:20:33] [PAYLOAD] 1'/**/AnD/**/1=1 [14:20:33] [PAYLOAD] 1'/**/AnD/**/1 LIKE 1 [14:20:33] [PAYLOAD] 1'/**/aNd/**/1 LiKe 1观察最终Payload是否符合你的预期,是否绕过了手工测试时发现的过滤规则。
常见问题:如果组合脚本后注入仍然失败,检查-v 3的输出。可能某个脚本把前一个脚本处理过的有效字符又改坏了。这时需要调整脚本顺序,或者换用其他脚本。
4. 高级技巧:编写你自己的Tamper脚本
当现有的Tamper脚本都无法满足需求,或者你想针对某个特定的、奇葩的过滤规则时,自己编写脚本就是终极解决方案。别担心,编写一个基础的Tamper脚本比想象中简单。
4.1 Tamper脚本的基本结构
SQLMap的Tamper脚本都位于/tamper/目录下。每个脚本都是一个标准的Python文件。我们来看一个最简单的例子,比如模仿space2comment.py:
#!/usr/bin/env python """ Copyright (c) 2006-2024 sqlmap developers (http://sqlmap.org/) See the file 'LICENSE' for copying permission """ from lib.core.enums import PRIORITY __priority__ = PRIORITY.NORMAL # 定义脚本优先级,用于多个脚本时的默认排序 def dependencies(): pass # 这里可以定义脚本依赖,比如需要某个特定的数据库类型 def tamper(payload, **kwargs): """ 这是核心函数,接收原始payload,返回处理后的payload。 kwargs 可能包含一些有用的上下文信息,如 DBMS 类型。 """ if payload: # 最简单的替换:将空格替换为注释符 /**/ retVal = payload.replace(" ", "/**/") else: retVal = payload return retVal这就是一个完整可用的Tamper脚本了!把它保存为my_space2comment.py,放到SQLMap的tamper目录下,就可以用--tamper=my_space2comment来调用了。
4.2 编写一个实用的自定义脚本:绕过自定义关键字过滤
假设我们遇到一个奇葩的WAF,它把union select这个短语整体过滤了,但单独过滤union或select。我们可以写一个脚本,在union和select之间插入一个不起眼的、可被SQL解析器忽略的“脏数据”。
#!/usr/bin/env python """ 自定义脚本:在 UNION 和 SELECT 之间插入无效的数值表达式,绕过整体短语匹配。 """ from lib.core.enums import PRIORITY import re __priority__ = PRIORITY.NORMAL def dependencies(): pass def tamper(payload, **kwargs): if not payload: return payload # 使用正则表达式查找 'union select' (不区分大小写) # 并将其替换为 'union/*123*/select',其中/*123*/会被SQL解析为注释而忽略 # 注意:这里使用 re.IGNORECASE 标志进行不区分大小写匹配 retVal = re.sub(r"(union)\s+(select)", r"\1/*1337*/\2", payload, flags=re.IGNORECASE) # 为了更健壮,也可以处理其他变体,比如 UNION ALL SELECT retVal = re.sub(r"(union all)\s+(select)", r"\1/*1337*/\2", retVal, flags=re.IGNORECASE) return retVal脚本解析:
import re:导入正则表达式模块,用于进行复杂的模式匹配和替换。re.sub(pattern, repl, string, flags):这是核心函数。pattern=r"(union)\s+(select)":匹配“union”后面跟一个或多个空白字符,再跟“select”。括号()用于创建捕获组。repl=r"\1/*1337*/\2":替换文本。\1代表第一个捕获组(即union),\2代表第二个捕获组(即select)。我们在它们之间插入/*1337*/。flags=re.IGNORECASE:使匹配不区分大小写,能匹配UNION SELECT、Union Select等。
- 这个脚本将把
union select user,pass from table变成union/*1337*/select user,pass from table。对于只匹配“union select”连续字符串的简单WAF规则,这很可能就绕过去了。
4.3 利用kwargs上下文信息编写更智能的脚本
tamper函数接收的**kwargs参数包含一些执行上下文信息,比如目标数据库类型(kwargs.get("dbms"))。我们可以利用这些信息,让脚本针对不同数据库采取不同的混淆策略。
#!/usr/bin/env python """ 智能脚本:根据数据库类型,采用不同的注释语法分割关键字。 """ from lib.core.enums import PRIORITY import re __priority__ = PRIORITY.NORMAL def dependencies(): pass def tamper(payload, **kwargs): if not payload: return payload dbms = kwargs.get("dbms", "").lower() retVal = payload # 定义不同数据库的注释风格 comment_styles = { "mysql": "/**/", # MySQL 多行注释 "mssql": "/**/", # SQL Server 也支持 /**/ "oracle": "--", # Oracle 单行注释 "postgresql": "--", # PostgreSQL 单行注释 } # 默认使用 /**/ style = comment_styles.get(dbms, "/**/") # 在常见SQL关键字后插入注释(这里以SELECT为例) # 匹配 SELECT 后跟空格或括号等边界的情况 pattern = r"\b(select)\b(\s*)" replacement = r"\1" + style + r"\2" retVal = re.sub(pattern, replacement, retVal, flags=re.IGNORECASE) return retVal这个脚本会根据目标数据库是MySQL还是Oracle,自动选择使用/**/还是--作为插入的注释符,使得生成的Payload更贴合目标数据库的语法,提高绕过成功率。
实操心得:编写自定义脚本时,一定要多用-v 3参数观察输出,并用一个简单的本地测试环境(比如DVWA、SQLi-Labs)验证Payload的有效性。确保你的变形不会破坏SQL语句的原始语法逻辑。
5. 实战案例:绕过一个模拟的复合过滤WAF
让我们通过一个综合案例,串联前面所有知识。假设目标有如下过滤规则:
- 过滤
select、union、from、where等关键字(大小写不敏感)。 - 过滤空格和等号。
- 过滤单引号。
我们的绕过思路:
- 处理关键字:使用
randomcase.py进行大小写变形。但仅此可能不够,因为有些WAF会做大小写归一化。我们可以结合versionedmorekeywords.py(如果是MySQL),用内联注释包裹关键字。 - 处理空格:使用
space2comment.py将空格变为/**/。 - 处理等号:使用
equaltolike.py将=变为LIKE。 - 处理单引号:尝试
charencode.py进行URL编码。如果不行,尝试unmagicquotes.py(针对开启了magic_quotes或类似转义的场景)。
构造SQLMap命令: 由于versionedmorekeywords.py和randomcase.py都作用于关键字,需要注意顺序。通常先进行结构性的注释包裹,再进行大小写变换。
sqlmap -u "http://target.com/page?id=1" \ --tamper="versionedmorekeywords,space2comment,equaltolike,charencode,randomcase" \ -v 3 \ --dbms=mysql \ --technique=B \ # 假设使用布尔盲注 --batch命令解析:
versionedmorekeywords:首先将SELECT等关键字变成/*!50000SELECT*/。space2comment:将上述结果中的空格(包括注释内的空格?这里要注意,脚本可能不会处理注释内的内容,需要测试)变成/**/。equaltolike:将=变成LIKE。charencode:对整个字符串进行URL编码。这会编码所有字符,包括注释符和关键字。randomcase:最后对编码后的字符串进行大小写变换?注意:randomcase对已经URL编码的字符串(大部分是%加两个十六进制数字)作用有限,因为%和数字不变,只变字母。这个顺序可能不是最优的。可能需要调整顺序,比如把randomcase放在charencode之前。
通过-v 3观察Payload的演变,如果发现某一步变形导致Payload无效,就需要调整脚本顺序或更换脚本。这个过程是迭代和实验性的。
6. 常见问题、排查技巧与高级注意事项
即使掌握了所有技巧,实战中依然会踩坑。下面是我总结的一些常见问题及解决方法。
6.1 常见问题速查表
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 使用Tamper脚本后,注入完全没反应,连初始探测都失败 | 1. Tamper脚本破坏了Payload的基本SQL语法。 2. 脚本执行顺序导致最终Payload格式错误。 3. 目标对某种变形(如特定编码)本身就有过滤。 | 1. 使用-v 3查看最终Payload,复制到Burp Suite中手工测试,验证其语法和是否被拦截。2. 尝试只使用一个最简单的脚本(如 space2comment),逐步添加,定位问题脚本。3. 检查目标是否对 /**/、+、%0a等替换字符本身有过滤。 |
| 注入能探测到,但无法提取数据(Dump失败) | 1. 关键字过滤不彻底,SELECT、FROM等在数据提取阶段被过滤。2. Payload长度或复杂度触发了WAF的二次检测或速率限制。 3. Tamper脚本导致某些函数(如 substring(),mid())变形后失效。 | 1. 在数据提取阶段(--dump)使用-v 3观察Payload,看是否有关键字被拦截。2. 使用 --delay参数降低请求频率,避免触发速率限制。3. 尝试使用 --hex或--no-cast等参数,改变数据提取方式。 |
| 同一个Tamper脚本组合,有时成功有时失败 | 1. WAF可能有学习模式或动态规则,多次相似请求触发升级防护。 2. 脚本中的随机化因素(如 randomcase,space2hash中的随机字符串)导致某些变体被拦。3. 目标服务器负载均衡,不同后端节点的WAF策略略有差异。 | 1. 在SQLMap中使用--random-agent和--proxy轮换IP或User-Agent,模拟不同来源请求。2. 对于含随机化的脚本,尝试固定随机种子(需修改脚本源码),或换用非随机化脚本。 3. 使用 --flush-session清除会话,重新开始测试。 |
| 手工测试Payload能绕过,但SQLMap不行 | 1. SQLMap注入的上下文(如User-Agent、Cookie、其他参数)触发了WAF。 2. SQLMap默认使用更“激进”的Payload,即使经过Tamper变形,其模式仍被识别。 3. Tamper脚本未应用到所有类型的Payload上(SQLMap有多种注入技术)。 | 1. 使用--user-agent、--cookie等参数模拟手工测试时的请求头。2. 使用 --level和--risk参数从最低级别开始测试。3. 指定 --technique为手工测试成功的那种注入类型(如--technique=B布尔盲注)。 |
6.2 高级注意事项与心得
- 不要过度依赖Tamper脚本:Tamper脚本主要针对基于模式匹配的传统WAF。对于基于语义分析、机器学习或RASP(运行时应用自保护)的现代防护体系,简单的字符串变形很难奏效。此时需要更高级的技巧,如时间盲注、二阶注入、或者寻找WAF规则之外的攻击面。
- 理解“混淆”与“有效性”的平衡:越复杂的混淆,越可能绕过WAF,但也越可能破坏SQL语句的语法,或者被目标应用程序的输入处理逻辑(如多次解码、过滤)意外“修复”成危险语句。务必在测试环境中验证混淆后Payload的有效性。
- 善用SQLMap的其他参数:Tamper脚本不是孤立的,结合其他参数效果更好。
--prefix和--suffix:手动指定Payload的前缀和后缀,有时能解决闭合问题。--tamper与--eval:--eval允许你使用Python表达式在每次请求前动态修改参数,比Tamper更灵活,可以处理一些依赖上下文的复杂变形。--mobile、--random-agent:模拟移动端或随机化User-Agent,绕过一些基于客户端特征的简单封禁。
- 保持脚本更新与社区学习:WAF规则在更新,绕过技术也在发展。多关注SQLMap的官方更新(
/tamper/目录会新增脚本),以及安全社区(如Seclists、Exploit-DB)分享的新型WAF绕过技巧。理解这些技巧的原理,你就能编写出属于自己的“武器库”。 - 合法授权是前提:所有技术都应在拥有明确书面授权的渗透测试或安全研究环境中使用。未经授权对他人系统进行测试是违法行为。
Tamper脚本是SQLMap这把“瑞士军刀”上非常锋利的一个组件。从机械地使用到理解原理,再到能自定义脚本解决问题,这个过程本身就是对Web应用防火墙机制和SQL注入原理的深入理解。希望这篇指南能成为你从“脚本小子”迈向真正安全研究者的垫脚石。记住,工具是死的,人是活的,最强大的Tamper脚本永远是你善于分析和思考的大脑。
