逆转!MitmProxy + WireGuard 中间人代理抓包

第 1 节 概述

WireGuard 可以创建点对点连接,让客户端在外网访问Nas的内网应用,那么我们把思路逆转过来,Nas 的内网当然也可以获取客户端的访问流量。

摘要

本文主要介绍如何通过 MitmProxy 配合WireGuard 抓取客户端的访问流量,并以解析某考试App为例,说明如何使用 python 程序解析流量,并修改返回结果。

第 2 节 MitmProxy

MitmProxy 是一个开源的中间人代理(Man-in-the-Middle Proxy),用于 HTTP 和 HTTPS 流量的捕获、修改和分析。它允许开发者、测试人员或安全专家查看、修改并调试客户端与服务器之间的通信。常见的用途包括:

  1. 流量拦截和分析:可以监控和分析通过代理的 HTTP/HTTPS 请求和响应。
  2. 修改流量:实时修改请求或响应内容,例如修改响应的 JSON 数据,或者拦截请求以模拟不同的条件。
  3. 调试和测试:对 API 请求和 Web 应用进行调试,捕捉和测试不同的网络请求。
  4. SSL/TLS解密:MitmProxy 能通过生成自己的 SSL 证书来解密 HTTPS 流量,提供清晰的内容查看。

它支持命令行界面(CLI)、Web界面和 Python 脚本接口,可以高度自定义操作和扩展功能。

当然,也有一些功能比较类似的软件,比如 BurpSuite、Fiddler、TcpDump 等。

  • BurpSuite 和 Fiddler 桌面端更为常见,很少在 Docker 上面部署
  • TcpDump 在Linux 更为常见,可以在 Docker 上面部署,但是不支持 SSL/TLS 解密,所以获取到的数据包都是加密的

MitmProxy 这一类的软件直接部署在公网上面其实并不安全,因为其他人也可以通过 MitmProxy 发送流量来攻击设备,所以将 MitmProxy 和 WireGuard 配合使用,部署在内网中是比较合适的。

MitmProxy 原生就支持 WireGuard 模式,并且已经实现了对 WireGuard 协议流量的解析,部署起来相当方便。

2.1 部署 Docker 镜像

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
docker run -itd --name mitmproxy --restart always \
-v /PATH/mitmproxy/mitmproxy:/home/mitmproxy/.mitmproxy \
-v /PATH/mitmproxy/scripts:/scripts \
-p PORT1:8081 \
-p PORT2:51820/udp \
mitmproxy/mitmproxy \
mitmweb \
--web-host 0.0.0.0 \
--mode wireguard \
-s /scripts/main.py

MitmProxy 有三种工具,详见网页,我这里使用了 mitmweb,使用mitweb时,需要映射8081端口用来访问 Web 界面。

  • mitmproxy(命令行模式)提供了一个交互式的命令行界面(CLI)。它允许用户实时查看、修改、重放 HTTP/HTTPS 流量。
  • mitmweb(Web 界面模式)Web 界面版本,提供了一个基于浏览器的图形化界面来查看和修改流量。
  • mitmdump(批量模式)类似于 TcpDump,它以非交互式方式捕获和处理流量,并将数据输出到命令行或保存到文件中。

MitmProxy 有几种运行模式,详见网页,我这里使用的是 WireGuard 模式,使用该模式时,需要映射51820/udp端口用来代理,并且映射的端口要能够公网访问。使用其他模式时,使用的是8080端口作为代理。

在此模式下,MitmProxy 运行内部 WireGuard 服务器,设备可以使用标准 WireGuard 客户端应用程序连接到该服务器。

信息

这里的main.py是需要自己编写的,详见下文。

2.2 连接到服务器

使用浏览器访问mitmweb界面,可以在Capture页面找到二维码,扫码即可添加连接

https://img.papergate.top:5000/i/2025/01/677a5a5744421.webp

在容器的/home/mitmproxy/.mitmproxy文件夹下也能找到wireguard.conf记录了客户端的公钥和私钥,已经在构建容器时映射到外部了。

2.3 安装证书

手机连接 MitmProxy 服务器后访问页面,选择合适的证书下载并安装

1
http://mitm.it

通用 -> VPN与设备管理 -> mitmproxy

https://img.papergate.top:5000/i/2025/01/677a57f48846f.webp 通用 -> 关于本机 -> 证书信任设置 -> 针对根证书启用完全信任

https://img.papergate.top:5000/i/2025/01/677a584fb87e7.webp

2.4 编写处理程序

以实现解析某考试 App 为例,讲述如何编写程序,我们的目标是获取到该 App 返回的题目数据,并且把正确的选项内容改成选我,错误的选项内容改成-

获取数据格式

先获取数据格式,可以看到,返回的数据是 Json 格式

https://img.papergate.top:5000/i/2025/01/677a5fb84616f.webp

分析数据,选项主要存储在OPTION_CONTENTQUESTION_OPTIONS_CONTENT

编写程序

随着不断使用,解析程序会越来越多,为了保证可扩展性,使用addons来编写程序

编写exam.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
from mitmproxy import http
import json

class Exam:
    def response(self,flow) -> None:
        # 检查请求的 URL 是否包含 'getMockQuestionList'
        if 'getMockQuestionList' in flow.request.url and flow.response.headers.get("content-type", "").startswith("application/json"):
            try:
                # 解析 JSON 数据
                data = json.loads(flow.response.content)
                
                # 修改每道题目的选项内容
                if "data" in data and "question" in data["data"]:
                    for question in data["data"]["question"]:
                        right_answer = question.get("RIGHT_ANSWERS", "")
                        option_content = question.get("OPTION_CONTENT", "")
                        options = option_content.split("|")
    
                        # 创建一个新的选项内容列表
                        modified_options = []
                        modified_content = []
                        for label in ['A','B','C','D','E','F','G','H'][0:len(options)]:
                            if label in right_answer:
                                modified_options.append(f'{label}、选我')
                                modified_content.append('选我')
                            else:
                                modified_options.append(f'{label}、-')
                                modified_content.append('-')
                        # 更新 QUESTION_OPTIONS_CONTENT 为修改后的选项
                        question["OPTION_CONTENT"] = "|".join(modified_options)
                        question["QUESTION_OPTIONS_CONTENT"] = "|".join(modified_content)
                
                # 将修改后的数据重新赋值给响应内容
                flow.response.content = json.dumps(data, ensure_ascii=False).encode("utf-8")
            except Exception as e:
                print(f"Error processing response: {e}")


addons = [
    Exam()
]
扩充main.py
1
2
3
4
5
from exam import Exam

addons = [
    Exam()
]

运行结果

https://img.papergate.top:5000/i/2025/01/677a62f0148a5.webp

https://img.papergate.top:5000/i/2025/01/677a63f87dec6.webp

正确答案一目了然,轻松获得了 100 分。