使用 mitmproxy + python 做拦截代理

From:https://blog.wolfogre.com/posts/usage-of-mitmproxy    https://www.cnblogs.com/H4ck3R-XiX/p/12624072.html
            http://www.cnblogs.com/grandlulu/p/9525417.html

mitmProxy 介绍:https://blog.csdn.net/h416756139/article/details/51940757
github地址:https://github.com/mitmproxy/mitmproxy
mitmproxy 官网:https://mitmproxy.org        mitmproxy 官网文档:https://docs.mitmproxy.org/stable

mitmproxy 官方示例 及 API:(推荐从 simple 开始):https://github.com/mitmproxy/mitmproxy/tree/master/examples

如何突破网站对 selenium 的屏蔽 : https://blog.csdn.net/qq_26877377/article/details/83307208

和 Charles 同样强大的 iOS 免费抓包工具 mitmproxy:http://ios.jobbole.com/91030
朋友圈红包照片 mitmproxy 抓包破解:https://www.jianshu.com/p/4bef2926d8b9
mitmproxy 系列博文:https://blog.csdn.net/hqzxsc2006/article/category/6971655

http proxy 在 web 渗透上占据着非常重要的地位,这方面的工具也非常多,像 burp suite, Fiddler,Charles 简直每个都是搞 web 的必备神器。本文主要介绍 mitmproxy,是一个较为完整的 mitmproxy 教程,侧重于介绍如何开发拦截脚本,帮助读者能够快速得到一个自定义的代理工具。

本文假设读者有基本的 python 知识,且已经安装好了一个 python 3 开发环境。如果你对 nodejs 的熟悉程度大于对 python,可移步到 anyproxy,anyproxy 的功能与 mitmproxy 基本一致,但使用 js 编写定制脚本。除此之外我就不知道有什么其他类似的工具了,如果你知道,欢迎评论告诉我。

1. mitmproxy 是什么

顾名思义,mitmproxy 就是用于 MITM 的 proxy,MITM 即 中间人攻击(Man-in-the-middle attack),mitmproxy 译为中间人代理工具,可以用来拦截、修改、保存 HTTP/HTTPS 请求。以命令行终端形式呈现,操作上类似于Vim,同时提供了 mitmweb 插件,是类似于 Chrome 浏览器开发者模式的可视化工具。中间人代理一般在客户端和服务器之间的网络中拦截、监听和篡改数据。用于中间人攻击的代理首先会向正常的代理一样转发请求,保障服务端与客户端的通信,其次,会适时的查、记录其截获的数据,或篡改数据,引发服务端或客户端特定的行为。

不同于 fiddler 或 wireshark 等抓包工具,mitmproxy 不仅可以截获请求帮助开发者查看、分析,更可以通过自定义脚本进行二次开发。举例来说,利用 fiddler 可以过滤出浏览器对某个特定 url 的请求,并查看、分析其数据,但实现不了高度定制化的需求,类似于:“截获对浏览器对该 url 的请求,将返回内容置空,并将真实的返回内容存到某个数据库,出现异常时发出邮件通知”。mitmproxy 它是基于Python开发的开源工具,最重要的是它提供了Python API,这样就可以通过载入自定义 python 脚本轻松实现使用Python代码来控制请求和响应。这是其它工具所不能做到的。

但 mitmproxy 并不会真的对无辜的人发起中间人攻击,由于 mitmproxy 工作在 HTTP 层,而当前 HTTPS 的普及让客户端拥有了检测并规避中间人攻击的能力,所以要让 mitmproxy 能够正常工作,必须要让客户端(APP 或浏览器)主动信任 mitmproxy 的 SSL 证书,或忽略证书异常,这也就意味着 APP 或浏览器是属于开发者本人的——显而易见,这不是在做黑产,而是在做开发或测试。

那这样的工具有什么实际意义呢?据我所知目前比较广泛的应用是做仿真爬虫,即利用手机模拟器、无头浏览器来爬取 APP 或网站的数据,mitmpproxy 作为代理可以拦截、存储爬虫获取到的数据,或修改数据调整爬虫的行为。

5 种代理模式

事实上,以上说的仅是 mitmproxy 以正向代理模式工作的情况,通过调整配置,mitmproxy 还可以作为透明代理、反向代理、上游代理、SOCKS 代理等,

总共有五种代理模式:

正向代理

  • 1、正向代理(regular proxy)启动时默认选择的模式。是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向mitmproxy代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端必须要进行一些特别的设置才能使用正向代理。

正向代理:所谓正向代理就是顺着请求的方向进行的代理,即代理服务器他是由你配置为你服务,去请求目标服务器地址。比如我们要去访问国外的网站,我们直接访问不通,那么我们就可以找一个代理服务器为我们服务,我们通过代理服务器请求到国外的网站。对于国外的网站而言,他只知道有一个服务器访问了自己,并不知道这件事你是通过代理服务器访问自己。

一个通俗的例子:你需要钱,C正好有钱,但是C不直接借给你。你和B关系比较好,B可以找C借到钱。你和B沟通后,由B来找C借到钱后在给你。

正向代理类似一个跳板机,代理访问外部资源

正向代理的用途:
  (1) 通过间接方式,访问原来无法直接访问的资源。
       (2) 可以做缓存,加速访问资源
  (3) 对客户端访问授权,上网进行认证
  (4) 代理可以记录用户访问记录(上网行为管理),对外隐藏用户信息

反向代理

  • 2、反向代理(reverse proxy)启动参数 -R host。跟正向代理正好相反,对于客户端而言它就像是原始服务器,并且客户端不需要进行任何特别的设置。客户端向mitmproxy代理服务器发送普通请求,mitmproxy转发请求到指定的服务器,并将获得的内容返回给客户端,就像这些内容 原本就是它自己的一样。

反向代理:所谓反向代理正好与正向代理相反,代理服务器是为目标服务器服务的,虽然整体的请求返回路线都是一样的都是Client 到 Proxy 到 Server。比如:我们访问百度网站,百度的代理服务器对外的域名为 https://www.baidu.com 。具体内部的服务器节点我们不知道。现实中我们通过访问百度的代理服务器后,代理服务器给我们转发请求到他们N多的服务器节点中的一个给我们进行搜索后将结果返回。

再举例:我们同样需要钱,但是我们又不知道谁有钱,所以我们找了一家网贷平台,你提交资料后,网贷平台直接将钱打给你。但是你不知道,也不用关注网贷平台的钱从哪里来。网贷平台内部他们可能从哪一个财主哪里融的钱。对你而言网贷平台和他们的金主是一起的。

同样通过上面我们例子可以看到,此时的代理服务器和后面的目标主机是一个系统的(百度公司、网贷平台)。他们是对外提供服务的,所以称为反向代理,代理的是后的人。

反向代理(Reverse Proxy)实际运行方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个服务器

反向代理的作用:
    (1)保证内网的安全,阻止 web 攻击,大型网站,通常将反向代理作为公网访问地址,Web 服务器是内网
    (2)负载均衡,通过反向代理服务器来优化网站的负载

知乎 https://www.zhihu.com/question/24723688

反向代理 和 正向代理 区别:https://www.cnblogs.com/taostaryu/p/10547132.html

正向代理代理的对象是客户端,反向代理代理的对象是服务端。即 正向代理隐藏真实客户端,反向代理隐藏真实服务端

看图理解 1:

看图理解 2:

正向代理中,proxy 和 client 同属一个 LAN,对 server 透明;
反向代理中,proxy 和 server 同属一个 LAN,对 client 透明。

实际上 proxy 在两种代理中做的事都是代为收发请求和响应,不过从结构上来看正好左右互换了下,所以把后出现的那种代理方式叫成了反向代理

总结

正向代理 即是客户端代理,代理客户端,服务端不知道实际发起请求的客户端。正向代理:买票的黄牛
反向代理 即是服务端代理,代理服务端,客户端不知道实际提供服务的服务端。反向代理:租房的代理

上行代理

  • 3、上行代理(upstream proxy)启动参数 -U host。mitmproxy 接受代理请求,并将所有请求无条件转发到指定的上游代理服务器。这与反向代理相反,其中 mitmproxy 将普通 HTTP 请求转发给 上游服务器。

