背景
大家好,我是逐日。最近我们做软件测试的同事遇到一些问题,测试app时,网络链路是这样的:app--》后端nginx的ip+端口--》后端服务。一般为了便于定位是前端app还是后端的问题,会使用fiddler、charles等进行网络抓包。常见的方法是这样的:手机和电脑连同一个wifi,保证手机可以访问电脑上的服务(telnet要通)。比如电脑ip:192.168.1.1,fiddler/charles监听8080端口,手机设置wifi的代理为192.168.1.1:8080,这种情况下,fiddler、charles就承担了代理服务器的工作。
问题在于,fiddler、charles基本只能代理http、https类型的流量,对于纯粹的tcp私有协议,那自然是没有办法。
最近,我们这边有两款app,都因为不同原因,不能抓了。
一个app是因为,app发出去的http流量,为了安全,会转换为tcp流量,在服务端的tcp网关,再还原为http流量,然后再传输给后端nginx。链路为:
app发出http--》app转换为tcp--》tcp网关(转换tcp为http)--》后端nginx(接收http流量)。
另一个app呢,也是因为安全问题,会将https报文中的body进行加密;服务端呢,是一个openresty(加强版nginx),lua脚本对流量进行解密,然后再发到后端java服务。
这两个的共同问题是,在后端没解密之前,抓到的报文都没法看,对测试同事是很不友好的。我们想要的,就是一个能在图形界面上实时看流量的软件,但明文部分的流量都在linux服务器上。
基于这个原因,最终找到了mitmweb这个linux上的代理软件。
解决方案--mitmweb
mitmweb官网:https://www.mitmproxy.org/
mitmweb简单理解,还是和fiddler等一样,就是个代理软件。
一张图解释下我怎么用的,老的链路如下:

新的链路如下,将mitmweb接到了nginx的后面:

从上图可以看出,nginx拿到的流量,被转发给了mitmweb,然后再由mitmweb转给了java。既然流量都从mitmweb过了,那就相当于一个中间人,那就什么事都能干了,比如像fiddler、charles那样去改请求、改响应等等,都可以。
其实linux上的各种代理软件挺多的,难的是有图形界面可以实时查看经过的流量,而mitmweb就暴露了一个端口出来,可以通过浏览器来查看,大概长下面这样:

mitmweb介绍
linux服务器上,能把网络流量进行图形界面展示的,确实是少数,更多的是命令行方式展示的,就像tcpdump那样。
而mitmweb其实也不例外,它还有两个兄弟命令:
-
mitmproxy,命令行方式展示:

-
mitmdump,可以抓包到文件里。
这几个命令的差异,可以看下这个官网文章:
https://docs.mitmproxy.org/stable/
-
mitmproxy,数据都保存在内存里,所以使用它时,流量不要太大。支持和人交互,实时检查和修改流量。我没用过,可自行探索
-
mitmweb,数据都保存在内存里,同样,流量不要太大。支持和人交互,实时检查和修改流量。
-
mitmdump,不支持和人交互,数据应该是流式的,不会一直存在内存里,支持把流量存到文件里。另外,支持指定自定义的python脚本(类似于hook),对流量进行各种操作。我感觉这个模式比较像xml解析的流式模式。
shellmitmdump -w outfile 保存流量到文件outfile中。 指定一个脚本,如下脚本会给响应都加上一个header: mitmdump -s examples/simple/add_header.py
另外,这几个命令,支持的平台包括:windows、mac、linux,当然,像在linux上,部分功能对内核会有些要求,具体看官网。
支持的协议
https://docs.mitmproxy.org/stable/concepts/protocols/
http1.0(含https)
http1.1(含https)
http2(含https)
http3(含https)
websocket
dns
tcp、udp四层代理
支持的代理方式
参考:https://docs.mitmproxy.org/stable/concepts/modes/
常规的正向代理
用于代理客户端。
看官网这个图,正向代理就是说,下图的客户端自始自终,都知道自己要访问的是 http://example.com ,由于一些网络限制或者什么原因,决定通过mitmproxy来进行中转。

代理某个软件、某个进程
https://docs.mitmproxy.org/stable/concepts/modes/#local-capture
shell
mitmproxy --mode local # Intercept everything on this machine. 本机全部流量
mitmproxy --mode local:curl # Intercept cURL only. 指定进程名
mitmproxy --mode local:42 # Intercept PID 42 only. 指定pid
这个着实有点惊艳,好像是这几年才出的功能,有点类似于proxifier那个付费软件。准备重点试试在windows上的这个功能。
在linux上呢,发现对内核要求有点高,说要Linux 6.8以上,我还以为我们的centos 7也能用上了呢,想多了。
WireGuard
这个模式好像是,mitmproxy 启动后,相当于一个WireGuard VPN server,然后WireGuard client都可以来连了。
这有篇文章:https://www.mitmproxy.org/posts/wireguard-mode/
好像是安卓手机直接扫码,就能连上了:

