资讯动态

SQLMap Tamper脚本全解析:原理、组合与自定义绕过WAF

发布时间:2026/10/22 15:43:21 来源:尧图企业网站定制
SQLMap跑得好好的突然某一次授权测试里所有带明显SQL注入特征的请求全被一堵看不见的墙拦下来了。响应码一片红日志里全是WAF的拦截记录。那时候第一反应是换payload再试还是被拦。折腾到后半夜才意识到真正的问题不是SQLMap不会注入而是它默认发出的那些请求特征太“标准化”了WAF一认一个准。后来就是用Tamper脚本解决了问题也是从那次开始我把这一整套东西系统地捋了一遍。这篇文章约定一个前提所有测试都必须在获得书面授权的范围内进行目标是帮安全测试人员和运维同学验证WAF规则是否有效而不是教人拿工具去打别人的站点。Tamper脚本本身是一把双刃剑理解它的原理对写规则的人同样重要——不知道攻击方怎么变形就不知道怎么加固。1. Tamper脚本的设计初衷与运行原理1.1 为什么SQLMap需要Tamper脚本很多人对SQLMap的印象是“一条命令跑到黑”实际用过就会发现在裸奔的测试环境里确实舒服但一旦前面挡着WAF默认请求基本寸步难行。WAF拦截SQL注入靠的是啥说穿了就是规则匹配请求里有UNION SELECT、 OR 11、sleep(5)这类特征串直接判黑。SQLMap默认生成的payload虽然内部逻辑复杂但在传输形态上非常直白引号、关键字、注释符一个不少正好撞在各种正则规则的枪口上。Tamper脚本干的活就是在这条payload发出之前先按脚本里的逻辑做一次“文字变形”。变形之后语义不变但特征没了。举个最简单的例子SELECT改成SeLeCt如果WAF规则只匹配大写原文那这个请求就放行了。再比如把空格替换成/**/规则里没考虑注释符夹在关键字中间的情况一样能滑过去。这个思路跟说话改口音是一个道理。同一句话用标准普通话说出来安检员一听就知道你要干嘛换一口方言意思还是那个意思但对方可能就愣一下等他反应过来请求已经到后端了。WAF和Tamper脚本就是安检员和方言的关系。1.2 Tamper脚本在SQLMap里的执行链路SQLMap框架对Tamper脚本有一套固定的调用约定。脚本本质上是Python文件放在SQLMap的tamper/目录下框架启动时会自动扫描加载。每个脚本必须实现一个tamper(payload, **kwargs)函数函数接收原始payload返回变形后的payload。执行流程大致是这样SQLMap生成一个注入payload → 交给Tamper脚本链按优先级排序 → 每个脚本依次对payload做修改 → 最终请求发出 → 根据响应判断注入是否成功。注意这个“链”的概念多个Tamper脚本可以同时启用用逗号分隔后一个脚本拿到的是前一个脚本变形后的结果。所以脚本之间的顺序会影响最终形态这后面细说。每个脚本还可以声明一个__priority__属性控制它在脚本链里的执行顺序。框架内置了一套优先级常量比如LOW、NORMAL、HIGH但说实话大部分场景下我们依赖的是命令行里书写脚本参数的顺序优先级更多是让框架在默认状态下有个合理的执行序列。还有一点容易被忽略tamper函数接收的**kwargs里带有当前请求的上下文信息比如HTTP头、注入位置、数据库类型等。利用这些信息可以写出更“聪明”的脚本——不只是机械地替换字符串而是根据环境决定怎么变形。这一层放到第三节具体讲。2. 自带Tamper脚本的选用与组合技巧2.1 几个高频内置脚本逐一拆解SQLMap官方仓库内置了上百个Tamper脚本不可能全部记住但常用的就那么十几个我按用途分了三类。第一类空白符替换类。这类脚本专门对付WAF里针对空格的规则因为SQL语句里空格是词法分隔必需的东西。典型代表是space2comment把每个空格替换成/**/space2plus把空格换成这个在URL编码环境里很实用space2randomblank更狠随机插入一个制表符、换行符或者注释符替代空格。实测下来space2comment的命中率最高因为/**/在很多数据库里都能当空格用而WAF规则往往想不到去堵这个洞。第二类关键字混淆类。针对黑名单式WAF规则把敏感关键字打散。最基础的是randomcase随机改变每个字母的大小写equaltolike把替换成LIKE因为WHERE a1和WHERE a LIKE 1在某些数据库里效果一样modsecurityversioned在关键字中间插入版本化注释比如/*!12345SELECT*/MySQL会忽略注释而执行里面的SQL。这类脚本对付只做精确匹配的WAF特别有效。第三类编码转换类。charunicodeencode把字符串转成Unicode编码形式charordencode转成ASCII码十进制形式percentage转成URL百分号编码。这类脚本对付的是那些规则写得比较浅、只解码一层就匹配的WAF。但要注意编码类脚本也容易被更严格的WAF直接拦掉因为请求里带着大量的%u或char()函数特征一眼假。2.2 脚本组合的正确姿势与常见误区单个Tamper脚本效果有限因为实战里的WAF很少只设一道规则。这时候就要组合使用。命令写起来很简单sqlmap -u http://example.com/page?id1 --tamperspace2comment,randomcase框架会先执行space2comment把空格换掉再执行randomcase把剩下的字母随机大小写。两条规则叠加WAF既匹配不到原始关键字也匹配不到原始空格形态。组合的误区主要有两个。第一个是“堆得越多越好”。脚本多了payload体积膨胀请求超时、误拦截的概率飙升而且多个脚本的修改逻辑可能互相抵消。比如先用space2comment把空格换成注释再用space2plus把注释里的斜杠换掉最后生成一个既不是注释也不是加号的东西后端数据库反而解析不了。第二个误区是“一套组合打天下”。不同WAF的规则重点完全不同有的盯空格、有的盯关键字、有的盯编码特征组合必须基于对目标WAF行为的判断来定制。我自己的习惯是先单跑一两个脚本试试命中情况再看WAF日志里拦截的具体规则反推它匹配了哪一类特征然后针对性地在组合里加入对应脚本。说白了组合不是碰运气是排查之后的结果。3. 手写自定义Tamper脚本全流程3.1 脚本骨架与必备函数内置脚本不一定覆盖所有场景特别是遇到那些规则写得比较刁钻的WAF就得自己动手写。自定义Tamper脚本的骨架非常固定#!/usr/bin/env python from lib.core.enums import PRIORITY __priority__ PRIORITY.LOW def dependencies(): pass def tamper(payload, **kwargs): # 在这里实现对payload的变形 return payload逐行解释一下。__priority__决定脚本在链中的默认位置取值一般是PRIORITY.LOW、PRIORITY.NORMAL、PRIORITY.HIGH这几个常量。dependencies()函数在框架启动时被调用用来检查环境依赖一般留空或者写个pass就行。核心是tamper函数它接收当前payload返回修改后的版本。有一个新手最容易踩的坑忘记处理payload为None的情况。有些注入场景下SQLMap传递的payload可能是空值脚本里直接调用payload.replace()就抛异常了。规范的写法是先判断def tamper(payload, **kwargs): if not payload: return payload return payload.replace( , /**/)3.2 一个真实的编写实例从替换到智能变形拿一个我自己在授权测试里用过的脚本做例子。当时遇到的WAF会拦截包含INFORMATION_SCHEMA的请求这是个很常见的MySQL系统表名注入过程中几乎绕不开。直接替换大小写没用因为WAF规则里已经做了大小写归一化。靠注释符拆关键字也不行INFORMATION/**/SCHEMA这种形态部分WAF会解析但那只WAF直接把注释符进了黑名单看到注释就拦。最后写了个脚本把字段访问拆成等价形式。INFORMATION_SCHEMA.COLUMNS可以写成INFORMATION_SCHEMA.COLUMNS也可以利用反引号的特性做变形。脚本逻辑很简单def tamper(payload, **kwargs): if not payload: return payload payload payload.replace(INFORMATION_SCHEMA, INFORMATION_SCHEMA /*!30000*/ ) return payload这个脚本利用了MySQL的版本化注释特性/*!30000*/在MySQL等版本里会被当作空格保留同时破坏掉WAF的连续字符串匹配。跑通之后那面WAF的这条规则基本就失效了。要说这类测试说明了什么其实不是“WAF没用”而是规则库需要补充对版本化注释的识别。3.3 用上下文信息写出更聪明的脚本高级用法是让脚本根据不同的注入场景调整变形策略。kwargs里带了很多有价值的信息。比如HTTP请求头某些WAF只对特定Content-Type做完整检查脚本可以把payload的编码方式跟着头走。又比如数据库指纹同一个payload在MySQL和Oracle里的注释语法完全不同脚本完全可以检测到数据库类型再决定用哪种变形。看一下这个利用kwargs的示例框架def tamper(payload, **kwargs): if not payload: return payload headers kwargs.get(headers, {}) user_agent headers.get(User-Agent, ) if test-agent in user_agent.lower(): return payload # 某些场景下保持原样 payload payload.replace( , %09) return payload这就引入了动态行为。脚本不是一套固定规则而是根据请求上下文决定具体变形手段。不过得说实话这种脚本需要花时间调试在一个模拟环境里验证每种分支都符合预期不然在真实测试里出了问题很难排查是脚本逻辑错还是WAF规则导致。我的建议是先在完全无WAF的环境里跑通脚本确认变形后的payload能被数据库正常解析再放到WAF环境里去战。**kwargs里实际可用的字段还包括**wd这类框架内部参数但官方并没有把完整的字段列表写进文档最靠谱的办法是直接在脚本里print(kwargs.keys())看一次。每次SQLMap版本升级字段可能会有增删写死依赖某个字段的脚本需要特别注意版本兼容性。4. 实战在授权测试中评估WAF规则的有效性4.1 一次完整评估流程的现场还原直接讲一次我参与过的授权评估实践。目标环境是某公司部署了商业WAF的Web应用管理系统测试边界和授权函都提前确认过任务就是验证WAF是否真的能挡住常见SQL注入手法——这本身是防御加固的一部分。初始探测阶段直接用SQLMap默认配置跑sqlmap -u https://target.example/app/user?id1001 --batch结果一目了然请求被WAF拦截响应里带着典型的WAF拦截页面特征所有的注入点判定全是“无法注入”。这说明WAF确实在正常工作至少对教科书式payload是敏感的。接下来不是放弃而是分析拦截日志。拿到了WAF的访问日志后发现凡是URL里出现id且后面带有空格、引号、UNION关键字的请求全走拦截规则。这就知道了匹配的重点方向。第三步是单脚本试探。我用space2comment和randomcase分别跑了一遍。space2comment跑完依然被拦因为WAF对注释符做了第二层检查randomcase单独跑居然通了说明WAF规则里只匹配大写原词。组合这两个脚本再跑不仅通了而且注入效率和裸奔时几乎差不多。第四步验证并扩大战果。确认注入可行后按照测试流程继续做数据提取、权限评估每一步都带着不同Tamper组合记录哪个组合在什么步骤下有效。最后的交付物是一份完整的WAF规则缺陷报告里面附了每个漏洞对应的触发请求样本和修复建议。4.2 结果解读与加固视角的关键结论这场测试最大的价值不在“绕过了”而在“为什么能绕过”。拿那次实际结果来说WAF的规则缺陷集中在三块第一对关键字大小写做了归一化但漏了空格变体第二规则只匹配连续字符串没考虑数据库注释语法第三对编码类特征识别不充分导致Unicode变形payload漏过。作为交付方我特别强调了这个视角Tamper脚本绕过的本质是WAF规则集的覆盖不全而不是防御思路错了。修复建议也很有针对性WAF规则不只做静态匹配要加一层语法解析能力数据库侧的输入过滤和参数化查询不能省WAF只是第一道门后面还有数据库自己那道锁。规则测试应该覆盖Tamper脚本常用的变形手法把space2comment、randomcase、Unicode编码这类基础变体纳入规则回归用例。我个人的感受是一次成功的WAF评估测试重点不是炫耀“我绕过了一面墙”而是把墙上的裂缝指出来再去补上它。5. 常见问题与高效排错技巧5.1 脚本不生效的常见原因Tramper脚本跑不起来的典型案例我整理了一张表现象常见原因排查方法脚本加载报错Python语法错误或文件命名不规范在tamper目录下手动执行脚本测试语法payload没变化tamper函数内忘记return检查函数末尾是否有返回值脚本执行了但依然被拦变形力度不够没命中WAF规则盲区查看WAF日志确认拦截的特征字段数据库报错无法注入变形过度破坏SQL语法在无WAF环境单独验证变形后payload多个脚本互相冲突后脚本覆盖前脚本的变形结果逐个脚本开启测试二分定位冲突点部分请求超时脚本链过长或payload体积膨胀减少脚本数量简化变形逻辑这里特别说一下payload返回值的坑。有次我在一个脚本里对payload做多种替换中间写了多个if分支某些分支下忘了加return payload结果框架收到None直接报错。这种错误在本地单测时很容易暴露所以养成习惯任何修改路径都要有返回值。5.2 避坑心得与效率建议排错层面我强烈建议打开SQLMap的-v参数至少开到-v 3这样能看见每个HTTP请求的完整payload。很多“为什么不生效”的问题看一眼实际发出的请求就明白了。是不是脚本根本没加载是不是变形逻辑跟预期不符一眼可辨。效率层面别上来就挂一堆Tamper脚本跑。正确的流程是先用简单脚本快速度过判定阶段确认注入点活下来了再换上更复杂的组合去跑数据提取。数据提取阶段请求量大脚本解码开销不可忽视过分复杂的变形会把一次全表提取拖成慢动作。心理层面千万别把“绕过WAF”当成炫技目标。我在一次测试里为了绕过一面规则很粗糙的WAF写了七八个Tamper脚本最后虽然成功了但回头一看真正的问题根本不是Tamper不够多而是那面WAF本身就形同虚设。写更多脚本只是浪费了时间。判断一个测试值不值得做先看WAF基础规则质量不看自己能写多少脚本。5.3 关于工具版本与脚本兼容性的一点提醒SQLMap版本更新很快不同版本对Tamper脚本的接口定义有一些细微差别。老版本里tamper函数签名是固定的两个参数新版本增加了**kwargs的传参机制。拿一个旧版脚本直接跑到新版SQLMap里大部分时候没事但如果脚本里用了内部模块的引用版本升级后可能直接加载失败。我的做法是固定一个测试环境用的SQLMap版本或者把自定义脚本放到独立目录每次升级后用内置脚本跑一遍回归。至于那些依赖特定版本内部接口的脚本能不用就不用写脚本的时候尽量只用官方接口减少未来维护的成本。最后说点实在的写这一篇之前我把过去几年的测试笔记翻了一遍发现Tamper脚本相关的记录占了挺大篇幅。技术的本质其实不复杂无非是理解WAF规则怎么写的然后利用规则覆盖的盲区做变形。复杂的是使用场景里的各种边界——授权范围、测试目标、交付形式、合规要求这些都是安全测试者必须时刻拎清楚的东西。对我个人来说Tamper脚本教会我的最重要一课是防御方想防住攻击最好的办法不是堆一堆黑名单规则而是真正去理解攻击手法的语法本质然后把校验前移在应用层就用参数化查询把SQL注入的根基断掉。WAF永远只是最后一道防线而Tamper脚本只是检验这道防线质量的探针。如果你只是需要快速解决手头测试里被WAF拦住的问题直接从内置脚本开始试space2comment加randomcase是性价比最高的第一组尝试。如果你是想把这块学透那就多研究kwargs上下文多读几个内置脚本的源码再动手写自己的脚本。工具本身不复杂复杂的是你愿不愿意往下钻一层。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