透明代理

透明代理是指将网络流量直接重定向到网络端口,不需要客户端做任何设置。这个特性使得透明代理非常适合不能对客户端进行配置的时候,比如说 Android 应用等等。

  • 4、透明代理(transparent proxy)启动参数 -T。当使用透明代理时,流量将被重定向到网络层的代理,而不需要任何客户端配置。这使得透明代理非常适合那些无法更改客户端行为的情况 - 代理无聊的 Android 应用程序是一个常见的例子。要设置透明代理,我们需要两个新的组件。第一个是重定向机制,可以将目的地为 Internet 上的服务器的TCP连接透明地重新路由到侦听代理服务器。这通常采用与代理服务器相同的主机上的防火墙形式。比如 Linux 下的 iptables, 或者 OSX 中的 pf,一旦客户端初始化了连接,它将作出一个普通的 HTTP 请求(注意,这种请求就是客户端不知道代理存在)请求头中没有scheme(比如http:// 或者 https:// ), 也没有主机名(比如 example.com )我们如何知道上游的主机是哪个呢?路由机制执行了重定向,但保持了原始的目的地址。
    iptable 设置:
            iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j REDIRECT --to-port 8080
            iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 443 -j REDIRECT --to-port 8080
    启用透明代理:mitmproxy -T

启用 SOCKS5 代理

  • 5、socks5 proxy 启动参数 --socks。采用 socks 协议的代理服务器

但这些工作模式针对 mitmproxy 来说似乎不大常用,故本文仅讨论正向代理模式。

mitmproxy 是一款 Python 语言开发的开源中间人代理神器,支持 SSL,支持透明代理、反向代理,支持流量录制回放,支持自定义脚本等。功能上同 Windows中的 Fiddler 有些类似,但mitmproxy是一款console程序,没有GUI界面,不过用起来还算方便。使用mitmproxy可以很方便的过滤、拦截、修改任意经过代理的HTTP请求/响应数据包,甚至可以利用它的scripting API,编写脚本达到自动拦截修改HTTP数据的目的。

# test.py
def response(flow):flow.response.headers["BOOM"] = "boom!boom!boom!"

上面的脚本会在所有经过代理的Http响应包头里面加上一个名为BOOM的header。用mitmproxy -s 'test.py'命令启动mitmproxy,curl验证结果发现的确多了一个BOOM头。

$ http_proxy=localhost:8080 curl -I 'httpbin.org/get'
HTTP/1.1 200 OK
Server: nginx
Date: Thu, 03 Nov 2016 09:02:04 GMT
Content-Type: application/json
Content-Length: 186
Connection: keep-alive
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
BOOM: boom!boom!boom!
...

mitmweb 抓包截图:

显然 mitmproxy 脚本能做的事情远不止这些,结合Python强大的功能,可以衍生出很多应用途径。除此之外,mitmproxy还提供了强大的API,在这些API的基础上,完全可以自己定制一个实现了特殊功能的专属代理服务器。

经过性能测试,发现 mitmproxy 的效率并不是特别高。如果只是用于调试目的那还好,但如果要用到生产环境,有大量并发请求通过代理的时候,性能还是稍微差点。

2. 工作原理

mitmproxy 实现原理:

  1. 客户端发起一个到 mitmproxy 的连接,并且发出 HTTP CONNECT 请求,
  2. mitmproxy 作出响应 (200),模拟已经建立了 CONNECT 通信管道,
  3. 客户端确信它正在和远端服务器会话,然后启动 SSL 连接。在 SSL 连接中指明了它正在连接的主机名 (SNI),
  4. mitmproxy 连接服务器,然后使用客户端发出的 SNI 指示的主机名建立 SSL 连接,
  5. 服务器以匹配的 SSL 证书作出响应,这个 SSL 证书里包含生成的拦截证书所必须的通用名 (CN) 和服务器备用名 (SAN),
  6. mitmproxy 生成拦截证书,然后继续进行与第 3 步暂停的客户端 SSL 握手,
  7. 客户端通过已经建立的 SSL 连接发送请求,
  8. mitmproxy 通过第 4 步建立的 SSL 连接传递这个请求给服务器。

mitmproxy 工作步骤:

  1. 设置系统、浏览器、终端等的代理地址和端口为同一局域网中 mitmproxy 所在电脑的 IP 地址,比如我的 PC 开启 mitmproxy 之后,设置 8080 端口,本地 IP 为 192.168.1.130,那么设置 Android HTTP 代理为 192.168.1.130:8080
  2. 浏览器 或 移动端 访问 mitm.it 来安装 mitmproxy 提供的证书
  3. 在 mitmproxy 提供的命令行下,或者 mitmweb 提供的浏览器界面中就能看到 Android 端发出的请求。

3. 安装

“安装 mitmproxy”这句话是有歧义的,既可以指“安装 mitmproxy 工具”,也可以指“安装 python 的 mitmproxy 包”,注意后者是包含前者的。

如果只是拿 mitmproxy 做一个替代 fiddler 的工具,没有什么定制化的需求,那完全只需要“安装 mitmproxy 工具”即可,去 mitmproxy 官网 上下载一个 installer 便可开箱即用,不需要提前准备好 python 开发环境。但显然,这不是这里要讨论的,我们需要的是“安装 python 的 mitmproxy 包”。

安装 python 的 mitmproxy 包除了会得到 mitmproxy 工具外,还会得到开发定制脚本所需要的包依赖,其安装过程并不复杂。

首先需要安装好 python,版本需要不低于 3.6,且安装了附带的包管理工具 pip。不同操作系统安装 python 3 的方式不一,参考 python 的下载页,这里不做展开,假设你已经准备好这样的环境了。

3.1 Linux 安装

在 linux 中sudo pip3 install mitmproxy

一旦用户安装上了mitmproxy,那么,在python的dist-packages目录下就会有一个libmproxy的目录。点击进去,如下图所示。

有很多文件,里面最关键的一个文件就是flow.py。里面有从客户端请求的类Request,也有从服务器返回的可以操作的类Response。并且都实现了一些方法可以调用请求或回复的数据,包括请求url,header,body,content等。具体如下:

Request的一些方法:

get_query()      :得到请求的url的参数,被存放成了字典。
set_query(odict) :设置请求的url参数,参数是字典。
get_url()        :请求的url。
set_url(url)     :设置url的域。
get_cookies()    :得到请求的cookie。
headers          :请求的header的字典。
content          :请求的内容,如果请求时post,那么content就是指代post的参数。

Response的一些方法如下:

Headers     :返回的header的字典。
Code        :返回数据包的状态,比如200,301之类的状态。
Httpversion :http版本。

3.2 windows 和 mac 安装

在 windows 中,以管理员身份运行 cmd 或 power shell:pip3 install mitmproxy

在 Mac 上安装与使用 mitmproxy:https://www.cnblogs.com/LanTianYou/p/6542190.html

完成后,系统将拥有 mitmproxy、mitmdump、mitmweb 三个命令。其中 mitmproxy 命令不支持在 windows 系统中运行

我们可以拿 mitmdump 测试一下安装是否成功,执行:mitmdump --version

应当可以看到类似于这样的输出:

Mitmproxy: 4.0.1
Python:    3.6.5
OpenSSL:   OpenSSL 1.1.0h  27 Mar 2018
Platform:  Windows-10-10.0.16299-SP0

3.3 安装 CA 证书

官方提供的安装方式:https://docs.mitmproxy.org/stable/concepts-certificates

************ 要安装证书,必须先启动,可以先看下面的启动,在回过来看安装证书。************

方法1:

对于mitmproxy 来说,如果想要截获HTTPS请求,就得解决证书认证的问题,就需要设置CA证书,因此需要在通信发生的客户端安装证书,并且设置为受信任的根证书颁布机构。而mitmproxy安装后就会提供一套CA证书,只要客户信任了此证书即可。
当我们初次运行 mitmproxy 或 mitmdump 时,会在当前目录下生成 ~/.mitmproxy文件夹,其中该文件下包含4个文件,这就是我们要的证书了。
localhost:app zhangtao$ mitmdump
Proxy server listening at http://*:8080

文件说明:

  • mitmproxy-ca.pem PEM格式的证书私钥
  • mitmproxy-ca-cert.pem PEM格式证书,适用于大多数非Windows平台
  • mitmproxy-ca-cert.p12 PKCS12格式的证书,适用于大多数Windows平台
  • mitmproxy-ca-cert.cer 与 mitmproxy-ca-cert.pem 相同(只是后缀名不同),适用于大部分Android平台
  • mitmproxy-dhparam.pem PEM格式的秘钥文件,用于增强SSL安全性。

方法2:

配置 浏览器 和 手机 

  • 1.电脑和手机连接到同一个 wifi 环境下 
  • 2.修改浏览器代理服务器地址为运行 mitmproxy 的那台机器(本机)ip地址,端口设定为你启动 mitmproxy 时设定的端口,如果没有指定就使用 8080 
  • 3.手机做同样操作,修改wifi链接代理为 【手动】,然后指定 ip 地址和端口 

以手机配置为例: 

1. 设置服务器、端口 

2 . 安装 CA 证书 (只需要安装一次证书即可)

第一次使用 mitmproxy 的时候需要安装 CA 证书。在手机 或 pc 机上打开浏览器访问 http://mitm.it 这个地址,选择你当前平台的图标,点击安装证书。选择你当前平台的图标,点击安装证书。
在下图中点击Apple安装证书。

在各端配置好代理后,访问:http://mitm.it 下载 CA 证书,并按照以下方式进行验证。

iOS

  • 打开设置-无线局域网-所连接的Wifi-配置代理-手动
  • 填上代理服务器IP和端口
  • 打开设置-通用-关于本机-证书信任设置
  • 开启mitmproxy选项。

Android

  • 打开设置-WLAN-长按所连接的网络-修改网络-高级选项-手动
  • 填入代理服务器IP和端口
  • 打开设置-安全-信任的凭据
  • 查看安装的证书是否存在

macOS

  • 打开系统配置(System Preferences.app)- 网络(Network)- 高级(Advanced)- 代理(Proxies)- Web Proxy(HTTP)和Secure Web Proxy(HTTPS)
  • 填上代理服务器IP和端口
  • 打开Keychain Access.app
  • 选择login(Keychains)和Certificates(Category)中找到mitmproxy
  • 点击mitmproxy,在Trust中选择Always Trust

4. 运行启动(启动 mitmproxy 三种方式

在完成 mitmproxy 的安装之后,mitm 提供的三个命令。要启动 mitmproxy用 mitmproxy、mitmdump、mitmweb 这三个命令中的任意一个即可,这三个命令功能一致,且都可以加载自定义脚本,唯一的区别是交互界面的不同。

其中 mitmproxy 命令不支持在 windows 系统中运行。

  • mitmproxy 会提供一个在终端下的图形界面,具有修改请求和响应,流量重放等功能,具体操作方式有点 vim 的风格
  • mitmdump 可设定规则保存或重放请求和响应,mitmdump 的特点是支持 inline 脚本,由于拥有可以修改 request 和 response 中每一个细节的能力,批量测试,劫持等都可以轻松实现
  • mitmweb 提供的一个简单 web 界面,简单实用,初学者或者对终端命令行不熟悉的可以用 mitmweb 界面

4.1 mitmproxy 直接启动

mitmproxy 命令启动后,会提供一个命令行界面,用户可以实时看到发生的请求,并通过命令过滤请求,查看请求数据。形如:

mitmproxy 基本使用

可以使用 mitmproxy -h 来查看 mitmproxy 的参数及使用方法。常用的几个命令参数:

  • -p PORT, --port PORT 设置 mitmproxy 的代理端口
  • -T, --transparent 设置透明代理
  • --socks 设置 SOCKS5 代理
  • -s "script.py --bar", --script "script.py --bar" 来执行脚本,通过双引号来添加参数
  • -t FILTER 过滤参数

在 mitmproxy 命令模式下,在终端显示请求流,可以通过 Shift + ? 来开启帮助查看当前页面可用的命令。

基本快捷键b  保存请求 / 返回头
C  将请求内容导出到粘贴板,按 C 之后会有选择导出哪一部分
d  删除 flow 请求
E  将 flow 导出到文件
w  保存所有 flow 或者该 flow
W  保存该 flow
L  加载保存的 Flow
m  添加 / 取消 Mark 标记,会在请求列表该请求前添加红色圆圈
z  清空 flow list 和 eventlog
/  在详情界面,可以使用 / 来搜索,大小写敏感
i  开启 interception pattern 拦截请求移动j, k       上下
h, l        左右
g, G   go to beginning, end
space    下一页
pg up/down   上一页 / 下一页
ctrl+b/ctrl+f    上一页 / 下一页
arrows 箭头     上下左右全局快捷键
q   退出,或者后退
Q  不提示直接退出
  • mitmproxy的按键操作说明
按键说明
q退出(相当于返回键,可一级一级返回)
d删除当前(黄色箭头)指向的链接
D恢复刚才删除的请求
G跳到最新一个请求
g跳到第一个请求
C清空控制台(C是大写)
i可输入需要拦截的文件或者域名(逗号需要用\来做转译,栗子:feezu.cn)
a放行请求
A放行所有请求
?查看界面帮助信息
^ v上下箭头移动光标
enter查看光标所在列的内容
tab分别查看 Request 和 Response 的详细信息
/搜索body里的内容
esc退出编辑
e进入编辑模式

同样在 mitmproxy 中不同界面中使用 ? 可以获取不同的帮助,在请求详细信息中 m 快捷键的作用就完全不同 m 在响应结果中,输入 m 可以选择 body 的呈现方式,比如 json,xml 等 e 编辑请求、响应 a 发送编辑后的请求、响应。 因此在熟悉使用 ? 之后,多次使用并熟悉快捷键即可。就如同在 Linux 下要熟悉使用 man 命令一样,在不懂地方请教 Google 一样,应该是习惯性动作。多次反复之后就会变得非常数量。

4.2 mitmweb 命令启动

mitmweb 命令启动后,会提供一个 web 界面,用户可以实时看到发生的请求,并通过 GUI 交互来过滤请求,查看请求数据。形如:

4.3 mitmdump 命令启动

mitmdump 命令启动后——你应该猜到了,没有界面,程序默默运行,所以 mitmdump 无法提供过滤请求、查看数据的功能,只能结合自定义脚本,默默工作。

4.4 启动示例

由于 mitmproxy 命令的交互操作稍显繁杂且不支持 windows 系统,而我们主要的使用方式又是载入自定义脚本,并不需要交互,所以原则上说只需要 mitmdump 即可,但考虑到有交互界面可以更方便排查错误,所以这里以 mitmweb 命令为例。实际使用中可以根据情况选择任何一个命令。

启动 mitmproxy:mitmweb

应当看到如下输出:

Web server listening at http://127.0.0.1:8081/
Proxy server listening at http://*:8080

mitmproxy 绑定了 *:8080 作为代理端口,并提供了一个 web 交互界面在 127.0.0.1:8081。

Chrome 流量走 mitmproxy

现在可以测试一下代理,让 Chrome 以 mitmproxy 为代理并忽略证书错误。为了不影响平时正常使用,我们不去改 Chrome 的配置,而是通过命令行带参数起一个 Chrome。如果你不使用 Chrome 而是其他浏览器,也可以搜一下对应的启动参数是什么,应该不会有什么坑。此外示例仅以 windows 系统为例,因为使用 linux 或 mac 开发的同学应该更熟悉命令行的使用才对,应当能自行推导出在各自环境中对应的操作。

由于 Chrome 要开始赴汤蹈火走代理了,为了方便继续在 web 界面上与 mitmproxy 交互,我们委屈求全使用 Edge 或其他浏览器打开 127.0.0.1:8081。插一句,我用 Edge 实在是因为机器上没其他浏览器了(IE 不算),Edge 有一个默认禁止访问回环地址的狗屁设定,详见解决方案。

接下来关闭所有 Chrome 窗口,否则命令行启动时的附加参数将失效。打开 cmd,执行:"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --proxy-server=127.0.0.1:8080 -ignore-certificate-errors

或者

cd C:\Program Files (x86)\Google\Chrome\Application
chrome.exe --proxy-server=127.0.0.1:8080 -ignore-certificate-errors

前面那一长串是 Chrome 的的安装路径,应当根据系统实际情况修改,后面两参数设置了代理地址并强制忽略掉证书错误。用 Chrome 打开一个网站,可以看到:

同时在 Edge 上可以看到:

5. 编写并执行 Python 脚本

重点:一个完整的 HTTP flow 会依次触发 requestheadersrequestresponseheaders response

完成了上述工作,我们已经具备了操作 mitmproxy 的基本能力 了。接下来开始开发自定义脚本,这才是 mitmproxy 真正强大的地方。使用 -s 参数 制定 inline 脚本:

mitmproxy -s script.py

比如将指定 url 的请求指向新的地址

用于调试 Android 或者 iOS 客户端,打包比较复杂的时候,强行将客户端请求从线上地址指向本地调试地址。可以使用 mitmproxy scripting API mitmproxy 提供的事件驱动接口。

加上将线上地址,指向本地 8085 端口,文件为 redirect_request.py

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
def request(flow):if flow.request.pretty_host == 'api.github.com':flow.request.host = '127.0.0.1'flow.request.port = 8085

则使用 mitmweb -s redirect_request.py 来调用此脚本,则通过 mitm 的请求都会指向本地 http://127.0.0.1:8085。

更多的脚本可以参考

脚本编写遵循的规定( 2 种方法 )

        脚本的编写需要遵循 mitmproxy 规定的套路,这样的套路有两个。

方法 1:定义若干函数

方法 1 ( 定义若干函数,这些函数实现了某些 mitmproxy 提供的事件 ):

编写一个 py 文件供 mitmproxy 加载,文件中定义了若干函数,这些函数实现了某些 mitmproxy 提供的事件,mitmproxy 会在某个事件发生时调用对应的函数,形如:

import mitmproxy.http
from mitmproxy import ctxnum = 0def request(flow: mitmproxy.http.HTTPFlow):global numnum = num + 1ctx.log.info("We've seen %d flows" % num)

官网截图:

http 协议 事件 ( examples/addons/events-http-specific.py ):

"""HTTP-specific events."""
import mitmproxy.httpclass Events:def http_connect(self, flow: mitmproxy.http.HTTPFlow):"""An HTTP CONNECT request was received. Setting a non 2xx response onthe flow will return the response to the client abort theconnection. CONNECT requests and responses do not generate the usualHTTP handler events. CONNECT requests are only valid in regular andupstream proxy modes."""def requestheaders(self, flow: mitmproxy.http.HTTPFlow):"""HTTP request headers were successfully read. At this point, the bodyis empty."""def request(self, flow: mitmproxy.http.HTTPFlow):"""The full HTTP request has been read."""def responseheaders(self, flow: mitmproxy.http.HTTPFlow):"""HTTP response headers were successfully read. At this point, the bodyis empty."""def response(self, flow: mitmproxy.http.HTTPFlow):"""The full HTTP response has been read."""def error(self, flow: mitmproxy.http.HTTPFlow):"""An HTTP error has occurred, e.g. invalid server responses, orinterrupted connections. This is distinct from a valid server HTTPerror response, which is simply a response with an HTTP error code."""

方法 2:定义一个类

第二个是 ( 定义一个 类,类里面的方法实现了某些 mitmproxy 提供的事件):

官网说明:https://docs.mitmproxy.org/stable/addons-overview/

编写一个 py 文件供 mitmproxy 加载,文件定义了变量 addons,addons 是个数组,每个元素是一个类实例,这些类有若干方法,这些方法实现了某些 mitmproxy 提供的事件,mitmproxy 会在某个事件发生时调用对应的方法。这些类,称为一个个 addon,比如一个叫 Counter 的 addon:

import mitmproxy.http
from mitmproxy import ctxclass Counter:def __init__(self):self.num = 0def request(self, flow: mitmproxy.http.HTTPFlow):self.num = self.num + 1ctx.log.info("We've seen %d flows" % self.num)addons = [Counter()
]

这里强烈建议使用第二种套路,直觉上就会感觉第二种套路更为先进,使用会更方便也更容易管理和拓展。况且这也是官方内置的一些 addon 的实现方式。

我们将上面第二种套路的示例代码存为 addons.py,再重新启动 mitmproxy:mitmweb -s addons.py

当浏览器使用代理进行访问时,就应该能看到控制台里有类似这样的日志:

Web server listening at http://127.0.0.1:8081/
Loading script addons.py
Proxy server listening at http://*:8080
We've seen 1 flows
……
……
We've seen 2 flows
……
We've seen 3 flows
……
We've seen 4 flows
……
……
We've seen 5 flows
……

这就说明自定义脚本生效了。

执行脚本的三种方法

在 Python 脚本中使用 mitmproxy:https://www.coder.work/article/3121077

How to shutdown dumpmaster (mitmproxy) programmatically? :https://stackoverflow.com/questions/57178896/how-to-shutdown-dumpmaster-mitmproxy-programmatically

mitmproxy 的使用:https://www.jianshu.com/p/f6af1d57186e

方法1. 使用 mitmproxy -s 命令行启动。

示例:mitmproxy -s sample.py 

sample.py 

import mitmproxy.http
from mitmproxy import ctxclass Counter:def __init__(self):self.num = 0def request(self, flow: mitmproxy.http.HTTPFlow):self.num = self.num + 1ctx.log.info("We've seen %d flows" % self.num)addons = [Counter()
]

方法2. 使用 Python 直接执行,而无需从命令启动

( https://github.com/mitmproxy/mitmproxy/issues/3306 )

示例 1:

from mitmproxy import proxy, options
from mitmproxy.tools.dump import DumpMaster
from mitmproxy.addons import coreclass AddHeader:def __init__(self):self.num = 0def response(self, flow):self.num = self.num + 1print(self.num)flow.response.headers["count"] = str(self.num)addons = [AddHeader()
]opts = options.Options(listen_host='127.0.0.1', listen_port=9999)m = DumpMaster(options=opts)
m.addons.add(*addons)
print(m.addons)
# m.addons.add(core.Core())try:m.run()
except KeyboardInterrupt:m.shutdown()

示例 2:

# -*- coding: utf-8 -*-import json
import datetime
from mitmproxy.options import Options
from mitmproxy.tools.dump import DumpMaster
from mitmproxy import ctx, httpclass TTAddon(object):def __init__(self):self._temp = Nonepassdef request(self, flow: http.HTTPFlow):self._temp = None# do something in responser_url = flow.request.urlpassdef response(self, flow: http.HTTPFlow):self._temp = None# do something in responser_url = flow.request.urlif 'api/news/feed/v88/' in r_url and 'category=news_car' in r_url:print(r_url)resp = flow.response.textresp_dict = json.loads(resp)print(json.dumps(resp_dict, ensure_ascii=False, indent=4))passclass ProxyMaster(DumpMaster):def __init__(self, *args, **kwargs):super().__init__(*args, **kwargs)def start_run(self):try:DumpMaster.run(self)except KeyboardInterrupt:self.shutdown()if __name__ == "__main__":opts = Options(listen_host='0.0.0.0', listen_port=8080, http2=True)# master = ProxyMaster(options=opts, with_termlog=False, with_dumper=False)master = ProxyMaster(options=opts, with_termlog=True, with_dumper=False)master.addons.add((TTAddon(), ))master.start_run()

方法3. 使用第三方库 mitmdump  运行 mitmproxy

( mitmdump 是对 mitmproxy 的简单封装,实现以编程的方式运行 mitmproxy 服务 )

pypi 地址( 示例代码 ):https://pypi.org/project/mitmdump/     安装:pip install mitmdump

示例 1:

# sample1.pyfrom mitmproxy.http import HTTPFlow
from mitmdump import DumpMaster, Optionsclass AddHeader:def __init__(self):self.num = 0def response(self, flow: HTTPFlow):self.num = self.num + 1flow.response.headers["count"] = str(self.num)addons = [AddHeader()
]if __name__ == '__main__':opts = Options(listen_host='0.0.0.0', listen_port=8888, scripts=__file__)m = DumpMaster(opts)m.run()

示例 2:

# sample2.pyfrom mitmproxy import flowfilter, ctx, addonmanager
from mitmproxy.http import HTTPFlow
from mitmdump import DumpMaster, Optionsclass FilterFlow:def __init__(self):self.filter = Nonedef load(self, loader: addonmanager.Loader):self.filter = flowfilter.parse(ctx.options.dumper_filter)def request(self, flow: HTTPFlow):if flowfilter.match(self.filter, flow):print(flow.request.url)def response(self, flow: HTTPFlow):if flowfilter.match(self.filter, flow):print(flow.response.headers)addons = [FilterFlow()
]if __name__ == '__main__':opts = Options(listen_host='0.0.0.0', listen_port=8888, scripts=None, dumper_filter='~m POST',flow_detail=1, termlog_verbosity='info', show_clientconnect_log=False)m = DumpMaster(opts)# It's necessary if scripts parameter is None# 如果你的 scripts 参数为 None,则下方加载插件的语句是必须要有的m.addons.add(*addons)m.run()

更多官网示例:https://docs.mitmproxy.org/stable/addons-examples/

示例

mitmproxy 启动时可以使用 -s 参数导入外部的脚本进行拦截处理,比如我要修改每个链接的响应头的python脚本编写如下:

1、简单方法

from mitmproxy import httpdef response(flow: http.HTTPFlow) -> None:flow.response.headers["server"] = "nginx"

2、使用类

class ModifyHeader:def response(self, flow):flow.response.headers["serverr"] = "nginx"def start():return ModifyHeader()

保存为 modifyheader.py。然后启动 mitmdump -s modifyheader.py,就会把代理抓到包的每个响应头的Server都改成“nginx”

官方参考例子:https://github.com/mitmproxy/mitmproxy/tree/master/examples

编写脚本的话,主要用到的有两个东西
    - Event
    - API

1. Event 事件
        事件里面有3个事件是比较重要的
                start                  启动的时候被调用,会替换当前的插件,可以用此事件注册过滤器.
                request(flow)    当发送请求时,被调用.
                response(flow) 当接收到回复时被调用.

2. API
        三个比较重要的数据结构
                mitmproxy.models.http.HTTPRequest
                mitmproxy.models.http.HTTPResponse
                mitmproxy.models.http.HTTPFlow

在编写脚本的时候,如果不知道该怎么写,传过来的参数里面有什么,去 (https://docs.mitmproxy.org/stable) 查看这些就对了.

先上代码:头脑王者即时显示答案脚本

#!/usr/bin/env python
#coding=utf-8
import sys
import json
from mitmproxy import flowfilter
from pymongo import MongoClient
reload(sys)
sys.setdefaultencoding('utf-8')'''
头脑王者即时显示答案脚本
'''class TNWZ:'''从抓包可以看到 问题包的链接最后是 findQuiz'''def __init__(self):#添加一个过滤器,只处理问题包self.filter = flowfilter.parse('~u findQuiz')#连接答案数据库self.conn = MongoClient('localhost', 27017)self.db = self.conn.tnwzself.answer_set = self.db.quizzesdef request(self, flow):'''演示request事件效果, 请求的时候输出提示:param flow: :return: '''if flowfilter.match(self.filter,flow):print(u'准备请求答案')def responseheaders(self, flow):'''演示responseheaders事件效果, 添加头信息:param flow: :return: '''if flowfilter.match(self.filter, flow):flow.response.headers['Cache-Control'] = 'no-cache'flow.response.headers['Pragma'] = 'no-cache'def response(self, flow):'''HTTPEvent 下面所有事件参数都是 flow 类型 HTTPFlow可以在API下面查到 HTTPFlow, 下面有一个属性response 类型 TTPResponseHTTPResponse 有个属性为 content 就是response在内容,更多属性可以查看 文档:param flow: :return: '''if flowfilter.match(self.filter, flow):#匹配上后证明抓到的是问题了, 查答案data = flow.response.contentquiz = json.loads(data)#获取问题question = quiz['quiz']print(question)#获取答案answer = self.answer_set.find_one({"quiz":question})if answer is None:print('no answer')else:answerIndex = int(answer['answer'])-1options = answer['options']print(options[answerIndex])#这里简单演示下start事件
def start():return TNWZ()

使用方法:mitmdump -s quiz.py

启动后也可以编辑脚本文件,mitmdump会自动重新加载不需要重新运行命令,本来是写了一个头脑王者自动抓问题找答案,结果游戏被封了,只在本地模拟下

是不是很简单, 如果还不够,可以考虑,在发送答案的时候, 拦截请求,然后替换为标准答案再发送到服务器,是不是很给力。

6. 事件

上述的脚本估计不用我解释相信大家也看明白了,就是当 request 发生时,计数器加一,并打印日志。这里对应的是 request 事件,那拢共有哪些事件呢?不多,也不少,这里详细介绍一下。

事件针对不同生命周期分为 5 类。“生命周期”这里指在哪一个层面看待事件,举例来说,同样是一次 web 请求,我可以理解为“HTTP 请求 -> HTTP 响应”的过程,也可以理解为“TCP 连接 -> TCP 通信 -> TCP 断开”的过程。那么,如果我想拒绝来个某个 IP 的客户端请求,应当注册函数到针对 TCP 生命周期 的 tcp_start 事件,又或者,我想阻断对某个特定域名的请求时,则应当注册函数到针对 HTTP 声明周期的 http_connect 事件。其他情况同理。

下面一段估计会又臭又长,如果你没有耐心看完,那至少看掉针对 HTTP 生命周期的事件,然后跳到示例。

1. 针对 HTTP 生命周期

def http_connect(self, flow: mitmproxy.http.HTTPFlow):
        (Called when) 收到了来自客户端的 HTTP CONNECT 请求。在 flow 上设置非 2xx 响应将返回该响应并断开连接。CONNECT 不是常用的 HTTP 请求方法,目的是与服务器建立代理连接,仅是 client 与 proxy 的之间的交流,所以 CONNECT 请求不会触发 request、response 等其他常规的 HTTP 事件。

def requestheaders(self, flow: mitmproxy.http.HTTPFlow):
        (Called when) 来自客户端的 HTTP 请求的头部被成功读取。此时 flow 中的 request 的 body 是空的。

def request(self, flow: mitmproxy.http.HTTPFlow):
        (Called when) 来自客户端的 HTTP 请求被成功完整读取。

def responseheaders(self, flow: mitmproxy.http.HTTPFlow):
        (Called when) 来自服务端的 HTTP 响应的头部被成功读取。此时 flow 中的 response 的 body 是空的。

def response(self, flow: mitmproxy.http.HTTPFlow):
        (Called when) 来自服务端端的 HTTP 响应被成功完整读取。

def error(self, flow: mitmproxy.http.HTTPFlow):
        (Called when) 发生了一个 HTTP 错误。比如无效的服务端响应、连接断开等。注意与“有效的 HTTP 错误返回”不是一回事,后者是一个正确的服务端响应,只是 HTTP code 表示错误而已。

2. 针对 TCP 生命周期

def tcp_start(self, flow: mitmproxy.tcp.TCPFlow):
        (Called when) 建立了一个 TCP 连接。

def tcp_message(self, flow: mitmproxy.tcp.TCPFlow):
        (Called when) TCP 连接收到了一条消息,最近一条消息存于 flow.messages[-1]。消息是可修改的。

def tcp_error(self, flow: mitmproxy.tcp.TCPFlow):
        (Called when) 发生了 TCP 错误。

def tcp_end(self, flow: mitmproxy.tcp.TCPFlow):
        (Called when) TCP 连接关闭。

3. 针对 Websocket 生命周期

def websocket_handshake(self, flow: mitmproxy.http.HTTPFlow):
        (Called when) 客户端试图建立一个 websocket 连接。可以通过控制 HTTP 头部中针对 websocket 的条目来改变握手行为。flow 的 request 属性保证是非空的的。

def websocket_start(self, flow: mitmproxy.websocket.WebSocketFlow):
        (Called when) 建立了一个 websocket 连接。

def websocket_message(self, flow: mitmproxy.websocket.WebSocketFlow):
        (Called when) 收到一条来自客户端或服务端的 websocket 消息。最近一条消息存于 flow.messages[-1]。消息是可修改的。目前有两种消息类型,对应 BINARY 类型的 frame 或 TEXT 类型的 frame。

def websocket_error(self, flow: mitmproxy.websocket.WebSocketFlow):
        (Called when) 发生了 websocket 错误。

def websocket_end(self, flow: mitmproxy.websocket.WebSocketFlow):
        (Called when) websocket 连接关闭。

4. 针对网络连接生命周期

def clientconnect(self, layer: mitmproxy.proxy.protocol.Layer):
        (Called when) 客户端连接到了 mitmproxy。注意一条连接可能对应多个 HTTP 请求。

def clientdisconnect(self, layer: mitmproxy.proxy.protocol.Layer):
        (Called when) 客户端断开了和 mitmproxy 的连接。

def serverconnect(self, conn: mitmproxy.connections.ServerConnection):
        (Called when) mitmproxy 连接到了服务端。注意一条连接可能对应多个 HTTP 请求。

def serverdisconnect(self, conn: mitmproxy.connections.ServerConnection):
        (Called when) mitmproxy 断开了和服务端的连接。

def next_layer(self, layer: mitmproxy.proxy.protocol.Layer):
        (Called when) 网络 layer 发生切换。你可以通过返回一个新的 layer 对象来改变将被使用的 layer。详见 layer 的定义。

5. 通用生命周期

def configure(self, updated: typing.Set[str]):
        (Called when) 配置发生变化。updated 参数是一个类似集合的对象,包含了所有变化了的选项。在 mitmproxy 启动时,该事件也会触发,且 updated 包含所有选项。

def done(self):
        (Called when) addon 关闭或被移除,又或者 mitmproxy 本身关闭。由于会先等事件循环终止后再触发该事件,所以这是一个 addon 可以看见的最后一个事件。由于此时 log 也已经关闭,所以此时调用 log 函数没有任何输出。

def load(self, entry: mitmproxy.addonmanager.Loader):
        (Called when) addon 第一次加载时。entry 参数是一个 Loader 对象,包含有添加选项、命令的方法。这里是 addon 配置它自己的地方。

def log(self, entry: mitmproxy.log.LogEntry):
        (Called when) 通过 mitmproxy.ctx.log 产生了一条新日志。小心不要在这个事件内打日志,否则会造成死循环。

def running(self):
        (Called when) mitmproxy 完全启动并开始运行。此时,mitmproxy 已经绑定了端口,所有的 addon 都被加载了。

def update(self, flows: typing.Sequence[mitmproxy.flow.Flow]):
        (Called when) 一个或多个 flow 对象被修改了,通常是来自一个不同的 addon。

From:https://www.cnblogs.com/c-x-a/p/9753526.html

主要 events 一览表

需要修改各种事件内容时,重写以下对应方法,这里主要用的是request、response方法

import typing

import mitmproxy.addonmanager
import mitmproxy.connections
import mitmproxy.http
import mitmproxy.log
import mitmproxy.tcp
import mitmproxy.websocket
import mitmproxy.proxy.protocol

def requestheaders(self, flow: mitmproxy.http.HTTPFlow):"""HTTP request headers were successfully read. At this point, the bodyis empty."""def request(self, flow: mitmproxy.http.HTTPFlow):"""The full HTTP request has been read."""def responseheaders(self, flow: mitmproxy.http.HTTPFlow):"""HTTP response headers were successfully read. At this point, the bodyis empty."""def response(self, flow: mitmproxy.http.HTTPFlow):"""The full HTTP response has been read."""def error(self, flow: mitmproxy.http.HTTPFlow):"""An HTTP error has occurred, e.g. invalid server responses, orinterrupted connections. This is distinct from a valid server HTTPerror response, which is simply a response with an HTTP error code."""# TCP lifecycle
def tcp_start(self, flow: mitmproxy.tcp.TCPFlow):"""A TCP connection has started."""def tcp_message(self, flow: mitmproxy.tcp.TCPFlow):"""A TCP connection has received a message. The most recent messagewill be flow.messages[-1]. The message is user-modifiable."""def tcp_error(self, flow: mitmproxy.tcp.TCPFlow):"""A TCP error has occurred."""def tcp_end(self, flow: mitmproxy.tcp.TCPFlow):"""A TCP connection has ended."""# Websocket lifecycle
def websocket_handshake(self, flow: mitmproxy.http.HTTPFlow):"""Called when a client wants to establish a WebSocket connection. TheWebSocket-specific headers can be manipulated to alter thehandshake. The flow object is guaranteed to have a non-None requestattribute."""def websocket_start(self, flow: mitmproxy.websocket.WebSocketFlow):"""A websocket connection has commenced."""def websocket_message(self, flow: mitmproxy.websocket.WebSocketFlow):"""Called when a WebSocket message is received from the client orserver. The most recent message will be flow.messages[-1]. Themessage is user-modifiable. Currently there are two types ofmessages, corresponding to the BINARY and TEXT frame types."""def websocket_error(self, flow: mitmproxy.websocket.WebSocketFlow):"""A websocket connection has had an error."""def websocket_end(self, flow: mitmproxy.websocket.WebSocketFlow):"""A websocket connection has ended."""# Network lifecycle
def clientconnect(self, layer: mitmproxy.proxy.protocol.Layer):"""A client has connected to mitmproxy. Note that a connection cancorrespond to multiple HTTP requests."""def clientdisconnect(self, layer: mitmproxy.proxy.protocol.Layer):"""A client has disconnected from mitmproxy."""def serverconnect(self, conn: mitmproxy.connections.ServerConnection):"""Mitmproxy has connected to a server. Note that a connection cancorrespond to multiple requests."""def serverdisconnect(self, conn: mitmproxy.connections.ServerConnection):"""Mitmproxy has disconnected from a server."""def next_layer(self, layer: mitmproxy.proxy.protocol.Layer):"""Network layers are being switched. You may change which layer willbe used by returning a new layer object from this event."""# General lifecycle
def configure(self, updated: typing.Set[str]):"""Called when configuration changes. The updated argument is aset-like object containing the keys of all changed options. Thisevent is called during startup with all options in the updated set."""def done(self):"""Called when the addon shuts down, either by being removed fromthe mitmproxy instance, or when mitmproxy itself shuts down. Onshutdown, this event is called after the event loop isterminated, guaranteeing that it will be the final event an addonsees. Note that log handlers are shut down at this point, socalls to log functions will produce no output."""def load(self, entry: mitmproxy.addonmanager.Loader):"""Called when an addon is first loaded. This event receives a Loaderobject, which contains methods for adding options and commands. Thismethod is where the addon configures itself."""def log(self, entry: mitmproxy.log.LogEntry):"""Called whenever a new log entry is created through the mitmproxycontext. Be careful not to log from this event, which will cause aninfinite loop!"""def running(self):"""Called when the proxy is completely up and running. At this point,you can expect the proxy to be bound to a port, and all addons to beloaded."""def update(self, flows: typing.Sequence[mitmproxy.flow.Flow]):"""Update is called when one or more flow objects have been modified,usually from a different addon."""

7. 针对 http,常用的 API

http.HTTPFlow 实例 flow

# http.HTTPFlow 实例 flow
flow.request.headers          # 获取所有头信息,包含Host、User-Agent、Content-type等字段
flow.request.url              # 完整的请求地址,包含域名及请求参数,但是不包含放在body里面的请求参数
flow.request.pretty_url       # 同flow.request.url目前没看出什么差别
flow.request.host             # 域名
flow.request.method           # 请求方式。POST、GET等
flow.request.scheme           # 什么请求 ,如 https
flow.request.path             # 请求的路径,url除域名之外的内容
flow.request.get_text()       # 请求中body内容,有一些http会把请求参数放在body里面,那么可通过此方法获取,返回字典类型
flow.request.query            # 返回MultiDictView类型的数据,url直接带的键值参数
flow.request.get_content()    # bytes,结果如flow.request.get_text()
flow.request.raw_content      # bytes,结果如flow.request.get_content()
flow.request.urlencoded_form  # MultiDictView,content-type:application/x-www-form-urlencoded 时的请求参数,不包含url直接带的键值参数
flow.request.multipart_form   # MultiDictView,content-type:multipart/form-data 时的请求参数,不包含url直接带的键值参数

以上均为获取 request 信息的一些常用方法,对于 response,同理

flow.response.status_code  # 状态码
flow.response.text         # 返回内容,已解码
flow.response.content      # 返回内容,二进制
flow.response.setText()    # 修改返回内容,不需要转码

以上为不完全列举

示例

修改response内容,这里是服务器已经有返回了结果,再更改,也可以做不经过服务器处理,直接返回,看需求

def response(flow:http.HTTPFlow)-> None:
# 特定接口需要返回1001结果
interface_list=["page/**"] #由于涉及公司隐私问题,隐藏实际的接口

url_path=flow.request.path
if  url_path.split("?")[0] in  interface_list:ctx.log.info("#"*50)ctx.log.info("待修改路径的内容:"+url_path)ctx.log.info("修改成:1001错误返回")ctx.log.info("修改前:\n")ctx.log.info(flow.response.text)flow.response.set_text(json.dumps({"result":"1001","message":"服务异常"}))#修改,使用set_text不用转码ctx.log.info("修改后:\n")ctx.log.info(flow.response.text)ctx.log.info("#"*50)
elif  flow.request.host in  host_list:#host_list 域名列表,作为全局变量,公司有多个域名,也隐藏ctx.log.info("response= "+flow.response.text)

8. 示 例

估计看了那么多的事件你已经晕了,正常,鬼才会记得那么多事件。事实上考虑到 mitmproxy 的实际使用场景,大多数情况下我们只会用到针对 HTTP 生命周期的几个事件。再精简一点,甚至只需要用到 http_connectrequestresponse 三个事件就能完成大多数需求了。

这里以一个稍微有点黑色幽默的例子,覆盖这三个事件,展示如果利用 mitmproxy 工作。

需求是这样的:

  1. 因为百度搜索是不靠谱的,所有当客户端发起百度搜索时,记录下用户的搜索词,再修改请求,将搜索词改为“360 搜索”;
  2. 因为 360 搜索还是不靠谱的,所有当客户端访问 360 搜索时,将页面中所有“搜索”字样改为“请使用谷歌”。
  3. 因为谷歌是个不存在的网站,所有就不要浪费时间去尝试连接服务端了,所有当发现客户端试图访问谷歌时,直接断开连接。
  4. 将上述功能组装成名为 Joker 的 addon,并保留之前展示名为 Counter 的 addon,都加载进 mitmproxy。

第一个需求需要篡改客户端请求,所以实现一个 request 事件:

def request(self, flow: mitmproxy.http.HTTPFlow):# 忽略非百度搜索地址if flow.request.host != "www.baidu.com" or not flow.request.path.startswith("/s"):return# 确认请求参数中有搜索词if "wd" not in flow.request.query.keys():ctx.log.warn("can not get search word from %s" % flow.request.pretty_url)return# 输出原始的搜索词ctx.log.info("catch search word: %s" % flow.request.query.get("wd"))# 替换搜索词为“360搜索”flow.request.query.set_all("wd", ["360搜索"])

第二个需求需要篡改服务端响应,所以实现一个 response 事件:

def response(self, flow: mitmproxy.http.HTTPFlow):# 忽略非 360 搜索地址if flow.request.host != "www.so.com":return# 将响应中所有“搜索”替换为“请使用谷歌”text = flow.response.get_text()text = text.replace("搜索", "请使用谷歌")flow.response.set_text(text)

第三个需求需要拒绝客户端请求,所以实现一个 http_connect 事件:

def http_connect(self, flow: mitmproxy.http.HTTPFlow):# 确认客户端是想访问 www.google.comif flow.request.host == "www.google.com":# 返回一个非 2xx 响应断开连接flow.response = http.HTTPResponse.make(404)

为了实现第四个需求,我们需要将代码整理一下,即易于管理也易于查看。

创建一个 joker.py 文件,内容为:

import mitmproxy.http
from mitmproxy import ctx, httpclass Joker:def request(self, flow: mitmproxy.http.HTTPFlow):if flow.request.host != "www.baidu.com" or not flow.request.path.startswith("/s"):returnif "wd" not in flow.request.query.keys():ctx.log.warn("can not get search word from %s" % flow.request.pretty_url)returnctx.log.info("catch search word: %s" % flow.request.query.get("wd"))flow.request.query.set_all("wd", ["360搜索"])def response(self, flow: mitmproxy.http.HTTPFlow):if flow.request.host != "www.so.com":returntext = flow.response.get_text()text = text.replace("搜索", "请使用谷歌")flow.response.set_text(text)def http_connect(self, flow: mitmproxy.http.HTTPFlow):if flow.request.host == "www.google.com":flow.response = http.HTTPResponse.make(404)

创建一个 counter.py 文件,内容为:

import mitmproxy.http
from mitmproxy import ctxclass Counter:def __init__(self):self.num = 0def request(self, flow: mitmproxy.http.HTTPFlow):self.num = self.num + 1ctx.log.info("We've seen %d flows" % self.num)

创建一个 addons.py 文件,内容为:

import counter
import jokeraddons = [counter.Counter(),joker.Joker(),
]

将三个文件放在相同的文件夹,在该文件夹内启动命令行,运行:mitmweb -s addons.py

老规矩,关闭所有 Chrome 窗口,从命令行中启动 Chrome 并指定代理且忽略证书错误。

测试一下运行效果:

利用appium和mitmproxy登录获取cookies

环境搭建

参考我之前写的 :
    windows中Appium-desktop配合夜神模拟器的使用 : https://www.cnblogs.com/c-x-a/p/9163221.html

appium

代码 start_appium.py

# -*- coding: utf-8 -*-
# @Time    : 2018/10/8 11:00
# @Author  : cxa
# @File    : test.py
# @Software: PyCharmctx
from appium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
import time
import base64def start_appium():desired_caps = {}desired_caps['platformName'] = 'Android'  # 设备系统desired_caps['deviceName'] = '127.0.0.1:62001'  # 设备名称desired_caps['appPackage'] = 'com.xxxx.xxxx'  # 测试app包名,如何获取包名方式看上面的环境搭建。desired_caps['appActivity'] = 'com.xxxx.xxxx.xxx.xxxx'  # 测试appActivity,如何获取包名方式看上面的环境搭建。desired_caps['platformVersion'] = '4.4.2'  # 设备系统的安卓版本,版本不要太高,设计安全策略得外部因素。desired_caps['noReset'] = True  # 启动后结束后不清空应用数据desired_caps['unicodeKeyboard'] = True  # 此两行是为了解决字符输入不正确的问题desired_caps['resetKeyboard'] = True  # 运行完成后重置软键盘的状态  driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)  # 启动app,启动前记得打开appium服务。wait = WebDriverWait(driver, 60)#设置等待事件try:btn_xpath = '//android.widget.Button[@resource-id="com.alicom.smartdail:id/m_nonum_confirm_btn"]'btn_node = wait.until(EC.presence_of_element_located((By.XPATH, btn_xpath)))#等元素出现再继续,最长等待时间上面设置的60s。# btn_node=driver.find_element_by_xpath(btn_xpath)btn_node.click()except:driver.back()btn_xpath = '//android.widget.Button[@resource-id="com.alicom.smartdail:id/m_nonum_confirm_btn"]'btn_node = wait.until(EC.presence_of_element_located((By.XPATH, btn_xpath)))# btn_node = driver.find_element_by_xpath(btn_xpath)btn_node.click()# sleep 30s# 点击def login_in(driver):id_xpath = '//android.widget.EditText[@content-desc="账户名输入框"]'id_node = driver.find_element_by_xpath(id_xpath)id_node.clear()id_node.send_keys("test")pwd = str(base64.b64decode("MTIzNHF3ZXI="), 'u8')pwd_xpath = '//android.widget.EditText[@content-desc="密码输入框"]'pwd_node = driver.find_element_by_xpath(pwd_xpath)pwd_node.clear()pwd_node.send_keys(pwd)submit = "//android.widget.Button[@text='登录']"submit_node = driver.find_element_by_xpath(submit)submit_node.click()time.sleep(10)if __name__ == '__main__':start_appium()

mitmproxy

代码 mitm_proxy_script.py

# -*- coding: utf-8 -*-
# @Time    : 2018/10/8 11:00
# @Author  : cxa
# @File    : mitm_proxy_script.py
# @Software: PyCharm
import sys
sitename = 'ali'def response(flow):request = flow.requestif '.png' in request.url or 'xxx.x.xxx.com' not in request.url:return  #如果不在观察的url内则返回if 'xxx.x.xxx.com' in request .url:print(request .url)cookies = dict(request.cookies) #转换cookies格式为dictif cookies:save_cookies(repr(cookies))#如果不为空保存cookiesdef save_cookies(cookies):sys.path.append("../")from database import getcookiesgetcookies.insert_data(sitename, cookies) #保存cookies

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/495269.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ZeroMQ全面介绍

★ZMQ是啥玩意儿?   通俗地说,ZMQ是一个开源的、跨语言的、非常简洁的、非常高性能、非常灵活的网络通讯库。它的官方网站在"这里",维基百科的介绍在"这里"(暂时没有中文的维基词条)。这玩意儿推…

Android IDA 动态调试最完善攻略,跨过各种坑

From:https://www.pianshen.com/article/3409449384/ IDA 静态分析 与 动态分析:https://zhuanlan.zhihu.com/p/38983223 新手向总结:IDA 动态调试 So 的一些坑:https://zhuanlan.zhihu.com/p/145383282 Android 逆向之旅 --- …

一场科技盛宴,一次“盈”满天下 安创成长营五期Demo Day完美收官

2018年6月21日,安创成长营第五期Demo Day在北京金隅喜来登拉开帷幕,16家安创成长营五期成员代表齐齐亮相,为资本圈带来了一场集科技、创新、前瞻于一体的项目展示。数百数位业内有影响力的Arm生态系统的合作伙伴、知名品牌厂商创新事业部负责…

安卓逆向_25 --- 密码学 之 《Java加密与解密的艺术》

《Java加密与解密的艺术》中文 PDF版 :https://www.jb51.net/books/65048.html 1. 密码学应用 :https://www.bilibili.com/video/BV1oA411J7Lb?p1 密码学应用 密码学的基本概念,对称密码,公钥密码,Hash算法&#xff…

深度丨一文读懂智能制造的主线——智能生产(工厂/车间数字化)

来源: 亿欧摘要: 发展智能制造对于中国制造业乃至中国经济的重要性不言而喻,先进制造业作为深耕制造业领域的专业媒体,长期关注智能制造发展,助力中国制造转型升级,努力做“中国制造2025”的推动者。近年来…

最好用的十六进制编辑器 010 Editor

最好用的十六进制编辑器010 Editor https://blog.csdn.net/qq_38482805/article/details/89309120 新版010Edit注册机&去除网络验证:https://bbs.pediy.com/thread-249724.htm 010Editor脚本语法入门:https://www.jianshu.com/p/ba60ebd8f916 宇宙…

Object to XML

摘要:本节主要介绍如何把Object对象转换为XML 引言:最近工作中因为工作需要,需要把两个系统之间的传递的报文修改一下(现在系统之间一般都是通过发送xml字符串传数据吧),最开始用的Jdom来实现的&#xff0c…

Science:发现重写创伤记忆的神经元

来源:生物谷摘要:对创伤经历的回忆会导致精神健康问题,如创伤后应激障碍(PTSD),这会破坏一个人的生活。对创伤经历的回忆会导致精神健康问题,如创伤后应激障碍(PTSD)&…

安卓逆向_23 --- Hook 框架 Cydia Substrate( Hook Java层 和 so层)

From:Android Hook 框架 Cydia_substrate 详解:https://www.cnblogs.com/lkislam/p/4859957.html 通过 cydia substrate 对 framework API进行注入:https://www.jianshu.com/p/cc49b30c5b5b Android 逆向之旅 --- Native层的Hook神器Cydia …

高校人工智能热的“冷”思考

来源:中国科学报高校在开设相关专业时,应该组织教授委员会、学术委员会,结合国家的人才政策、产业发展对人才的需求、国内外其他高校同类专业人才培养的情况,就本校开设这方面的专业有无现实条件,怎样进行师资建设、课…

转载:实用 FRIDA 进阶 --- objection :内存漫游、hook anywhere、抓包

转载:实用FRIDA进阶:内存漫游、hook anywhere、抓包:https://www.anquanke.com/post/id/197657 Frida Hook Android 常用方法:https://blog.csdn.net/zhy025907/article/details/89512096 实用FRIDA进阶:脱壳、自动化…

谷歌李飞飞:我们依旧站在人工智能研究的起点

来源:机器人大讲堂摘要:8 年来,在 ImageNet 数据集的训练下,人工智能对于图像识别的准确度整整提高了 10 倍,甚至超越了人类视觉本身。但李飞飞认为,我们对于人工智能的研究仍在起点上。说起人工智能&#…

Android Intent 用法总结

From:https://www.jianshu.com/p/67d99a82509b Android 中提供了 Intent 机制来协助应用间的交互与通讯,Intent 负责对应用中一次操作的动作、动作涉及数据、附加数据进行描述,Android 则根据此 Intent 的描述,负责找到对应的组件…

FRIDA - API使用篇:rpc、Process、Module、Memory 使用方法及示例

官方 API (JavaScript API):https://frida.re/docs/javascript-api/ From: ( FRIDA-API使用篇 ):https://www.anquanke.com/post/id/195215 前言 在这篇文章中来对其官方的一些非常常用的 API 进行学习。所谓工欲善其事,必先利其…

Entity Framework 实体关系总结(转)

通过 Entiy Framework实践系列文章,理了理 Entity Framework 的实体关系。 为什么要写文章来理清这些关系?“血”的教训啊,刚开始使用 Entity Framework 的时候,由于没有静下心来认真理清关系,走了一些"痛不欲生&…

技术架构分析:攻克Dota2的OpenAI-Five

来源:CreateAMind摘要:OpenAI昨日发布研究成果,宣布Dota2 5v5在限定条件下(英雄阵容固定,部分道具和功能禁用)战胜人类半职业选手。本文主要对其模型技术架构做一些分析总结。一、 模型输入与输出模型的输入…

Redis基础-Redis概念及常见命令

1.nosql数据库 NoSQL数据库是一种提供了非关系型数据存储的数据库系统,与传统的关系型数据库(如SQL数据库)不同。NoSQL数据库的特点是灵活性高,能够处理结构化、半结构化或非结构化数据。它们通常用于大数据和实时Web应用。NoSQL数…

Java 高级特性 --- 反射

From:Java 高级特性 --- 反射:https://www.jianshu.com/p/9be58ee20dee From:Java 基础之 --- 反射(非常重要):https://blog.csdn.net/sinat_38259539/article/details/71799078 From:Java 高级…

G20国家科技竞争力大盘点,中国科研创新表现突出,人工智能变道超车

来源:科睿唯安中国科学院文献情报中心和科睿唯安6月25日在北京联合发布了《G20国家科技竞争格局之辩》系列报告,报告分为总体篇及人工智能专题篇(下文有重点介绍),聚焦G20国家的科研产出规模、学术影响力、领域分布、国…

Java中泛型 Class<T>、T与Class<?>、 Object类和Class类、 object.getClass() 和 Object.class

From&#xff1a;Java中泛型 Class<T>、T 与 Class<?>、 Object类 和 Class类、 object.getClass() 和 Object.class &#xff1a;https://www.cnblogs.com/zhaoyanhaoBlog/p/9362267.html Class<T>和 Class<?>类型 有什么区别&#xff1a;https://…