基于Yakit MITM插件实现前端加密数据透明化拦截与自动化测试
1. 项目概述当加密数据遇上“透视镜”在Web应用安全测试和日常开发调试中前端加密数据一直是个让人又爱又恨的存在。爱它是因为它保护了用户数据的安全是合规和信任的基石恨它是因为它像一层磨砂玻璃横亘在测试工程师、开发者和真实的业务数据之间。你明明看到请求发出去了响应也回来了但中间流动的却是一堆无法直接理解的密文。传统的抓包工具如Burp Suite、Fiddler此时只能干瞪眼它们能拦截但无法“理解”更谈不上自动化地处理和重放这些加密请求。这个项目的核心就是解决这个痛点如何在不破坏前端加密逻辑的前提下像处理明文一样对加密的请求和响应进行拦截、查看、修改和自动化转发听起来有点像“魔法”但实现的钥匙就是Yakit。Yakit 不是传统意义上的抓包代理。它更像一个“可编程的中间人”其核心能力在于通过MITM中间人攻击技术劫持流量并允许你注入自定义的代码Go 语言插件来实时处理这些流量。这就为我们“透视”加密数据提供了可能我们不需要去逆向前端的加密算法那可能很复杂且涉及法律风险而是在前端加密之后、数据发出之前以及服务端返回密文之后、前端解密之前这两个关键节点上进行操作。简单来说我们的目标不是破解加密而是成为加密通信流程中的一个“透明观察者”和“可控转发器”。这对于以下场景至关重要安全测试对登录、支付、敏感信息查询等加密接口进行深入的漏洞检测而不仅仅是黑盒测试。数据Mock在开发联调或测试环境需要模拟服务端返回特定的加密响应数据时。故障排查当生产环境出现加密接口相关问题时能够快速解密并查看实际传输的数据定位是前端、网络还是服务端的问题。自动化脚本编写自动化测试脚本时能够直接处理加密接口无需关心前端加密细节使脚本更健壮。接下来我将以一个典型的RSA AES 混合加密的前端场景为例拆解如何利用 Yakit 实现从“抓瞎”到“透视”的全过程。你会看到这不仅仅是配置一个工具更是一套完整的技术思路和实战方案。2. 核心思路与架构设计要实现透明化拦截和自动化转发我们不能蛮干需要一个清晰的策略。核心思路是“借力打力旁路解码”。我们绝不尝试去破解或绕过前端的加密逻辑而是利用 Yakit 的代码执行能力在流量经过的瞬间调用与前端相同的加密解密方法完成数据的“翻译”。2.1 前端加密流程典型分析以常见的非对称RSA加密对称AES密钥再用对称密钥加密业务数据的场景为例前端加载时从服务端获取 RSA 公钥。前端随机生成一个 AES 密钥。前端用 RSA 公钥加密这个 AES 密钥得到encryptedKey。前端用 AES 密钥加密真实的业务数据如{username:test, password:123}得到encryptedData。前端将encryptedKey和encryptedData一同发送给服务端。服务端用 RSA 私钥解密encryptedKey得到 AES 密钥。服务端用 AES 密钥解密encryptedData得到明文业务数据并进行处理。响应时服务端可能会用同一个 AES 密钥加密返回数据也可能使用新的临时密钥。我们的拦截点就在第5步之后请求和第8步之后响应。但此时数据已是密文。2.2 Yakit 插件化处理架构Yakit 的“MITM 插件”功能是我们的主战场。其工作流程如下浏览器/APP - (HTTPS请求) - Yakit MITM 服务器 - (解密HTTPS) - 插件处理层 - 目标服务器 目标服务器 - (HTTPS响应) - Yakit MITM 服务器 - 插件处理层 - (重新加密HTTPS) - 浏览器/APP关键在于“插件处理层”。我们可以为请求和响应分别编写处理函数。在这个函数里我们能拿到请求部分method,url,headers,body(原始密文数据)响应部分statusCode,headers,body(原始密文数据)我们的任务就是在插件里对body进行解密和重新加密。这就需要我们将前端的加密解密逻辑“移植”到 Yakit 的插件环境中。2.3 技术选型与依赖管理Yakit 插件使用 Go 语言编写。因此我们需要用 Go 来复现前端的加密解密逻辑。这通常涉及算法识别通过前端代码查看 JavaScript 源文件或网络抓包分析确定使用的加密库和模式。常见的有crypto-js、node-forge、或浏览器原生WebCryptoAPI。Go 库选型RSAGo 标准库crypto/rsa和crypto/x509足以应对绝大多数 PKCS1 或 PKCS8 格式的加解密。AESGo 标准库crypto/aes和crypto/cipher支持 CBC、GCM 等模式。关键点必须确保 Go 中使用的填充方式如 PKCS7、初始向量IV生成方式、字符编码等与前端完全一致。一个字节的差异都会导致解密失败。密钥管理如何获取 RSA 私钥绝对不要从生产环境前端代码或通信中获取私钥。正确做法是测试环境使用测试环境专用的密钥对私钥由测试团队安全保管并配置到 Yakit 插件中。开发/联调使用开发团队提供的密钥对。核心原则私钥只存在于你信任的环境中插件中的私钥应以配置项或环境变量的方式传入切勿硬编码在代码里。这个架构的优势在于“无侵入性”。前端应用无需任何修改整个加解密过程对它完全透明。我们只是在通信链路上增加了一个“理解”密文的智能中间件。3. 实战构建 Yakit 加解密插件理论清晰后我们进入实战环节。假设我们已分析出前端使用RSA-OAEP加密 AES 密钥使用AES-256-CBC加密业务数据且响应体也使用相同的 AES 密钥加密。3.1 插件基础框架搭建在 Yakit 中进入MITM 插件模块点击“编写新插件”。一个基本的插件结构如下package main import ( encoding/base64 encoding/json fmt strings github.com/yaklang/yakit github.com/yaklang/yakit/static_assets/generated/data ) // 定义全局配置用于存储密钥等敏感信息实际应从文件或环境变量读取 var ( rsaPrivateKeyPEM -----BEGIN PRIVATE KEY----- 你的RSA私钥内容 -----END PRIVATE KEY----- currentAESKey []byte // 用于存储当前会话的AES密钥 ) // 处理HTTP请求的入口函数 func handleRequest(req *yakit.MITMRequest) *yakit.MITMRequest { // 1. 判断是否是目标加密接口 if !strings.Contains(req.Url, /api/encrypted-endpoint) { return req // 非目标接口直接放行 } // 2. 解析请求体假设是JSON格式包含encryptedKey和encryptedData var reqBody map[string]string if err : json.Unmarshal(req.Body, reqBody); err ! nil { yakit.Output([-] 解析请求体失败: err.Error()) return req } encKeyB64, ok1 : reqBody[encryptedKey] encDataB64, ok2 : reqBody[encryptedData] if !ok1 || !ok2 { yakit.Output([-] 请求体中未找到加密字段) return req } // 3. 解密AES密钥 aesKey, err : decryptRSAKey(encKeyB64) if err ! nil { yakit.Output([-] 解密AES密钥失败: err.Error()) return req } currentAESKey aesKey // 存储起来用于解密响应 yakit.Output(fmt.Sprintf([] 当前AES密钥: %x, aesKey)) // 4. 解密业务数据 plainData, err : decryptAESData(encDataB64, aesKey) if err ! nil { yakit.Output([-] 解密业务数据失败: err.Error()) return req } yakit.Output([] 解密后的请求体: string(plainData)) // 5. (可选) 修改明文数据。例如修改测试账号的密码。 var dataMap map[string]interface{} json.Unmarshal(plainData, dataMap) if username, ok : dataMap[username]; ok username testUser { dataMap[password] modifiedPassword123 newPlainData, _ : json.Marshal(dataMap) plainData newPlainData yakit.Output([] 已修改请求明文数据) } // 6. 重新加密业务数据如果修改了的话 newEncDataB64, err : encryptAESData(plainData, aesKey) if err ! nil { yakit.Output([-] 重新加密数据失败: err.Error()) return req } // 7. 更新请求体发送给服务器 reqBody[encryptedData] newEncDataB64 newBody, _ : json.Marshal(reqBody) req.Body newBody // 8. 在Yakit界面中这个请求的详情里将显示我们解密的明文便于调试 req.SetPlainBody(plainData) // 这是一个关键方法让密文请求在History中显示为明文 return req } // 处理HTTP响应的入口函数 func handleResponse(rsp *yakit.MITMResponse) *yakit.MITMResponse { if !strings.Contains(rsp.Url, /api/encrypted-endpoint) || currentAESKey nil { return rsp } // 1. 解密响应体假设响应体是直接AES加密的base64字符串 encRespB64 : string(rsp.Body) plainResp, err : decryptAESData(encRespB64, currentAESKey) if err ! nil { yakit.Output([-] 解密响应体失败: err.Error()) return rsp } yakit.Output([] 解密后的响应体: string(plainResp)) // 2. (可选) 修改响应数据。例如强制让某个接口返回错误状态。 // var respMap map[string]interface{} // json.Unmarshal(plainResp, respMap) // respMap[code] 500 // respMap[message] Mock Server Error // newPlainResp, _ : json.Marshal(respMap) // plainResp newPlainResp // 3. 重新加密响应数据 newEncRespB64, err : encryptAESData(plainResp, currentAESKey) if err ! nil { yakit.Output([-] 重新加密响应失败: err.Error()) return rsp } // 4. 更新响应体 rsp.Body []byte(newEncRespB64) // 5. 同样设置明文响应以便查看 rsp.SetPlainBody(plainResp) return rsp } // 以下为具体的加解密函数实现需根据前端实际算法填充 func decryptRSAKey(encryptedKeyB64 string) ([]byte, error) { // 实现RSA解密逻辑返回AES密钥字节 // 1. base64解码 // 2. 解析PEM格式私钥 // 3. 使用rsa.DecryptOAEP解密 // 伪代码... return []byte(decrypted_aes_key_32_bytes), nil } func decryptAESData(encryptedDataB64 string, key []byte) ([]byte, error) { // 实现AES-CBC解密逻辑 // 1. base64解码 // 2. 提取IV可能拼接在密文前或由固定算法生成 // 3. 使用cipher.NewCBCDecrypter解密 // 4. 去除PKCS7填充 // 伪代码... return []byte({data: decrypted}), nil } func encryptAESData(plainData []byte, key []byte) (string, error) { // 实现AES-CBC加密逻辑与前端对应 // 1. PKCS7填充 // 2. 生成随机IV // 3. 使用cipher.NewCBCEncrypter加密 // 4. 将IV和密文拼接然后base64编码 // 伪代码... return new_encrypted_data_b64, nil } // 插件初始化注册处理函数 func init() { yakit.RegisterMITMHandler(handleRequest, handleResponse) }注意以上代码是高度简化的框架核心在于展示handleRequest和handleResponse的流程以及SetPlainBody这个关键方法。真正的decryptRSAKey,decryptAESData,encryptAESData函数需要你根据前端具体的加密库和参数完整实现。这是整个项目中最需要耐心和细心的部分。3.2 插件调试与集成编写完插件代码后你需要本地测试加解密函数先在独立的 Go 程序中测试你的加解密函数确保能用测试密钥成功解密前端发出的数据包。可以先用 Python 或 Node.js 模拟前端生成一组密文然后用你的 Go 函数解密验证。在 Yakit 中加载插件将插件代码粘贴到 Yakit 编辑器中保存并启用它。配置 MITM 证书确保浏览器或手机已安装 Yakit 的根证书以解密 HTTPS 流量。启动 MITM 服务器在 Yakit 中启动 MITM并配置代理到你的浏览器或测试设备。触发加密请求访问你的目标 Web 应用触发加密接口调用。观察输出在 Yakit 的插件日志和控制台查看输出。如果一切顺利你将在History页面看到原本是密文的请求和响应现在旁边多了一个“明文”的标签页里面就是你解密后的 JSON 数据。这个过程可能会遇到很多坑比如 IV 提取错误、填充模式不对、编码问题等。耐心查看错误日志对比前端加密后的字节流和你解密过程中的中间字节流是解决问题的关键。4. 实现自动化密文转发透明化拦截和解密查看只是第一步。我们的终极目标是“自动化”即让其他工具如自动化测试脚本、漏洞扫描器能像处理明文接口一样处理这个加密接口。这就需要实现“密文转发”。4.1 基于 Yakit 的“反向代理”模式Yakit 本身就是一个代理服务器。最直接的自动化方法就是让你的自动化脚本将请求发送到 Yakit 的代理地址如http://127.0.0.1:8083。Yakit 的 MITM 插件会自动对经过的特定请求进行解密和重新加密。操作流程保持上述加解密插件启用。你的 Python 自动化测试脚本使用requests库不再需要任何加密逻辑它只需要构造明文的 JSON 数据。# 自动化脚本示例 - 明文构造请求 import requests import json proxy {http: http://127.0.0.1:8083, https: http://127.0.0.1:8083} plain_payload { username: auto_test_user, action: query_balance } # 注意这里发送的是“模拟的明文请求”但需要告诉Yakit如何识别并加密它。 # 我们需要一个额外的“协议”或“标记”。 headers { Content-Type: application/json, X-Yakit-Encrypt: true # 自定义头告知插件此请求需要特殊处理 } # 直接发送明文 response requests.post( https://target.com/api/encrypted-endpoint, jsonplain_payload, headersheaders, proxiesproxy, verifyFalse # 忽略证书验证因为使用了Yakit的MITM证书 ) print(response.json()) # 期望得到解密的明文响应修改之前的 Yakit 插件不仅处理来自浏览器的请求也处理来自自动化脚本的、带有特殊标记如自定义 HTTP 头X-Yakit-Encrypt: true的请求。插件逻辑需要增加一个分支如果请求体是encryptedKeyencryptedData格式走原有的解密-修改-加密流程。如果请求头包含X-Yakit-Encrypt且请求体是明文 JSON则走明文-加密流程。插件读取明文用当前会话的或预配置的 AES 密钥加密并构造出前端标准的encryptedKey和encryptedData格式再转发给服务器。响应处理同理始终尝试解密并将明文通过SetPlainBody设置这样requests库收到的就是解密后的响应体。这样一来你的自动化脚本完全从加密算法中解放出来就像在测试一个明文 API 一样。所有的加解密脏活累活都由 Yakit 插件这个“智能网关”完成了。4.2 插件参数化与动态密钥管理在实际自动化场景中不同测试用例可能需要不同的用户身份而每个用户的登录会话可能对应不同的 AES 密钥。静态配置一个currentAESKey是不够的。解决方案会话Session映射在插件中维护一个全局的map用于存储sessionIdentifier - aesKey的映射。sessionIdentifier可以从请求的 Cookie如JSESSIONID、Authorization Header 或其他业务 token 中提取。在handleRequest中解密得到 AES 密钥后将其存入 map。var sessionKeyMap make(map[string][]byte) func handleRequest(req *yakit.MITMRequest) *yakit.MITMRequest { // ... 解密得到 aesKey ... sessionId : extractSessionId(req) if sessionId ! { sessionKeyMap[sessionId] aesKey } // ... }在handleResponse中根据当前请求的 sessionId 从 map 里取出对应的 AES 密钥来解密响应。对于来自自动化脚本的、不带密文只带明文的请求插件需要根据脚本传入的 sessionId可以通过自定义头X-Session-Id传递从 map 中查找对应的 AES 密钥用于加密请求。通过这种方式插件可以同时处理多个并发用户的加密流量并正确地为自动化脚本提供转发服务。4.3 集成到 CI/CD 流水线将这套方案集成到 Jenkins、GitLab CI 等自动化流水线中就构成了真正的“自动化密文接口测试”。准备测试环境在专用的测试服务器上部署一个运行 Yakit 的容器或常驻进程并加载好针对测试环境的加解密插件。配置测试密钥将测试环境的 RSA 私钥安全地注入到该容器的环境变量或配置文件中。编写测试脚本如上所述测试脚本只需关心业务逻辑通过代理将请求发送给 Yakit 服务器。流水线任务在 CI 任务中启动待测应用然后执行测试脚本。脚本的代理地址指向测试服务器上的 Yakit 实例。结果收集测试脚本根据解密的响应结果进行断言生成测试报告。这样无论是开发提交后的接口回归测试还是 nightly build 的全面测试都能覆盖到加密接口极大提升了测试的深度和自动化程度。5. 避坑指南与实战心得这条路我走过坑也踩过不少。下面这些经验希望能帮你节省大量时间。5.1 加解密对齐的魔鬼细节这是失败率最高的环节。前端加密后是一个 Base64 字符串但这个字符串背后可能隐藏着多个步骤字符编码前端 JavaScript 字符串是 UTF-16但加密操作通常在字节数组层面进行。确保在 Go 中解密时对 Base64 解码后的字节数组进行操作而不是字符串。IV 的处理AES-CBC 模式必须要有 IV。前端和 Go 的 IV 来源必须一致。常见情况有IV 是固定的、IV 是随机生成并拼在密文前一起做 Base64、IV 由密钥派生而来。一定要用相同的明文和密钥分别在前端和你的 Go 测试代码中加密对比生成的密文 Base64 字符串是否完全一致。这是验证算法对齐的唯一金标准。填充模式AES 块加密需要填充。PKCS7 和 PKCS5 在 AES 上通常是等价的但实现时要确认。Go 的cipher包不自动处理填充需要自己实现。RSA 密钥格式前端使用的公钥可能是 PKCS8 格式而 Go 的x509.ParsePKCS8PrivateKey和ParsePKCS1PrivateKey有区别。用openssl命令检查你的私钥格式。5.2 Yakit 插件开发与调试技巧善用yakit.Output()这是你调试插件的眼睛。把关键变量如解密后的密钥、明文片段打印出来。分阶段开发不要试图一次性写完所有功能。先写一个插件只做一件事打印出目标请求的 URL 和 Body确认能捕获到。然后增加 Base64 解码再增加解密逻辑。错误处理要健壮加解密过程很容易 panic。一定要用recover或在关键函数外做好错误处理避免因为一个请求处理失败导致整个 MITM 代理崩溃。注意性能RSA 解密是 CPU 密集型操作。如果流量很大可能会成为瓶颈。可以考虑缓存解密后的 AES 密钥避免对每个请求都进行 RSA 解密。5.3 安全与合规红线必须再三强调仅用于授权测试这套方案只能在你拥有完全测试权限的系统上使用例如你自己开发的项目、公司内部的测试环境、获得明确书面授权的渗透测试活动。保护密钥测试用的 RSA 私钥是最高机密。不要提交到代码仓库不要写在插件明文里。通过环境变量、外部配置文件在.gitignore中或启动参数传入。尊重隐私解密后的数据可能包含真实用户的敏感信息即使在测试环境。妥善处理日志不要长期存储测试完成后及时清理。明确边界这个方案的目的不是“破解”加密而是在拥有合法密钥的前提下实现测试过程的自动化和透明化。其伦理定位与拥有源码进行调试是类似的。5.4 进阶处理更复杂的加密场景动态密钥协商如果前端每次会话都通过复杂的握手协议动态协商密钥如 ECDH插件复现的难度会指数级上升。这时可能需要更深入的分析甚至编写一个“无头浏览器”脚本来模拟前端在 Yakit 插件中调用这个脚本获取密钥。这属于高阶用法。加密与签名结合有些接口不仅加密还对请求体进行签名。插件在修改明文后必须重新计算签名。你需要同时复现前端的签名算法如 HMAC-SHA256。WebSocket 加密Yakit 同样支持 WebSocket 流量劫持和插件处理思路与 HTTP 类似但需要处理数据帧的解析和重组。实现前端加密数据的透明化拦截与自动化转发就像为加密通信管道安装了一个“智能阀门”。这个阀门不仅能让数据流过还能实时翻译管道内的“暗语”并允许你按规则调整流量。Yakit 凭借其强大的 MITM 和插件化能力是实现这个“阀门”的绝佳平台。它将加解密的复杂性封装在插件内部对外则提供了明文化、可自动化的接口真正打通了安全测试、开发调试与自动化流程中的加密壁垒。