简单查了下,后续试试:

反向代理
类似于nginx了,这就是我这个场景下准备用的。

透明代理
比较复杂,后面研究,反正就是改路由表。
https://docs.mitmproxy.org/stable/concepts/modes/#transparent-proxy
tun interface
仅linux可用。
https://docs.mitmproxy.org/stable/concepts/modes/#tun-interface
Upstream Proxy
https://docs.mitmproxy.org/stable/concepts/modes/#upstream-proxy

SOCKS Proxy
正向代理,不过不是http那些协议,而是代理SOCKS5协议。
dns server
作为一个dns服务器,支持dns客户端查询。
linux上安装mitmweb
https://docs.mitmproxy.org/stable/overview/installation/
按官网意思,下载二进制是最推荐的。
https://www.mitmproxy.org/downloads/
我先选了个最新的版本:https://www.mitmproxy.org/downloads/#12.2.3/
我看下图也说支持docker等方式:

centos 7.9无法运行
shell
[root@year-account-nginx2 mitmproxy-12.2.3]# tar -zxvf mitmproxy-12.2.3-linux-x86_64.tar.gz
mitmproxy
mitmdump
mitmweb
由于缺少glibc_2.18版本,运行失败:

由于我的CentOS 7.9 的 glibc 是 2.17,所以二进制是跑不起来的。要跑只能docker跑。
但我决定换一台机器。
open-euler
换了一台open-euler的机器,这个glibc是没问题的。
shell
[root@xxx-openrestry ~]# cat /etc/os-release
NAME="openEuler"
VERSION="22.03 (LTS-SP4)"
ID="openEuler"
VERSION_ID="22.03"
PRETTY_NAME="openEuler 22.03 (LTS-SP4)"
ANSI_COLOR="0;31"
[root@xxx-openrestry ~]# ldd --version
ldd (GNU libc) 2.34
这个直接可以启动:
shell
[root@111-openrestry test]# ./mitmweb
[21:33:11.813] Using a plaintext password to protect the mitmweb user interface. Consider using an argon2 hash for `web_password` instead.
[21:33:11.818] HTTP(S) proxy listening at *:8080.
[21:33:11.820] Web server listening at http://127.0.0.1:8081/
[21:33:11.870] No web browser found. Please open a browser and point it to http://127.0.0.1:8081/
可以通过指定--web-host 0.0.0.0来允许ip访问:
shell
[root@VM-0-6-rockylinux mitmproxy-12.2.3]# ./mitmweb --web-host 0.0.0.0
[21:49:56.844] HTTP(S) proxy listening at *:8080.
[21:49:56.845] Web server listening at http://0.0.0.0:8081/?token=70854dcc18c57019425f836187e88e53
[21:49:56.978] No web browser found. Please open a browser and point it to http://0.0.0.0:8081/?token=70854dcc18c57019425f836187e88e53
[21:49:56.978] You can configure a fixed authentication token by setting the `web_password` option (https://docs.mitmproxy.org/stable/concepts-options/#web_password).
然后访问,需要输入显示的token:


上述默认是在正向代理模式下运行的:

正向代理
我是在公网云服务器测试,因为就这一台服务器,所以我先弄了个目标服务:
shell
[root@VM-0-6-rockylinux ~]# python -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
假设http://1.1.1.1:8000就是最终要访问的后台服务。

mitmweb启动命令如下:
shell
./mitmweb --web-host 0.0.0.0 --set block_global=false
假设我们要通过正向代理htp://1.1.1.1:8080来访问http://1.1.1.1:8000
shell
可以这么写:
curl -x http://1.1.1.1:8080 http://127.0.0.1:8000

反向代理
不停服务,直接可以改:

测试:这次没指定代理了:

配置保存
但我发现个问题,上述已经改成反向代理了,但是程序关了一重启,又恢复原样,变成正向代理了。
可以这样,先:
shell
./mitmweb --options > /root/.mitmproxy/config.yaml
然后自己再去config.yaml里慢慢改,我主要也就改了下:
shell
web_columns:
- tls
- icon
- path
- method
- status
- size
- time
改成了:
web_columns:
- tls
- icon
- path
- method
- status
- size
- time
- timestamp
web_host: 0.0.0.0
密码:
web_password: '11111111'
效果如下:

linux上后台多实例运行
我实际是下面这样运行,而且机器上会启动多个实例,多个实例共享配置文件:/root/.mitmproxy/config.yaml
然后这里通过命令行参数指定,这个优先级更高:
shell
nohup /root/upload/mitmproxy-tool/mitmweb --mode reverse:http://1.1.1.1:9905 --web-host 0.0.0.0 --web-port 8168 --listen-port 8167 2>&1 &
nohup /root/upload/mitmproxy-tool/mitmweb --mode reverse:http://1.1.1.1:9906 --web-host 0.0.0.0 --web-port 8268 --listen-port 8267 2>&1 &
总结
回头再试试其他的代理模式,及https代理之类的。好了,就这。