使用 mitmproxy + python 做拦截代理 ( 后附猫眼跳转到美团验证码的拦截脚本 )
From:https://blog.wolfogre.com/posts/usage-of-mitmproxy
http://www.cnblogs.com/grandlulu/p/9525417.html
mitmProxy 介绍:https://blog.****.net/h416756139/article/details/51940757
github地址:https://github.com/mitmproxy/mitmprox
mitmproxy 官网:https://mitmproxy.org
mitmproxy 中文网址:http://www.hpaizhao.cn
mitmproxy 官网文档:https://docs.mitmproxy.org/stable
mitmproxy 官方示例 及 API:(推荐从 simple 开始):https://github.com/mitmproxy/mitmproxy/tree/master/examples
如何突破网站对selenium的屏蔽 : https://blog.****.net/qq_26877377/article/details/83307208
和 Charles 同样强大的 iOS 免费抓包工具 mitmproxy:http://ios.jobbole.com/91030
朋友圈红包照片 mitmproxy 抓包**:https://www.jianshu.com/p/4bef2926d8b9
mitmproxy 系列博文:https://blog.****.net/hqzxsc2006/article/category/6971655
IT 学院 之 Python 网络 --- mitmproxy的使用:https://book.itxueyuan.com/L8P9/DWGg
App爬虫神器mitmproxy和mitmdump的使用:https://yq.aliyun.com/articles/603782?utm_content=m_1000003903
mitmproxy套件使用攻略及定制化开发:https://www.freebuf.com/sectool/76361.html
http proxy 在web渗透上占据着非常重要的地位,这方面的工具也非常多,像burp suite, Fiddler,Charles简直每个搞web的必备神器。本文主要介绍 mitmproxy,是一个较为完整的 mitmproxy 教程,侧重于介绍如何开发拦截脚本,帮助读者能够快速得到一个自定义的代理工具。
本文假设读者有基本的 python 知识,且已经安装好了一个 python 3 开发环境。如果你对 nodejs 的熟悉程度大于对 python,可移步到 anyproxy,anyproxy 的功能与 mitmproxy 基本一致,但使用 js 编写定制脚本。除此之外我就不知道有什么其他类似的工具了,如果你知道,欢迎评论告诉我。
本文基于 mitmproxy v4,当前版本号为 v4.0.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 作为代理可以拦截、存储爬虫获取到的数据,或修改数据调整爬虫的行为。
事实上,以上说的仅是 mitmproxy 以正向代理模式工作的情况,通过调整配置,mitmproxy 还可以作为透明代理、反向代理、上游代理、SOCKS 代理等,
总共有五种代理模式:
- 1、正向代理(regular proxy)启动时默认选择的模式。是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向mitmproxy代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端必须要进行一些特别的设置才能使用正向代理。
- 2、反向代理(reverse proxy)启动参数 -R host。跟正向代理正好相反,对于客户端而言它就像是原始服务器,并且客户端不需要进行任何特别的设置。客户端向mitmproxy代理服务器发送普通请求,mitmproxy转发请求到指定的服务器,并将获得的内容返回给客户端,就像这些内容 原本就是它自己的一样。
- 3、上行代理(upstream proxy)启动参数 -U host。mitmproxy接受代理请求,并将所有请求无条件转发到指定的上游代理服务器。这与反向代理相反,其中mitmproxy将普通HTTP请求转发给上游服务器。
- 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 - 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 的效率并不是特别高。如果只是用于调试目的那还好,但如果要用到生产环境,有大量并发请求通过代理的时候,性能还是稍微差点。
工作原理
mitmproxy 实现原理:
- 客户端发起一个到 mitmproxy 的连接,并且发出 HTTP CONNECT 请求,
- mitmproxy 作出响应 (200),模拟已经建立了 CONNECT 通信管道,
- 客户端确信它正在和远端服务器会话,然后启动 SSL 连接。在 SSL 连接中指明了它正在连接的主机名 (SNI),
- mitmproxy 连接服务器,然后使用客户端发出的 SNI 指示的主机名建立 SSL 连接,
- 服务器以匹配的 SSL 证书作出响应,这个 SSL 证书里包含生成的拦截证书所必须的通用名 (CN) 和服务器备用名 (SAN),
- mitmproxy 生成拦截证书,然后继续进行与第 3 步暂停的客户端 SSL 握手,
- 客户端通过已经建立的 SSL 连接发送请求,
- mitmproxy 通过第 4 步建立的 SSL 连接传递这个请求给服务器。
mitmproxy 工作步骤:
- 设置系统、浏览器、终端等的代理地址和端口为同一局域网中 mitmproxy 所在电脑的 IP 地址,比如我的 PC 开启 mitmproxy 之后,设置 8080 端口,本地 IP 为 192.168.1.130,那么设置 Android HTTP 代理为 192.168.1.130:8080
- 浏览器或移动端访问 mitm.it 来安装 mitmproxy 提供的证书
- 在 mitmproxy 提供的命令行下,或者 mitmweb 提供的浏览器界面中就能看到 Android 端发出的请求。
安装
“安装 mitmproxy”这句话是有歧义的,既可以指“安装 mitmproxy 工具”,也可以指“安装 python 的 mitmproxy 包”,注意后者是包含前者的。
如果只是拿 mitmproxy 做一个替代 fiddler 的工具,没有什么定制化的需求,那完全只需要“安装 mitmproxy 工具”即可,去 mitmproxy 官网 上下载一个 installer 便可开箱即用,不需要提前准备好 python 开发环境。但显然,这不是这里要讨论的,我们需要的是“安装 python 的 mitmproxy 包”。
安装 python 的 mitmproxy 包除了会得到 mitmproxy 工具外,还会得到开发定制脚本所需要的包依赖,其安装过程并不复杂。
首先需要安装好 python,版本需要不低于 3.6,且安装了附带的包管理工具 pip。不同操作系统安装 python 3 的方式不一,参考 python 的下载页,这里不做展开,假设你已经准备好这样的环境了。
在 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版本。
在 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
安装 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
运行启动(启动 mitmproxy 三种方式)
在完成 mitmproxy 的安装之后,mitm 提供的三个命令。要启动 mitmproxy, 用 mitmproxy、mitmdump、mitmweb 这三个命令中的任意一个即可,这三个命令功能一致,且都可以加载自定义脚本,唯一的区别是交互界面的不同。
- mitmproxy 会提供一个在终端下的图形界面,具有修改请求和响应,流量重放等功能,具体操作方式有点 vim 的风格
- mitmdump 可设定规则保存或重放请求和响应,mitmdump 的特点是支持 inline 脚本,由于拥有可以修改 request 和 response 中每一个细节的能力,批量测试,劫持等都可以轻松实现
- mitmweb 提供的一个简单 web 界面,简单实用,初学者或者对终端命令行不熟悉的可以用 mitmweb 界面
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 一样,应该是习惯性动作。多次反复之后就会变得非常数量。
2. mitmweb 命令启动
mitmweb 命令启动后,会提供一个 web 界面,用户可以实时看到发生的请求,并通过 GUI 交互来过滤请求,查看请求数据。形如:
3. mitmdump 命令启动
mitmdump 命令启动后——你应该猜到了,没有界面,程序默默运行,所以 mitmdump 无法提供过滤请求、查看数据的功能,只能结合自定义脚本,默默工作。
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 的配置,而是通过命令行带参数起一个 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
前面那一长串是 Chrome 的的安装路径,应当根据系统实际情况修改,后面两参数设置了代理地址并强制忽略掉证书错误。用 Chrome 打开一个网站,可以看到:
同时在 Edge 上可以看到:
脚本
重点:一个完整的 HTTP flow 会依次触发 requestheaders, request, responseheaders 和 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。
更多的脚本可以参考
启用 SOCKS5 代理
添加参数 --socks
可以使用 mitmproxy 的 SOCK5 代理
透明代理
透明代理是指将网络流量直接重定向到网络端口,不需要客户端做任何设置。这个特性使得透明代理非常适合不能对客户端进行配置的时候,比如说 Android 应用等等。
脚本编写遵循的规定
脚本的编写需要遵循 mitmproxy 规定的套路,这样的套路有两个。
第一个是:编写一个 py 文件供 mitmproxy 加载,文件中定义了若干函数,这些函数实现了某些 mitmproxy 提供的事件,mitmproxy 会在某个事件发生时调用对应的函数,形如:
-
import mitmproxy.http
-
from mitmproxy import ctx
-
-
num = 0
-
-
-
def request(flow: mitmproxy.http.HTTPFlow):
-
global num
-
num = num + 1
-
ctx.log.info("We've seen %d flows" % num)
第二个是:编写一个 py 文件供 mitmproxy 加载,文件定义了变量 addons,addons 是个数组,每个元素是一个类实例,这些类有若干方法,这些方法实现了某些 mitmproxy 提供的事件,mitmproxy 会在某个事件发生时调用对应的方法。这些类,称为一个个 addon
,比如一个叫 Counter 的 addon:
-
import mitmproxy.http
-
from mitmproxy import ctx
-
-
-
class Counter:
-
def __init__(self):
-
self.num = 0
-
-
def request(self, flow: mitmproxy.http.HTTPFlow):
-
self.num = self.num + 1
-
ctx.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
-
……
这就说明自定义脚本生效了。
---------------------------------------------------------------------------------------------------------------------------------
mitmproxy启动时可以使用 -s 参数导入外部的脚本进行拦截处理
比如我要修改一个每个链接的响应头的
python脚本:
1、简单方法
-
from mitmproxy import http
-
-
def 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
事件
上述的脚本估计不用我解释相信大家也看明白了,就是当 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 body
-
is 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 body
-
is 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, or
-
interrupted connections. This is distinct from a valid server HTTP
-
error 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 message
-
will 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. The
-
WebSocket-specific headers can be manipulated to alter the
-
handshake. The flow object is guaranteed to have a non-None request
-
attribute.
-
"""
-
-
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 or
-
server. The most recent message will be flow.messages[-1]. The
-
message is user-modifiable. Currently there are two types of
-
messages, 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 can
-
correspond 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 can
-
correspond 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 will
-
be 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 a
-
set-like object containing the keys of all changed options. This
-
event is called during startup with all options in the updated set.
-
"""
-
-
def done(self):
-
"""
-
Called when the addon shuts down, either by being removed from
-
the mitmproxy instance, or when mitmproxy itself shuts down. On
-
shutdown, this event is called after the event loop is
-
terminated, guaranteeing that it will be the final event an addon
-
sees. Note that log handlers are shut down at this point, so
-
calls 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 Loader
-
object, which contains methods for adding options and commands. This
-
method is where the addon configures itself.
-
"""
-
-
def log(self, entry: mitmproxy.log.LogEntry):
-
"""
-
Called whenever a new log entry is created through the mitmproxy
-
context. Be careful not to log from this event, which will cause an
-
infinite 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 be
-
loaded.
-
"""
-
-
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.
-
"""
针对 http,常用的 API
-
# 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)
示 例
估计看了那么多的事件你已经晕了,正常,鬼才会记得那么多事件。事实上考虑到 mitmproxy 的实际使用场景,大多数情况下我们只会用到针对 HTTP 生命周期的几个事件。再精简一点,甚至只需要用到 http_connect
、request
、response
三个事件就能完成大多数需求了。
这里以一个稍微有点黑色幽默的例子,覆盖这三个事件,展示如果利用 mitmproxy 工作。
需求是这样的:
- 因为百度搜索是不靠谱的,所有当客户端发起百度搜索时,记录下用户的搜索词,再修改请求,将搜索词改为“360 搜索”;
- 因为 360 搜索还是不靠谱的,所有当客户端访问 360 搜索时,将页面中所有“搜索”字样改为“请使用谷歌”。
- 因为谷歌是个不存在的网站,所有就不要浪费时间去尝试连接服务端了,所有当发现客户端试图访问谷歌时,直接断开连接。
- 将上述功能组装成名为 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.com
-
if flow.request.host == "www.google.com":
-
# 返回一个非 2xx 响应断开连接
-
flow.response = http.HTTPResponse.make(404)
为了实现第四个需求,我们需要将代码整理一下,即易于管理也易于查看。
创建一个 joker.py
文件,内容为:
-
import mitmproxy.http
-
from mitmproxy import ctx, http
-
-
-
class Joker:
-
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"))
-
flow.request.query.set_all("wd", ["360搜索"])
-
-
def response(self, flow: mitmproxy.http.HTTPFlow):
-
if flow.request.host != "www.so.com":
-
return
-
-
text = 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 ctx
-
-
-
class Counter:
-
def __init__(self):
-
self.num = 0
-
-
def request(self, flow: mitmproxy.http.HTTPFlow):
-
self.num = self.num + 1
-
ctx.log.info("We've seen %d flows" % self.num)
创建一个 addons.py
文件,内容为:
-
import counter
-
import joker
-
-
addons = [
-
counter.Counter(),
-
joker.Joker(),
-
]
将三个文件放在相同的文件夹,在该文件夹内启动命令行,运行:mitmweb -s addons.py
老规矩,关闭所有 Chrome 窗口,从命令行中启动 Chrome 并指定代理且忽略证书错误。
测试一下运行效果:
脚本编写
From:https://www.jianshu.com/p/0eb46f21fee9
GitHub:https://github.com/ctwj/mitm_exam
编写脚本的话,主要用到的有两个东西
- 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.tnwz
-
self.answer_set = self.db.quizzes
-
-
def 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 类型 TTPResponse
-
HTTPResponse 有个属性为 content 就是response在内容,更多属性可以查看 文档
-
:param flow:
-
:return:
-
'''
-
-
if flowfilter.match(self.filter, flow):
-
#匹配上后证明抓到的是问题了, 查答案
-
data = flow.response.content
-
quiz = 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'])-1
-
options = answer['options']
-
print(options[answerIndex])
-
-
#这里简单演示下start事件
-
def start():
-
return TNWZ()
使用方法:mitmdump -s quiz.py
启动后也可以编辑脚本文件,mitmdump会自动重新加载不需要重新运行命令,本来是写了一个头脑王者自动抓问题找答案,结果游戏被封了,只在本地模拟下
是不是很简单, 如果还不够,可以考虑,在发送答案的时候, 拦截请求,然后替换为标准答案再发送到服务器,是不是很给力。工具都是死的,就看怎么用了。
利用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 base64
-
-
-
def 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.request
-
if '.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格式为dict
-
if cookies:
-
save_cookies(repr(cookies))#如果不为空保存cookies
-
-
-
def save_cookies(cookies):
-
sys.path.append("../")
-
from database import getcookies
-
getcookies.insert_data(sitename, cookies) #保存cookies
拦截猫眼跳转到美团的验证码
参考:selenium + chromedriver 被反爬的解决方法 :https://blog.****.net/weixin_39847926/article/details/82262048
如何突破网站对selenium的屏蔽 : https://blog.****.net/qq_26877377/article/details/83307208
使用 selenium 驱动 Chrome 浏览器时,猫眼的 js 代码 中有检测,所以需要使用 mitmproxy 做代理拦截请求,然后把 js 文件中对 selenium 检测的关键字全部替换了。
如果不替换,selenium 驱动浏览器出现美团验证码的时候,手动输入验证码点击确定会报错,
拦截替换后,手动输入验证码可以通过。
拦截脚本 (mitmproxy_script.py):
-
#!/usr/bin/python3
-
# -*- coding: utf-8 -*-
-
# @Author :
-
# @File : mitmproxy_script.py
-
# @Software : PyCharm
-
# @description : XXX
-
-
-
from mitmproxy import http
-
-
import winreg
-
import requests
-
import ctypes
-
import socks
-
-
import socket
-
-
-
# ############################# 改系统代理 ############################################
-
proxy = "117.60.167.134"
-
port = 4532
-
-
-
INTERNET_SETTINGS = winreg.OpenKey(
-
winreg.HKEY_CURRENT_USER,
-
r'Software\Microsoft\Windows\CurrentVersion\Internet Settings',
-
0,
-
winreg.KEY_ALL_ACCESS
-
)
-
-
INTERNET_OPTION_REFRESH = 37
-
INTERNET_OPTION_SETTINGS_CHANGED = 39
-
-
internet_set_option = ctypes.windll.Wininet.InternetSetOptionW
-
-
socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, proxy, port)
-
socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS4, proxy, port)
-
socks.setdefaultproxy(socks.PROXY_TYPE_HTTP, proxy, port)
-
socket.socket = socks.socksocket
-
-
-
def set_key(name, value):
-
"""
-
# 修改键值
-
:param name:
-
:param value:
-
:return:
-
"""
-
_, reg_type = winreg.QueryValueEx(INTERNET_SETTINGS, name)
-
winreg.SetValueEx(INTERNET_SETTINGS, name, 0, reg_type, value)
-
-
##########################################################################
-
-
-
def chang_ip():
-
"""
-
启用代理
-
:return:
-
"""
-
set_key('ProxyEnable', 1) # 启用
-
set_key('ProxyOverride', u'*.local;<local>') # 绕过本地
-
set_key('ProxyServer', u'{}:{}'.format(proxy,port)) # 代理IP及端口
-
internet_set_option(0, INTERNET_OPTION_REFRESH, 0, 0)
-
internet_set_option(0, INTERNET_OPTION_SETTINGS_CHANGED, 0, 0)
-
# 停用代理
-
set_key('ProxyEnable', 0)
-
internet_set_option(0, INTERNET_OPTION_REFRESH, 0, 0)
-
internet_set_option(0, INTERNET_OPTION_SETTINGS_CHANGED, 0, 0)
-
-
-
def request(flow):
-
print(flow.request.url)
-
# if 'bs/yoda-static/file' in flow.request.url:
-
# print('*' * 100)
-
# print(flow.request.url)
-
# flow.response.text = flow.response.text.replace("webdriver", "fuck_that")
-
# flow.response.text = flow.response.text.replace("Webdriver", "fuck_that")
-
# flow.response.text = flow.response.text.replace("WEBDRIVER", "fuck_that")
-
# chang_ip()
-
pass
-
-
-
def response(flow):
-
# print(type(flow.response.text))
-
if 'webdriver' in flow.response.text:
-
print('*' * 100)
-
print('find web_driver key')
-
flow.response.text = flow.response.text.replace("webdriver", "fuck_that_1")
-
if 'Webdriver' in flow.response.text:
-
print('*' * 100)
-
print('find web_driver key')
-
flow.response.text = flow.response.text.replace("Webdriver", "fuck_that_2")
-
if 'WEBDRIVER' in flow.response.text:
-
print('*' * 100)
-
print('find web_driver key')
-
flow.response.text = flow.response.text.replace("WEBDRIVER", "fuck_that_3")
-
猫眼 对 selenium 驱动 的关键字有三个 :webdriver、Webdriver、WEBDRIVER,把这三个全替换了。
然后执行:mitmweb -s mitmproxy_script.py
使用 selenium 驱动 Chrome 访问猫眼时,如果再出现验证码,手动输入就可以通过了。