基于mitmdump的前端加密请求拦截与修改实战指南
1. 项目概述与核心价值最近在和一些做数据采集、接口测试或者安全审计的朋友交流时经常听到一个共同的痛点现在越来越多的网站和App尤其是金融、电商、社交类应用前端交互不再是简单的明文传输了。登录、查询、提交订单这些关键操作数据在离开浏览器或App之前就被一套复杂的JavaScript代码加密得面目全非。你打开开发者工具看到的请求体不再是usernameadminpassword123456而是一串毫无规律的、长得像a7f8d9e0b1c2...这样的密文。这直接让传统的抓包、改包、自动化脚本调试变得异常困难仿佛面前立起了一堵高墙。这个项目就是专门用来“翻越”这堵墙的。它的核心是mitmdump一个基于Python的命令行版MitmProxy。我们不是要破解加密算法那是密码学家的事而是要在数据被加密前或者被解密后进行拦截和修改。想象一下你是一个海关官员货物数据在出口前端加密和进口服务端解密时都要经过你的检查站。我们的目标就是在检查站里看清货物的真面目甚至根据需要替换掉其中的一部分而整个过程对发货方浏览器和收货方服务器来说都是无感知的。这个“自用-练习”项目对于以下人群价值巨大爬虫工程师需要逆向加密逻辑构造合法请求获取数据。测试工程师需要测试服务端对异常或篡改数据的处理能力。安全研究人员需要分析前端与后端的数据交换过程寻找潜在漏洞。前端开发者需要调试自己编写的加密代码在实际网络中的表现。简单说它让你重新获得了对网络请求的“可见性”和“可控性”是应对日益普遍的前端加密方案的必备技能。下面我就把自己趟过坑、踩过雷的实战经验拆解成一步步可操作的方法。2. 核心思路与工具选型解析2.1 为什么是mitmdump面对前端加密通常有几种思路一是完全逆向JavaScript在本地模拟加密过程二是使用无头浏览器如Puppeteer执行完整页面逻辑。但这两种方法要么难度极高要么效率低下。而中间人代理Man-in-the-Middle Proxy提供了第三条路我不需要完全知道你怎么加密的我只需要在你加密前拿到原始数据或者在服务器返回密文后、浏览器解密前拿到响应结果。在众多代理工具中如Fiddler、Charles我选择mitmdump原因有三脚本化能力强它本质上是一个Python脚本的运行环境你可以用Python编写任意复杂的请求/响应处理逻辑灵活性远超其他图形化工具的断点功能。无头模式作为命令行工具它非常适合集成到自动化流程或服务器环境中无需图形界面。生态成熟MitmProxy社区活跃文档齐全遇到问题容易找到解决方案。它的工作原理很简单在你的设备上运行mitmdump设置它为系统或浏览器的代理。之后所有的HTTP/HTTPS流量都会先流经mitmdump再由它转发给真正的目标服务器。在这个过程中我们编写的Python脚本即“addon”就有机会对流量进行审查和修改。2.2 整体技术路线图我们的实战路线分为四个关键阶段这不仅仅是一个操作步骤更是一个理解数据流和制定策略的过程环境建立与流量捕获配置好mitmdump和客户端浏览器/手机确保能捕获到所有目标流量特别是HTTPS流量。这是基础但很多人在这里就卡住了。加密定位与逻辑分析从捕获到的加密请求出发逆向查找到负责加密的JavaScript代码位置。这需要一些前端调试技巧。脚本编写与逻辑复现在mitmdump的addon脚本中模拟或绕过加密逻辑。核心思路是在数据离开浏览器但尚未被前端JS加密时将其替换为我们想要的明文数据并让前端JS“以为”这是它自己加密的结果或者在服务器返回密文后我们先行解密查看内容。动态交互与自动化让我们的脚本能够处理复杂的、有状态交互如依赖Token、Session并实现自动化修改和转发。这个路线的核心思想是“拦截与替换”而非“破解与重建”。下面我们就进入第一个实操环节。3. 环境准备与HTTPS抓包配置3.1 安装与基础启动首先确保你的系统有Python3.6环境。使用pip安装mitmproxy是最简单的方式pip install mitmproxy安装完成后你会得到三个命令mitmproxy交互式终端UI、mitmwebWeb版UI和mitmdump命令行版。我们主要使用mitmdump。启动一个最简单的代理服务mitmdump -p 8080这会在本地的8080端口启动一个代理服务器。此时如果你将浏览器或系统的代理设置为127.0.0.1:8080就能看到mitmdump开始输出捕获到的请求日志。注意直接这样设置对于绝大多数HTTPS网站地址为https://开头你会看到证书错误警告。这是因为mitmdump作为中间人需要动态生成目标网站的证书而这个证书不被你系统中的根证书信任。3.2 配置HTTPS解密最关键的一步要让mitmdump成功解密HTTPS流量必须让客户端你的浏览器或测试设备信任mitmdump的根证书。生成并信任根证书 首次运行mitmdump或访问http://mitm.it时mitmproxy会生成一个唯一的根证书CA证书。你需要下载并安装它。在电脑上用设置了代理的浏览器访问http://mitm.it选择对应的操作系统如Windows、macOS下载证书并按照提示安装到“受信任的根证书颁发机构”。在手机上确保手机和电脑在同一局域网将手机Wi-Fi的代理设置为电脑的IP和mitmdump监听的端口如192.168.1.100:8080。然后用手机浏览器访问http://mitm.it下载并安装证书。对于iOS还需要在“设置-通用-关于本机-证书信任设置”中完全信任该根证书。启动支持解密的监听 使用以下命令启动-s参数指定我们的处理脚本稍后创建mitmdump -p 8080 -s my_addon.py验证HTTPS抓包 配置并信任证书后用浏览器访问https://www.baidu.com。在mitmdump的控制台你应该能看到完整的URL、请求头和响应头而不是一堆二进制乱码或TLS握手提示。如果看到 Cannot establish TLS with client之类的错误通常就是证书没有正确安装或信任。实操心得手机抓包是高频需求也是问题高发区。Android高版本和iOS对证书信任要求非常严格。一个常见坑是即使安装了证书某些App尤其是使用了证书绑定Certificate Pinning的依然无法抓包。对于这类App可能需要配合frida、objection等工具进行逆向解除证书绑定这属于更高级的范畴本项目暂不深入。4. 定位前端加密逻辑与逆向分析环境搭好能抓到包了现在我们面对一个满是密文的请求。下一步就是找到“加密黑盒”在哪里。4.1 从网络请求溯源寻找特征在开发者工具的Network标签中找到那个发送加密数据的请求通常是XHR或Fetch。查看其Initiator发起者栈它能告诉你是哪个JS文件、哪一行代码发起了这个请求。搜索关键词在发起请求的JS文件或全局搜索所有资源中搜索可能的关键词。这些关键词因加密方式而异常见的有加密函数名encrypt,encode,sign,RSA,AES,CryptoJS,JSEncrypt参数名查看加密请求的载荷Payload如果参数名是data、encryptedData、signature等就用它们作为关键词搜索。常量值有时加密后的字符串会包含固定的前缀或特征字符也可以尝试搜索。4.2 动态调试与逻辑跟踪单纯看代码可能很复杂我们需要让代码“跑起来”并设置断点。在加密前打断点在疑似加密的函数入口处或者发送网络请求的代码行如fetch或XMLHttpRequest.send处设置断点。观察调用栈和变量当断点触发时观察此时的调用栈Call Stack这能帮你理清加密函数的调用链条。最重要的是在Scope或Watch面板中查看传入加密函数的原始参数。这个原始参数很可能就是我们要找的明文数据。定位关键函数通过单步调试F10步入F11函数最终找到那个将明文变成密文的函数。它的输入是明文或一个包含明文的对象输出就是你在Network里看到的那串密文。4.3 一个典型的分析案例假设我们抓到一个登录请求请求体是{cipher: aBcDeFgHiJkL...}。通过上述方法我们可能发现在login.js中有一个函数叫submitLogin。submitLogin会调用一个来自utils/crypto.js的encryptData函数。调试发现encryptData接收一个对象{username: “xxx”, password: “yyy”, timestamp: 12345678}经过一系列处理可能是AES加密再用Base64编码最终生成了cipher的值。至此我们就完成了“定位”。我们知道了明文的结构和加密函数的位置。接下来我们的mitmdump脚本目标就很明确了在浏览器调用encryptData之前把{username: ..., password: ...}这个对象替换掉。5. mitmdump脚本编写实战拦截与修改这是整个项目的核心编码部分。我们将编写一个Python脚本例如bypass_encrypt.py并使用-s参数加载它。5.1 脚本的基本结构一个最基本的mitmdump addon脚本包含一个或多个request或response事件的处理器。# bypass_encrypt.py from mitmproxy import http def request(flow: http.HTTPFlow) - None: 每个请求经过时都会调用此函数 flow.request 包含请求的所有信息 # 1. 过滤目标请求 if “target-api.com” not in flow.request.pretty_host: return # 2. 检查是否是我们要处理的加密请求例如特定的路径 if flow.request.path.startswith(“/api/login”): # 3. 在这里编写我们的处理逻辑 process_login_request(flow.request) def response(flow: http.HTTPFlow) - None: 每个响应返回时都会调用此函数 flow.response 包含响应的所有信息 # 处理响应例如解密响应内容 pass # 具体的处理函数 def process_login_request(request): # 这里是核心逻辑 pass5.2 策略一直接替换请求体针对简单加密如果加密逻辑很简单或者我们只是想在加密前修改某个字段的值可以采用此策略。核心是在请求体被前端JS加密之前mitmdump是看不到明文的。所以我们需要“欺骗”前端。一种方法是我们不直接修改密文因为解不开而是修改即将被加密的原始数据。但这需要我们能注入JS代码。更实用的方法是如果我们通过逆向分析知道了加密后的密文和明文的对应关系就可以直接“伪造”密文。例如我们通过调试发现密码”123456”加密后总是变成”xyz789”。那么在脚本中我们可以这样做def request(flow: http.HTTPFlow) - None: if “login” in flow.request.path: import json # 假设原始请求体是JSON格式的密文 original_body flow.request.get_text() try: data json.loads(original_body) # 如果密文是我们已知的“123456”的加密结果就替换成“admin”的加密结果 # 这里“xyz789”和“abc000”是我们通过事先分析得到的对应关系 if data.get(“encryptedPassword”) “xyz789”: data[“encryptedPassword”] “abc000” # “admin”对应的密文 # 更新请求体 flow.request.set_text(json.dumps(data)) print(f“[] 已替换密码密文”) except: pass注意事项这种方法的前提是你必须事先知道至少一组明文和密文的映射关系并且加密是确定性的同样的明文每次加密结果相同。如果加密算法加入了随机因子如盐、IV向量每次结果都不同此方法失效。5.3 策略二注入JS代码在加密前修改明文通用性更强更强大的方法是利用mitmdump的响应修改功能在服务器返回的HTML或JS文件中注入我们自己的JavaScript代码。这段注入的代码会在页面环境中执行从而有机会在原始加密函数执行前修改其参数。这需要处理response事件def response(flow: http.HTTPFlow) - None: # 1. 定位到包含加密逻辑的JS文件 if flow.request.path.endswith(“.js”) and “crypto” in flow.request.path: # 2. 获取原始的JS内容 original_js flow.response.get_text() # 3. 在JS文件末尾或特定位置注入我们的Hook代码 hook_code “”” // 保存原始的加密函数 var originalEncrypt window.encryptData; if (originalEncrypt) { // 覆盖它 window.encryptData function(plainData) { console.log(“[MitmHook] 捕获到明文数据”, plainData); // 在这里修改plainData if (plainData.username) { plainData.username “hacked_username”; } if (plainData.password) { plainData.password “hacked_password”; } // 调用原始函数进行加密确保流程正常 return originalEncrypt.call(this, plainData); }; console.log(“[MitmHook] encryptData 函数已Hook成功”); } “”” modified_js original_js “\n” hook_code # 4. 更新响应内容 flow.response.set_text(modified_js) print(f“[] 已向 {flow.request.url} 注入JS Hook”)这个策略的威力在于它直接操作了前端JavaScript的执行环境修改的是加密函数的输入参数因此无论后端加密算法多复杂、是否包含随机数都能生效。因为最终的加密操作还是由网站自己的代码完成的只是数据被我们“调包”了。5.4 策略三处理响应尝试解密数据有时我们的目标不仅是修改发送的数据还想查看服务器返回的加密数据。思路类似如果能在前端解密函数执行前拦截到密文响应并尝试解密或者直接Hook解密函数。def response(flow: http.HTTPFlow) - None: # 处理API响应 if “/api/data” in flow.request.path: # 假设响应体是JSON其中data字段是密文 import json try: resp_data json.loads(flow.response.get_text()) encrypted_data resp_data.get(“data”) if encrypted_data: # 这里调用我们逆向出来的解密函数需要将JS解密逻辑用Python实现 # 或者如果我们注入了JS Hook也可以让前端解密后通过其他方式如另一个请求发给我们 # decrypted my_python_decrypt(encrypted_data) # print(f“[] 解密响应: {decrypted}”) pass except Exception as e: print(f“[-] 处理响应出错: {e}”)实现解密通常需要将JavaScript的加密/解密逻辑用Python重写一遍这涉及到对算法如AES, RSA和模式如CBC, ECB的深入理解是另一个技术挑战。6. 实战进阶处理复杂场景与状态保持真实的网站不会那么简单它们有Token、Session、验证码、请求签名等机制。6.1 处理Token和签名很多API的请求需要携带一个动态的Token在登录后获得并且可能对请求体或包含Token进行签名Sign以防止篡改。我们的脚本需要能处理这种链式依赖。捕获并存储Token在登录接口的响应中提取Token。token None def response(flow: http.HTTPFlow): global token if “/api/login” in flow.request.path: try: resp json.loads(flow.response.get_text()) token resp.get(“access_token”) print(f“[] 获取到Token: {token}”) except: pass自动添加Token到后续请求def request(flow: http.HTTPFlow): global token if token and “需要认证的接口路径” in flow.request.path: flow.request.headers[“Authorization”] f“Bearer {token}”处理签名如果请求有签名修改了请求体如密码后签名必然失效。我们需要要么逆向签名算法在Python中重新计算签名并更新。要么采用更彻底的JS Hook方法在计算签名的函数里同时修改原始数据和签名参数让它们匹配。这通常需要更精细的JS代码注入。6.2 处理流式传输与二进制数据有些应用可能使用Protocol Buffers、MessagePack等二进制格式或者WebSocket进行通信。mitmdump同样支持但处理方式不同。对于二进制请求体使用flow.request.get_content()获取字节数据修改后再用flow.request.set_content()设置。对于WebSocketmitmdump提供了websocket_message事件来处理WebSocket流量你可以在其中检查并修改消息内容。6.3 脚本的健壮性与调试异常捕获务必用try...except包裹你的处理逻辑避免因为一个请求处理出错导致整个代理崩溃。日志输出使用print输出关键信息如修改了哪个请求、替换了什么值。这能帮你确认脚本是否生效。条件过滤在脚本开头做好严格的URL、方法等条件过滤避免不必要的处理影响性能或误改其他网站请求。7. 常见问题排查与避坑指南在实际操作中你一定会遇到各种各样的问题。这里记录了几个最典型的“坑”和解决方案。问题现象可能原因排查步骤与解决方案mitmdump启动后浏览器无法上网1. 防火墙阻止了mitmdump的端口。2. 代理设置错误。3. 其他代理软件冲突。1. 检查防火墙设置允许mitmdump入站连接。2. 确认浏览器代理设置为127.0.0.1:8080(或你设置的端口)。3. 关闭VPN或其他代理工具。HTTPS网站显示证书错误/连接不安全1. mitmproxy的CA证书未安装。2. 证书已安装但未受信任。3. 目标网站使用了证书绑定。1. 访问http://mitm.it下载并安装证书。2. 对于系统/浏览器需将证书导入“受信任的根证书颁发机构”。3. 对于App可能需要使用frida等工具绕过证书绑定。能抓到包但请求体是乱码或TLS握手HTTPS流量未被成功解密。几乎可以肯定是证书信任问题。请严格按照上述步骤确保客户端完全信任了mitmproxy的CA证书。脚本已加载但print无输出请求未被修改1. 脚本路径错误或未加载。2. 脚本中的条件过滤如URL判断不匹配实际请求。3. 脚本存在语法错误静默失败。1. 检查-s参数后的文件路径是否正确。2. 在脚本开始加print(“脚本已加载”)测试。3. 在条件判断里打印flow.request.url确认是否命中目标请求。4. 单独运行python -m py_compile your_script.py检查语法。修改了请求但服务器返回签名错误请求被修改后破坏了原有的签名Sign或校验和。这是最难处理的情况。必须逆向签名算法1. 找到生成签名的JS函数。2. 在注入的Hook代码中先修改数据然后重新计算签名并替换。3. 或者尝试找到不校验签名的测试环境接口。注入的JS代码导致页面功能异常或报错1. 注入时机不对DOM或对象未加载。2. 注入的代码与原JS变量冲突。3. 修改了不可配置的属性。1. 尝试将注入代码包裹在setTimeout或DOMContentLoaded事件中确保页面加载完毕。2. 使用更独特的变量名避免冲突。3. 使用Object.defineProperty等更安全的方式Hook函数。手机抓包App无网络流量1. 手机代理设置未生效。2. App使用了纯TCP或自定义协议不走HTTP代理。3. App检测并禁用了代理。1. 确认手机和电脑在同一Wi-FiIP和端口正确。2. 尝试在手机上用浏览器访问http://mitm.it能打开说明代理通。3. 对于不走代理的App可能需要更底层的流量捕获工具如tcpdump。4. 对于禁用代理的App可能需要Xposed/EdXposed或太极等框架进行Hook。最重要的避坑技巧循序渐进逐个击破。不要试图一开始就写一个完美的、处理所有情况的脚本。先从最简单的HTTP站点开始确保能抓到包。然后找一个有简单加密的登录接口目标仅仅是修改密码并成功登录。把这个流程跑通你就能建立起信心和理解。之后再逐步增加复杂度如处理Token、处理签名、注入JS等。这个项目本质上是一场与前端开发者的“博弈”。它锻炼的不仅仅是工具使用能力更是逆向思维、代码调试和系统理解的能力。每一次成功的绕过都是对这些能力的一次提升。记住我们的目的是为了测试、学习或实现合法的自动化务必在授权和合法的范围内使用这些技术。