
1. 项目概述一次针对WAF与WASM加密的实战渗透最近在做一个授权范围内的安全测试项目目标是一个金融类Web应用。在初步信息收集和常规漏洞扫描阶段我遇到了一个典型的“现代防御组合拳”Web应用防火墙WAF在前端拦截可疑请求而核心的业务逻辑和敏感参数处理则被编译进了WebAssemblyWASM模块中。这直接导致像sqlmap这样的自动化工具即使尝试了各种绕过WAF的tamper脚本也收效甚微请求要么被直接阻断要么返回的响应是经过WASM加密处理后的、无法直接识别的数据。这激起了我的挑战欲决定手动深入分析目标很明确理解其整体防护逻辑找到WAF的检测盲点并最终逆向分析其WASM加密流程实现关键业务接口的自动化测试。这个过程不仅涉及对网络协议层的理解更需要深入到浏览器运行时和低级字节码的层面是一次非常综合的实战演练。简单来说这次实战要解决两个核心问题第一如何让我的测试请求“看起来正常”骗过WAF的规则检测第二如何破解前端那个“黑盒子”一样的WASM模块搞清楚它怎么加密数据并在我自己的脚本里复现这个加密过程。最终目的不是为了破坏而是为了在授权测试中能够模拟真实攻击者的深度更全面地评估系统的安全性。如果你也遇到过WAF拦截、前端混淆或WASM加密的难题那么我接下来的踩坑经验和破解思路或许能给你提供一个清晰的参考路径。2. 整体对抗思路与方案选型面对WAFWASM的双重防御盲目测试是徒劳的。我首先需要建立一个清晰的对抗模型。WAF通常工作在应用层HTTP/HTTPS基于规则库如正则表达式对请求的URL、参数、Header、Body进行模式匹配。而WASM加密发生在客户端浏览器内它接收前端JavaScript传递的明文参数经过内部算法计算后输出密文再由JavaScript将密文提交给服务器。服务器端则有对应的解密逻辑。因此我的整体思路是“分层拆解各个击破”WAF绕过层专注于HTTP请求的“形态”伪装。目标是构造出在WAF规则看来是合法、无害的请求从而将我的攻击载荷Payload送达后端应用本身。这一步不关心Payload是否被加密。WASM逆向层专注于数据“内容”的加解密。目标是理解WASM模块的输入、输出和内部逻辑并能够在我自己的Python/Node.js环境中模拟这个加密过程生成服务器可接受的合法密文。在方案选型上我放弃了单纯依赖sqlmap的--tamper脚本。因为tamper脚本主要处理的是Payload的变形如编码、注释绕过对于复杂的WAF行为识别和动态的WASM加密流程无能为力。我决定采用“手动分析 工具辅助 自定义脚本”的组合方案主要工具Chrome/Edge开发者工具核心、Burp Suite代理与重放、Python编写自动化脚本、wasm2wat/wasm-decompile等WASM分析工具。核心方法通过浏览器开发者工具动态调试捕获前端JavaScript与WASM模块的交互过程静态分析WASM二进制文件理解其导出函数和大致逻辑最后使用Python模拟整个加密流程。这个方案的优点在于灵活、深入能够应对非标准的防护措施。缺点是对分析者的技术要求较高耗时较长。但对于真正想理解其防御原理和安全边界来说这是必经之路。3. WAF检测绕过实战与技巧WAF是我的第一道关卡。我首先使用Burp Suite拦截了一个正常的登录请求观察其结构。然后我尝试在密码字段插入一个简单的SQL注入探测载荷 OR 11。果然请求被阻断返回了一个包含WAF厂商标识的403页面。3.1 WAF规则探测与指纹识别第一步是识别WAF的类型和可能的行为模式。我使用了WAFW00F这类指纹识别工具同时也手动观察拦截页面的特征、响应头如Server、X-Powered-By、X-*-WAF等确认了它是一款基于云的常见WAF产品。知道WAF类型有助于我查找公开的绕过技巧或已知的规则弱点。注意在授权测试中明确告知客户你正在测试WAF绕过是必要的并确保所有测试流量都在授权范围内。未经授权的WAF绕过测试可能违反法律和服务条款。3.2 常见绕过手法的实战应用我系统性地尝试了多种绕过手法并记录下哪些有效。这里分享几个在这次实战中起到关键作用的方法3.2.1 参数污染与特殊字符干扰某些WAF只检查第一个或最后一个同名参数。我尝试了参数污染passwordlegitpassword OR 11。有时在参数值中添加多余的空白符、换行符%0a、空字符%00或特殊的Unicode字符可以干扰WAF的正则解析引擎。例如将单引号转换为%u0027或%ef%bc%87全角单引号的URL编码。3.2.2 HTTP请求结构变异方法覆盖尝试使用POST方法但在Body中以GET参数形式x-www-form-urlencoded传递数据或者反过来。Content-Type混淆将Content-Type从application/x-www-form-urlencoded改为multipart/form-data并构造相应的数据块。WAF对multipart格式的解析有时不如标准格式严格。畸形分块传输编码使用Transfer-Encoding: chunked并构造畸形的分块大小如负数、超长数字可能使WAF的流式解析器出错而后端服务器却能正常处理。这一步需要借助Burp Suite的插件如Chunked插件或自定义Python脚本来实现。3.2.3 协议层与Header伪装修改User-Agent伪装成常见浏览器的完整字符串或搜索引擎爬虫。添加冗余Header添加大量无意义但合法的Header如X-Forwarded-For、X-Client-IP等可能淹没WAF的检测逻辑。HTTPS与HTTP/2确保测试使用HTTPS。有些WAF在HTTP/2协议下的检测规则可能存在差异可以尝试切换协议版本。3.2.4 针对SQL注入的进阶Payload变形对于SQL注入载荷我采用了组合拳大小写混合与内联注释 oR/**/11。/**/在SQL中是注释但能分割关键词绕过简单的正则匹配。双重URL编码对载荷进行两次URL编码。WAF可能只解码一次而后端应用解码两次导致Payload被还原。例如单引号第一次编码为%27第二次编码为%2527。使用非常见等价函数或语法例如用LIKE代替用||连接字符串代替或CONCAT。经过一系列尝试我发现组合使用multipart/form-data格式并在参数值中插入特定Unicode控制字符能够稳定地绕过该WAF对简单注入语句的检测。我的Payload成功抵达了服务器从Burp的响应时间或返回的错误信息可以判断但返回的数据依然是乱码——这说明我闯过了WAF但撞上了WASM加密这第二堵墙。4. WASM模块逆向分析与加密流程破解现在我的“子弹”Payload已经能送到目标“房间”后端应用门口但门口有个“密码机”WASM模块我必须知道密码的生成规则。接下来焦点转向浏览器内部。4.1 定位与提取WASM模块网络抓包打开浏览器开发者工具的Network面板清空记录执行一次正常的登录操作。在请求列表中除了常见的XHR/Fetch请求我重点关注.wasm文件的请求。果然在提交登录请求之前浏览器先加载了一个login_encrypt.wasm文件。源代码查看在Sources面板中也能找到这个WASM文件并可以直接下载到本地。内存中查找如果WASM是通过WebAssembly.instantiate流式编译的没有单独文件可以在Console中执行WebAssembly.Module.exports(module)来探查已实例化的模块需要先在代码中下断点获取module对象引用或者使用Memory面板搜索WASM魔数\0asm。4.2 静态分析初窥门径拿到login_encrypt.wasm文件后我首先使用命令行工具进行初步静态分析了解其接口。# 使用wasm2wat将二进制wasm转换为可读的文本格式WebAssembly Text wasm2wat login_encrypt.wasm -o login_encrypt.wat查看生成的.wat文件我快速搜索export关键字找到了导出的函数名例如(export encrypt_data (func $encrypt_data)) (export generate_key (func $generate_key))这告诉我这个模块至少暴露了两个函数encrypt_data和generate_key。这是最重要的突破口。接着我使用了更高级的反编译器如wasm-decompile它能生成一种类似高级语言的伪代码比.wat汇编代码更易读。wasm-decompile login_encrypt.wasm -o login_encrypt.dcmp在反编译的输出中我可以看到函数的大致结构、调用了哪些内部函数如内存操作、数学运算以及输入输出参数的类型。这让我对加密流程有了一个模糊的轮廓似乎generate_key会从某个地方可能是导入的内存或全局变量获取一个种子生成一个密钥然后encrypt_data使用这个密钥对输入数据进行处理。4.3 动态调试洞察交互细节静态分析只能看个大概真正的逻辑必须在运行时观察。我回到浏览器开发者工具。下断点在Sources面板找到加载WASM的JavaScript文件通常是一个包含WebAssembly.instantiate或WebAssembly.instantiateStreaming的JS文件。在调用WASM导出函数如instance.exports.encrypt_data的代码行设置断点。观察调用栈与参数触发登录操作代码会在断点处暂停。在Call Stack面板可以看到调用链在Scope或Console中可以查看传入encrypt_data函数的参数值。我发现它通常接收两个参数一个指向输入字符串内存地址的指针以及字符串的长度。跟踪内存这是关键。WASM函数通常通过线性内存Memory与JavaScript交换数据。在Memory面板我可以输入从JS传递过来的指针地址直接查看内存中存储的明文数据是什么。当函数执行完毕后再查看函数返回值通常是一个指向结果内存区域的指针就能在对应内存地址看到加密后的密文可能是一串十六进制或Base64字符串。记录输入输出我手动构造了几组不同的输入如test123、admin、 OR 11重复上述过程记录下每次的明文和对应的密文。这为后续分析加密算法是AESRC4还是自定义的混淆提供了数据样本。4.4 算法分析与模拟复现通过动态调试我获得了多组明文密文对。现在需要分析其加密算法。观察模式相同明文每次加密结果是否相同—— 结果是相同的说明不是随机IV的加密模式可能使用了固定的密钥或种子。密文的长度与明文长度有何关系—— 密文长度固定比如总是32字节的十六进制字符串这强烈暗示了可能是哈希如MD5或者是分组加密的ECB模式。如果密文长度可变且比明文长可能是流加密或带填充的分组加密。修改明文的一个字符观察密文变化。—— 如果密文完全改变雪崩效应可能是哈希或分组加密。如果只有局部变化可能是简单的流加密或异或操作。猜测与验证结合静态分析中看到的generate_key函数我猜测流程是JS调用generate_key()生成或获取一个密钥然后调用encrypt_data(plaintext)。为了验证我在JS调用generate_key后也下断点尝试导出或记录这个密钥。有时密钥会被放在一个全局变量或浏览器的localStorage中。模拟实现经过分析我确定该WASM实现了一个简单的AES-128-ECB加密密钥是通过一个硬编码的种子字符串经过多次MD5哈希生成的。这个种子字符串在WASM模块的常量数据段中可以被静态分析找到。于是我使用Python的pycryptodome库来复现这个过程import hashlib from Crypto.Cipher import AES from Crypto.Util.Padding import pad import binascii def simulate_wasm_encrypt(plaintext): # 1. 还原密钥生成过程 (从静态分析中获得) seed hardcoded_secret_salt_from_wasm # 模拟WASM中的多次哈希 key_material hashlib.md5(seed.encode()).hexdigest() for _ in range(10): # 循环次数从反编译代码中得知 key_material hashlib.md5(key_material.encode()).hexdigest() # 取前16字节作为AES-128密钥 key binascii.unhexlify(key_material)[:16] # 2. 还原加密过程 cipher AES.new(key, AES.MODE_ECB) # 注意padding方式观察WASM使用的是PKCS7还是ZeroPadding padded_data pad(plaintext.encode(), AES.block_size, stylepkcs7) ciphertext cipher.encrypt(padded_data) # 观察WASM输出是hex还是base64 return ciphertext.hex() # 假设输出为十六进制字符串 # 测试 test_input admin123 encrypted simulate_wasm_encrypt(test_input) print(f明文: {test_input} - 密文: {encrypted})我将Python脚本生成的密文与浏览器中动态调试捕获的密文进行对比完全一致至此WASM加密流程被成功破解和复现。实操心得WASM逆向的核心在于“动静结合”。静态分析wasm2wat,wasm-decompile帮你快速找到入口和脉络像看地图动态调试浏览器DevTools让你观察实时数据和逻辑流转像实际在路上开车。两者缺一不可。另外耐心记录和对比输入输出数据对是猜解算法的最直接依据。5. 整合绕过与自动化测试脚本编写现在我已经掌握了两个关键绕过WAF的请求构造方法和WASM加密的模拟算法。接下来就是将二者整合编写一个能够自动对目标进行深度测试的Python脚本。脚本的核心逻辑如下接收一个原始Payload例如SQL注入语句。使用复现的WASM加密逻辑将Payload加密。构造能够绕过WAF的HTTP请求结构例如使用multipart/form-data添加特定Header插入干扰字符等。将加密后的Payload作为请求参数发送。解析响应判断漏洞是否存在。import requests import hashlib from Crypto.Cipher import AES from Crypto.Util.Padding import pad import binascii from urllib.parse import quote class TargetTester: def __init__(self, base_url): self.base_url base_url self.session requests.Session() # 配置绕过WAF的Headers self.session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ..., X-Forwarded-For: 192.168.1.100, # 其他伪装Header... }) def _wasm_encrypt(self, plaintext): # 复用之前的加密函数 seed hardcoded_secret_salt_from_wasm key_material hashlib.md5(seed.encode()).hexdigest() for _ in range(10): key_material hashlib.md5(key_material.encode()).hexdigest() key binascii.unhexlify(key_material)[:16] cipher AES.new(key, AES.MODE_ECB) padded_data pad(plaintext.encode(), AES.block_size, stylepkcs7) return cipher.encrypt(padded_data).hex() def test_sql_injection(self, param_name, original_value, payload_list): 测试某个参数是否存在SQL注入 for payload in payload_list: # 1. 加密Payload encrypted_payload self._wasm_encrypt(payload) # 2. 构造请求数据 (使用multipart/form-data绕过) files { param_name: (None, encrypted_payload) # 关键将加密后数据作为文件字段值 } # 也可以添加额外的干扰字段 data { dummy_field: some_legit_value } try: # 3. 发送请求 resp self.session.post(self.base_url, filesfiles, datadata) # 4. 分析响应 if database error in resp.text.lower() or sql syntax in resp.text.lower(): print(f[] 潜在SQL注入漏洞发现! Payload: {payload}) print(f 响应特征: {resp.text[:200]}...) elif resp.status_code 500: print(f[?] 服务器内部错误Payload可能生效: {payload}) else: print(f[-] Payload未触发明显异常: {payload}) except Exception as e: print(f[!] 请求失败: {e}) if __name__ __main__: tester TargetTester(https://target.com/login) # 准备测试Payload注意Payload已经是打算被加密的内容 sql_payloads [ OR 11, UNION SELECT null,version()--, admin--] tester.test_sql_injection(password, legit_password, sql_payloads)这个脚本将绕过WAF的请求构造和WASM加密模拟自动化极大地提高了测试效率。我可以轻松地扩展它来测试其他类型的漏洞如XSS、命令注入只需修改Payload列表和响应分析逻辑即可。6. 常见问题、排查技巧与防御思考在整个过程中我遇到了不少坑也总结了一些排查技巧。6.1 常见问题与解决WASM内存访问错误在动态调试时如果传递的指针地址错误WASM会触发RuntimeError: memory access out of bounds。确保在JS调用WASM函数时字符串是通过module.exports.__memory或new TextEncoder().encodeInto正确写入内存的。调试时可以在Memory面板手动查看指针地址附近的数据是否正确。加密结果不一致Python复现的加密结果与浏览器不一致。检查密钥确认密钥生成过程的每一步哈希次数、编码方式、截取长度都与WASM内完全一致。在WASM中下断点将每一步的中间结果打印到Console与Python脚本的中间结果对比。检查加密参数加密模式ECB、CBC、填充方式PKCS7、ZeroPadding、初始向量IV是否一致。AES的ECB模式不需要IV而CBC模式需要。检查输入编码确保明文在传入WASM前和传入Python函数前的编码一致通常是UTF-8。WAF规则更新今天能绕过的姿势明天可能就被规则库覆盖了。需要保持对最新绕过技术的研究并准备多套备用方案。6.2 给开发与安全人员的防御思考从攻击者的视角复盘也能为防御方提供加固思路对于WAF深度防御WAF不应是唯一防线。需要与后端输入验证、参数化查询、安全的编码实践相结合。行为分析除了基于规则的匹配应引入基于机器学习的异常行为检测识别低频、畸形的请求模式。定期更新与测试规则库需及时更新并定期进行绕过测试检验WAF的有效性。对于WASM加密不要依赖客户端加密进行安全校验客户端WASM加密只能用于增加逆向难度和防止简单的流量分析绝不能用于替代服务端的身份认证、授权或输入验证。所有安全关键逻辑必须在服务端完成。增加混淆与反调试可以在WASM代码中植入反调试代码检测开发者工具是否打开对WASM二进制进行混淆增加静态分析的难度将密钥材料动态化而不是硬编码。使用代码保护技术考虑使用专业的WASM代码混淆和虚拟化保护工具将核心逻辑转换成自定义的字节码或指令集大幅提升逆向工程的门槛。这次实战让我深刻体会到现代Web安全是一个持续对抗的过程。攻击技术在演进防御体系也需要层层递进、不断加固。作为安全研究人员理解这些绕过和破解技术最终目的是为了帮助构建更坚固的防御。整个过程中最宝贵的不是某个具体的绕过Payload或解密脚本而是这套分析复杂系统、分层拆解问题的方法论。无论防护手段如何变化这套“观察、假设、验证、实现”的流程都是应对新挑战的利器。