Python脚本化mitmproxy:实现HTTP/HTTPS流量自动化监控与修改 1. 项目概述从“中间人”到“脚本化”的深度掌控在网络安全测试、移动应用调试乃至日常的网络行为分析中我们常常需要窥探或修改设备与服务器之间的通信数据。传统的抓包工具如Wireshark擅长“看”但想要“改”或者“自动化地处理”就显得力不从心。这时mitmproxy就登场了。它不仅仅是一个代理服务器更是一个功能强大的交互式中间人攻击Man-in-the-Middle, MITM工具。而当我们谈论“Python脚本化中间人攻击”时核心就是将mitmproxy的可编程能力发挥到极致——通过编写Python脚本实现对HTTP/HTTPS流量的实时监听、拦截、修改、重放以及自动化分析将一次性的手动操作转变为可重复、可定制、可集成的自动化流程。简单来说这个项目就是教你如何用Python给mitmproxy装上“大脑”让它能按照你的逻辑去智能地处理网络流量。无论是安全研究员想自动化测试API接口的漏洞还是开发人员想模拟各种网络异常场景如断网、延迟、返回特定错误码亦或是数据分析师想批量采集和处理某些网络请求掌握这项技能都能让你从被动的流量观察者变为主动的流量操控者。它解决的痛点非常明确提升网络协议交互测试与分析的效率和深度。2. 核心原理与工具选型解析2.1 中间人攻击MITM的本质与前提要玩转mitmproxy必须理解中间人攻击的基本原理。它并非一个贬义词在授权测试和开发调试场景下它是一个中性且强大的技术手段。其核心在于“代理”和“证书”代理劫持你需要让目标设备手机、电脑、另一台服务器的所有网络流量都先经过你运行mitmproxy的机器。这通常通过配置系统的网络代理设置如http_proxy和https_proxy环境变量或设置Wi-Fi的代理服务器来实现。此时你的机器就成了流量的必经之路。证书欺骗针对HTTPSHTTPS协议通过SSL/TLS加密来防止窃听和篡改。为了解密HTTPS流量mitmproxy会动态生成一个针对目标域名的“伪造”证书。目标设备必须安装并信任mitmproxy的根证书CA证书才会认为这个伪造证书是可信的从而建立加密连接。这样mitmproxy就能以“中间人”的身份同时与客户端和服务器端建立独立的TLS连接解密、查看、修改两端的明文数据后再重新加密转发。注意此技术仅限用于你拥有完全控制权的设备如你自己的手机、测试服务器或已获得明确书面授权的测试目标。在任何其他场景下使用都可能构成违法行为。2.2 为什么是 mitmproxy Python市面上抓包工具很多为什么偏偏是这对组合mitmproxy 的优势跨平台纯Python编写在Windows、macOS、Linux上都能完美运行。支持HTTPS开箱即用地提供HTTPS解密功能证书管理方便。交互式与脚本化并存既提供了mitmwebWeb界面和mitmdump命令行两种交互模式更核心的是其强大的脚本支持。清晰的API其事件驱动型的API设计非常直观例如request,response,error等事件让编写处理逻辑如同搭积木。Python 脚本化的价值无限扩展你可以用Python实现任何你能想到的流量处理逻辑基于正则表达式或关键词过滤请求、自动修改请求头/体、根据响应内容决定是否重试、将特定数据存入数据库、甚至与你的其他自动化测试框架如pytest集成。提升效率手动测试一个需要修改Cookie的流程可能需要重复操作十几次。一个脚本可以自动为所有相关请求添加或修改Cookie一次运行全部搞定。复杂场景模拟模拟网络延迟、随机丢包、返回自定义错误页面、批量替换响应中的资源链接等这些在脚本里就是几行代码的事。3. 环境搭建与基础配置3.1 安装 Python 与 mitmproxy首先确保你有一个健康的Python环境3.6以上。我强烈建议使用虚拟环境来管理项目依赖避免包冲突。# 1. 创建并进入项目目录 mkdir mitm-scripts cd mitm-scripts # 2. 创建Python虚拟环境以venv为例 python -m venv venv # 3. 激活虚拟环境 # Windows: venv\Scripts\activate # macOS/Linux: source venv/bin/activate # 4. 安装 mitmproxy pip install mitmproxy安装成功后你会得到三个命令mitmproxy终端交互式、mitmwebWeb交互式、mitmdump命令行式最常用于结合脚本。3.2 安装并信任 CA 证书这是解密HTTPS流量的关键一步也是最容易踩坑的地方。启动 mitmproxy首先在命令行运行mitmweb或mitmdump它会自动在~/.mitmproxy用户目录下生成证书文件。找到证书在~/.mitmproxy目录下你会看到mitmproxy-ca-cert.cer(DER格式) 和mitmproxy-ca-cert.pem(PEM格式)。对于大多数系统使用.pem或.cer均可。安装到系统Windows双击.cer文件选择“安装证书” - “当前用户” - “将所有证书放入下列存储” - “受信任的根证书颁发机构”。macOS双击.cer文件会打开钥匙串访问。找到名为mitmproxy的证书双击打开在“信任”部分将“使用此证书时”设置为“始终信任”。Linux (Ubuntu为例)将.pem证书复制到/usr/local/share/ca-certificates/然后运行sudo update-ca-certificates。安装到移动设备确保手机和运行mitmproxy的电脑在同一局域网。在手机浏览器中访问http://mitm.it。这是一个由mitmproxy提供的特殊页面它会自动检测你的设备类型并提供相应格式的证书安装指南和下载链接。按照页面指引下载并安装证书在iOS中安装后还需在“设置”-“通用”-“关于本机”-“证书信任设置”中完全信任该根证书。实操心得很多人在手机上装了证书却依然抓不到HTTPS包90%的原因是证书没有在系统级被完全信任。特别是iOS安装证书和信任证书是两个独立步骤缺一不可。安卓各厂商设置路径也不同可能需要到“设置”-“安全”-“加密与凭据”-“安装证书”中查看。3.3 配置代理与首次抓包启动代理在电脑上运行mitmweb -p 8080。-p指定代理端口默认为8080。配置设备代理在目标设备手机或另一台电脑的网络设置中配置手动代理服务器地址填写运行mitmproxy的电脑的局域网IP地址如192.168.1.100端口填写8080。验证在目标设备上访问任意HTTP网站如http://httpbin.org/get然后在mitmweb的Web界面默认http://127.0.0.1:8081中应该能看到捕获到的请求和响应。如果能看到明文HTTP请求但HTTPS请求显示为“Tunnel to …”说明证书信任环节可能有问题请返回上一步检查。4. Python 脚本化核心事件驱动模型mitmproxy的脚本化核心是事件驱动。你编写的Python脚本本质上是一系列事件处理函数的集合。mitmdump加载你的脚本并在特定事件发生时调用对应的函数。4.1 脚本基本结构创建一个名为my_script.py的文件#!/usr/bin/env python3 一个简单的 mitmproxy 脚本示例 def request(flow): 每个HTTP请求到达时触发 :param flow: mitmproxy.http.HTTPFlow 对象包含请求和响应的所有信息 # 打印请求的URL print(f拦截到请求: {flow.request.url}) # 你可以在这里修改 flow.request 的任何属性 def response(flow): 每个HTTP响应返回时触发 # 打印响应状态码和内容类型 print(f收到响应: {flow.response.status_code}, Content-Type: {flow.response.headers.get(Content-Type, )}) # 你可以在这里修改 flow.response 的任何属性使用脚本运行mitmdumpmitmdump -s my_script.py现在所有流经代理的请求和响应都会触发你在脚本中定义的request和response函数并在控制台打印信息。4.2 核心事件与 HTTPFlow 对象除了request和response还有其他有用的事件例如http_connect用于处理HTTPS隧道建立、error等。但最核心的是HTTPFlow对象它是单个HTTP事务的完整封装。一个HTTPFlow对象主要包含flow.request: 一个mitmproxy.http.Request对象。flow.request.url: 完整的请求URL。flow.request.method: 请求方法GET, POST等。flow.request.headers: 请求头类字典对象。flow.request.content: 请求体bytes类型。通过flow.request.text可以获取解码后的文本如果编码已知。flow.response: 一个mitmproxy.http.Response对象在response事件中才可用。flow.response.status_code: 状态码。flow.response.headers: 响应头。flow.response.content: 响应体bytes类型。flow.response.text获取文本。flow.kill(): 一个强大的方法可以立即中断这个请求不再向前转发。常用于屏蔽广告或特定请求。4.3 实战修改请求与响应让我们看几个最常见的脚本操作。示例1为所有请求添加一个特定的Headerdef request(flow): # 添加一个自定义头用于标识流量经过代理 flow.request.headers[X-My-Mitm-Proxy] Injected # 修改User-Agent伪装成浏览器 flow.request.headers[User-Agent] Mozilla/5.0 (My-Custom-Agent)示例2拦截并修改特定API的响应假设我们想修改一个返回JSON的API的响应内容。import json def response(flow): # 检查是否是目标API if /api/user/profile in flow.request.url: # 确保响应是JSON if bapplication/json in flow.response.headers.get(Content-Type, ).encode(): try: # 解析原始JSON original_data json.loads(flow.response.content) # 篡改数据 original_data[username] HackedByMitm original_data[premium] True # 将修改后的数据重新设置为响应体 flow.response.text json.dumps(original_data) # 注意修改了content后可能需要更新Content-Length头 # mitmproxy 通常会自动处理但在某些严格场景下需手动更新 # del flow.response.headers[Content-Length] # flow.response.headers[Content-Length] str(len(flow.response.content)) print(f已修改响应: {flow.request.url}) except json.JSONDecodeError: print(f响应不是有效JSON: {flow.request.url})示例3实现一个简单的“断网攻击”屏蔽特定请求这模拟了网络中断或屏蔽特定域名的效果。def request(flow): # 屏蔽所有对广告域名和追踪域的请求 blocked_domains [ads.example.com, tracker.somecdn.com, analytics.google.com] from urllib.parse import urlparse host urlparse(flow.request.url).hostname if host in blocked_domains: print(f屏蔽请求: {flow.request.url}) flow.kill() # 关键直接杀死这个请求客户端会收到连接错误 # 或者你也可以返回一个自定义的错误响应 # flow.response mitmproxy.http.Response.make( # 403, # 状态码 # bBlocked by mitmproxy, # 响应体 # {Content-Type: text/plain} # 响应头 # )5. 高级脚本技巧与项目实战5.1 状态保持与跨请求操作有时你需要在一个会话多个连续请求中保持状态例如记录登录后的Cookie并在后续请求中使用。mitmproxy提供了flow.metadata这个字典来存储自定义数据它在同一个flow的生命周期内有效。但对于跨请求的状态你需要使用全局变量或外部存储如文件、内存数据库。示例自动在登录后为请求添加Auth Token# 使用一个简单的全局变量存储token注意在多线程环境下需考虑线程安全 AUTH_TOKEN None def response(flow): global AUTH_TOKEN # 假设登录接口返回token if flow.request.path /api/login and flow.response.status_code 200: try: data json.loads(flow.response.content) AUTH_TOKEN data.get(token) print(f获取到新Token: {AUTH_TOKEN}) except: pass def request(flow): global AUTH_TOKEN # 为需要认证的API添加Token头 if AUTH_TOKEN and flow.request.path.startswith(/api/protected): flow.request.headers[Authorization] fBearer {AUTH_TOKEN}5.2 处理HTTPS与SSL/TLS解密脚本本身无需特别处理HTTPS解密只要客户端信任了CA证书mitmproxy会自动完成解密你的脚本看到的就是明文。但有时会遇到**证书绑定SSL Pinning**的应用程序如某些银行APP、微信它们会校验服务器证书是否与预置的特定证书匹配从而拒绝mitmproxy的伪造证书。应对证书绑定是一个更高级的话题通常需要反编译APP、修改代码或使用Frida等动态插桩工具来绕过这超出了基础脚本的范畴。但在脚本层面你可以尝试通过flow.server_conn获取一些TLS连接信息。5.3 实战项目自动化API模糊测试框架我们可以构建一个简单的框架自动对经过代理的API进行参数模糊测试。import copy import json from mitmproxy import http # 定义要测试的参数和payload FUZZ_PAYLOADS [ None, # 置空 , # 空字符串 9999999999, # 超大数 -1, # 负数 0, true, false, OR 11, # SQL注入尝试 scriptalert(1)/script, # XSS尝试 ../../../etc/passwd, # 路径遍历 非常非常非常长的字符串 * 100, ] def fuzz_params(params, param_name): 生成参数变异后的请求列表 fuzzed_flows [] for payload in FUZZ_PAYLOADS: new_params copy.deepcopy(params) new_params[param_name] payload # 这里需要构造新的flow实际中更常用的是记录下变异然后重放原始flow # 本例为简化仅打印信息 fuzzed_flows.append((param_name, payload, new_params)) return fuzzed_flows def request(flow: http.HTTPFlow): # 只测试POST的JSON API if flow.request.method POST and application/json in flow.request.headers.get(Content-Type, ): try: original_data json.loads(flow.request.text) for param_name in original_data.keys(): if isinstance(original_data[param_name], (str, int, float, type(None))): fuzzed fuzz_params(original_data, param_name) for fuzz_name, payload, new_params in fuzzed: print(f[FUZZ] 目标: {flow.request.url}) print(f 参数: {fuzz_name} - {payload}) print(f 变异后数据: {new_params}) # 在实际框架中这里会克隆flow修改数据然后通过mitmproxy的ctx模块重放请求 # 并对比响应状态码、内容长度、错误信息等以发现潜在漏洞 except json.JSONDecodeError: pass # 一个简单的响应分析器 def response(flow: http.HTTPFlow): # 检查是否有可疑的响应特征 suspicious_keywords [error, exception, sql, stack trace, undefined] if flow.response.text: resp_text_lower flow.response.text.lower() for keyword in suspicious_keywords: if keyword in resp_text_lower: print(f[SUSPICIOUS] 请求 {flow.request.url} 的响应中包含关键字: {keyword}) # 可以在这里记录到文件或数据库供后续分析这个框架只是一个起点你可以扩展它集成进SQLMap的检测规则或者自动对比正常响应与模糊测试响应的差异实现一个轻量级的自动化API安全扫描器。6. 常见问题排查与调试技巧即使按照步骤操作也难免会遇到问题。这里记录一些常见的坑和解决方法。问题1手机/客户端装了证书但HTTPS流量还是抓不到显示Tunnel或连接失败。排查证书信任这是最常见原因。确保在设备上不仅安装了证书而且在系统级完全信任了它iOS的“证书信任设置”安卓的“用户凭据”或“加密与凭据”。代理设置确认设备代理的IP和端口是否正确且电脑防火墙允许了该端口的入站连接。APP证书绑定尝试用系统浏览器访问http://mitm.it如果能正常显示页面并安装证书说明代理基本通畅。如果某个特定APP抓不到包很可能是它使用了证书绑定。需要尝试绕过。网络环境有些公司网络或公共Wi-Fi会强制使用自己的代理或进行流量过滤导致你的代理失效。尝试切换到手机热点环境测试。问题2脚本修改了请求/响应但客户端或服务器端没生效。排查事件顺序确保你在正确的事件中修改。修改请求必须在request事件中修改响应必须在response事件中。修改时机request事件在请求发出前触发response事件在响应返回后、发送给客户端前触发。确认你的逻辑符合这个时序。内容编码直接修改flow.request.content或flow.response.content是最可靠的它们是字节。使用flow.request.text或flow.response.text时mitmproxy会尝试解码/编码如果编码不匹配特别是非UTF-8的中文可能导致乱码或错误。可以先用flow.request.headers.get(Content-Type)判断编码再使用.decode(gbk)等方式处理。Headers修改了请求体后如果服务端严格校验Content-Length你可能需要删除旧的Content-Length头让mitmproxy或服务器重新计算。但现代HTTP库通常能处理。问题3脚本运行时报错或导入模块失败。排查Python路径确保运行mitmdump -s your_script.py时所在的虚拟环境或Python环境已经安装了脚本所需的所有第三方库如requests,json是内置的但pymongo等需要自己装。脚本权限确保脚本文件有可执行权限并且第一行#!/usr/bin/env python3指向正确的Python解释器。mitmproxy版本不同版本的mitmproxyAPI可能有细微变化。查看官方文档对应你版本的API说明。使用mitmproxy --version查看版本。问题4性能问题脚本运行导致代理变慢。优化减少阻塞操作避免在事件处理函数中进行耗时的网络IO或复杂计算。如果必须考虑使用异步或将其放入队列由后台线程处理。精准过滤在脚本开头尽早判断是否需要处理当前flow如果不需要就快速return。避免对所有流量都执行全套处理逻辑。使用mitmdump而非mitmwebmitmweb的Web界面本身会消耗资源。在生产或高强度测试环境使用无界面的mitmdump性能更好。调试技巧善用print最简单的调试方法在控制台输出关键变量和流程信息。使用日志模块更规范的做法是使用Python的logging模块可以设置不同级别DEBUG, INFO, ERROR并输出到文件。交互式调试在复杂脚本中可以在代码中插入import pdb; pdb.set_trace()来启动Python调试器但这会阻塞所有请求仅限开发阶段使用。检查flow对象在脚本中print(dir(flow))或print(flow.__dict__)可以查看flow对象的所有属性和方法有助于理解数据结构。掌握mitmproxy的Python脚本化相当于获得了一把网络流量手术刀。它让协议分析、安全测试、数据模拟和自动化开发工作流变得前所未有的灵活和强大。从简单的请求修改到构建复杂的自动化测试框架其可能性仅受限于你的Python编程能力和想象力。记住能力越大责任越大务必在合法合规的范围内使用这项技术。