Javascript is required
Mitmproxy

Mitmproxy

https://www.mitmproxy.org/

mitmproxy 就是用于 MITM 的 proxy,MITM 即中间人攻击(Man-in-the-middle attack)。用于中间人攻击的代理首先会向正常的代理一样转发请求,保障服务端与客户端的通信,其次,会适时的查、记录其截获的数据,或篡改数据,引发服务端或客户端特定的行为。

image-20201223201756570

特点

  1. 和正常的代理一样转发请求,保障服务端、客户端运行
  2. 拦截请求、修改请求、拦截返回、修改返回
  3. 载入自定义Python脚本

三大组件

  • mitmproxy
  • mitmdump
  • mitmweb

安装

$ mitmweb --version
Mitmproxy: 4.0.4
Python:    3.7.3
OpenSSL:   OpenSSL 1.1.0i  14 Aug 2018
Platform:  Windows-10-10.0.17763-SP0
$ mitmdump --version
Mitmproxy: 4.0.4
Python:    3.7.3
OpenSSL:   OpenSSL 1.1.0i  14 Aug 2018
Platform:  Windows-10-10.0.17763-SP0

安装证书

前提要启动服务

mitmproxy -p 8200 --set block_global=false

将浏览器切换代理8080端口

http://mitm.it/

image-20201223201809899

下载相关平台的新人证书然后导入,即可。

运行

要启动 mitmproxy 用 mitmproxymitmdumpmitmweb 这三个命令中的任意一个即可,这三个命令功能一致,且都可以加载自定义脚本,唯一的区别是交互界面的不同。

image-20201223201819924

命令

mitmproxy 
-p  // 设置监听端口号
--set block_global=false // 公网环境需要设置否则失败连接

mitmproxy是一个控制台工具 使用?快捷键从任何mitmproxy屏幕上查看帮助文档

过滤表达式f

快捷键f

~a过滤返回来的资源比如 CSS, Javascript, Flash, images.
~b regexBody
~bq regexRequest body
~bs regexResponse body
~c intHTTP response code
~d regex域名正则
~dst regexMatch destination address
~eMatch error
~h regexHeader
~hq regexRequest header
~hs regexResponse header
~httpMatch HTTP flows
~m regexMethod
~markedMatch marked flows
~qMatch request with no response
~sMatch response
~src regexMatch source address
~t regexContent-type header
~tcpMatch TCP flows
~tq regexRequest Content-Type header
~ts regexResponse Content-Type header
~u regexURL
~websocketMatch WebSocket flows
!not
&and
|or
(...)grouping
  • 正则表达式是Python风格的

例子

过滤只有百度的域名

f 然后输入过滤规则~d baidu.com

image-20201223201911052

过滤请求方法为Post且域名为包含baidu的数据流

~d baidu.com % ~m post

image-20201223201919539

断点拦截i

hint type=“info” title=“快捷键”

快捷键i

/hint

请求拦截

比如想要拦截itaolaity.com 的数据

image-20201223201927134

然后停留到这里来了,且时间为红色标记

image-20201223201936997

按下回车查看详细信息。且按下e

image-20201223201944205

选择要修改的参数

比如
cookies
url
...

最后返回界面按下a 继续往下走。

脚本 - mitmdump

编写一个py文件提供给mitmproxy加载。

文件中定义了若干函数,这些函数实现了某些 mitmproxy 提供的事件,mitmproxy 会在某个事件发生时调用对应的函数

或者 编写一个 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()
]

启动

mitmdump -p 8080

编写一个py脚本用来供mitmproxy加载。

实现mitmproxy提供的事件,mitmproxy会在某个事件发生调用相关函数。

mitmdump 
-p 指定端口号
-s 指定py脚本
-w 指定写入文件 默认将所有流量写入outfile

测试

import mitmproxy.http # http处理模块
from mitmproxy import ctx # 日志模块

def request(flow):
    print("URL: \t"+flow.request.url)
    ctx.log.info(str(flow.request.headers)) # 请求头
    ctx.log.warn(str(flow.request.path))
λ mitmdump -p 8080 -s main.py
Loading script main.py # 加载脚本
Proxy server listening at http://*:8080 #监听端口

127.0.0.1:14072: clientconnect
URL:    https://sug.so.360.cn/suggest?encodein=UTF-8&encodeout=UTF-8&format=opensearch&word=
Headers[(b'Host', b'sug.so.360.cn'), (b'Connection', b'keep-alive'), (b'Sec-Fetch-Site', b'none'), (b'Sec-Fetch-Mode', b'no-cors'), (b'User-Agent', b'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36'), (b'Accept-Encoding', b'gzip, deflate, br'), (b'Accept-Language', b'zh,zh-CN;q=0.9,en;q=0.8'), (b'Cookie', b'__huid=110y7q8GOJVD4AeHprC/qVgeaKmeZo0r/0O1mg48NrWBQ=; __guid=209084363.1328179603006700032.1557537740001.3984; __gid=156009789.758223608.1572875073297.1572875076825.4; __DC_gid=156009789.758223608.1572875073297.1573629455750.9')]
/suggest?encodein=UTF-8&encodeout=UTF-8&format=opensearch&word=

127.0.0.1:14072: GET https://sug.so.360.cn/suggest?encodein=UTF-8&encodeout=UTF-8&format=opensearch&word=
              << 200 OK 7b
URL:    https://sug.so.360.cn/suggest?encodein=UTF-8&encodeout=UTF-8&format=opensearch&word=i
Headers[(b'Host', b'sug.so.360.cn'), (b'Connection', b'keep-alive'), (b'Sec-Fetch-Site', b'none'), (b'Sec-Fetch-Mode', b'no-cors'), (b'User-Agent', b'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36'), (b'Accept-Encoding', b'gzip, deflate, br'), (b'Accept-Language', b'zh,zh-CN;q=0.9,en;q=0.8'), (b'Cookie', b'__huid=110y7q8GOJVD4AeHprC/qVgeaKmeZo0r/0O1mg48NrWBQ=; __guid=209084363.1328179603006700032.1557537740001.3984; __gid=156009789.758223608.1572875073297.1572875076825.4; __DC_gid=156009789.758223608.1572875073297.1573629455750.9')]
/suggest?encodein=UTF-8&encodeout=UTF-8&format=opensearch&word=i

定义了一个request()方法。参数为flow,它其实是一个HTTPFlow对象。通过request属性即可获取到当前请求对象。

日志输出

from mitmproxy import ctx

可以设定不同级别的不同颜色的结果

ctx.log.info() // 白色
     ...warn() // 黄色
     ...error()// 红色

调用ctx模块

Request对象

Request对象什么功能呢?

例子 - 常用属性

import mitmproxy.http
from mitmproxy import ctx # 日志模块

def request(flow):
    info = ctx.log.info
    request = flow.request
    print("\n\n+++++++++++++++++  开始  ++++++++++++++++++++++")
    info("URL: \t"+request.url)
    info("Headers: \t" + str(request.headers))
    info("Cookies: \t"+ str(request.cookies))
    info("Method: \t" + request.method)
    info("Port: \t"+ str(request.port))
    info("Scheme: \t" + request.scheme)
    print("+++++++++++++++++++++++++++++++++++++++\n\n")
URL: https://blog.itaolaity.com/usr/plugins/Mirages/biaoqing/paopao/E5A4A7E68B87E68C87_2x.png                                                          
Headers: Headers[(b'Host', b'blog.itaolaity.com'), (b'Connection', b'keep-alive')
Cookies: MultiDictView[['crisp-client%2Fsession%2F1e699570-3cf8-4358-a...                                                                                                               
Method: GET                                                                                                                                       
Port: 443                                                                                                                                               
Scheme: https

伪造站点

import mitmproxy.http

def request(flow):
    url = 'https://blog.itaolaity.com/'
    if 'itaolaity.com' not in flow.request.url:
        flow.request.url = url

image-20201223202018320

可以看到无论访问什么网站,都会访问指定url但是上面的域名还是要访问的。

Response对象

响应内容,Response Body才是爬取的结果。

测试

import mitmproxy.http

def request(flow):
    pass

def response(flow):
    response = flow.response
    print(response.status_code)
    print("响应内容: \t" + response.text)

事件

事件针对不同生命周期分为 5 类 。

同样是一次 web 请求,我可以理解为“HTTP 请求 -> HTTP 响应”的过程,也可以理解为“TCP 连接 -> TCP 通信 -> TCP 断开”的过程。那么,如果我想拒绝来个某个 IP 的客户端请求,应当注册函数到针对 TCP 生命周期 的 tcp_start 事件,又或者,我想阻断对某个特定域名的请求时,则应当注册函数到针对 HTTP 声明周期的 http_connect 事件

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 response(self, flow: mitmproxy.http.HTTPFlow):

(Called when) 来自服务端端的 HTTP 响应被成功完整读取

def error(self, flow: mitmproxy.http.HTTPFlow):

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

tcp周期

# 建立了一个 TCP 连接
def tcp_start(self, flow: mitmproxy.tcp.TCPFlow):
# TCP 连接收到了一条消息
def tcp_message(self, flow: mitmproxy.tcp.TCPFlow):
# 发生了 TCP 错误
def tcp_error(self, flow: mitmproxy.tcp.TCPFlow):
#  TCP 连接关闭
def tcp_end(self, flow: mitmproxy.tcp.TCPFlow):

Websocket 生命周期

def websocket_handshake(self, flow: mitmproxy.http.HTTPFlow):
def websocket_start(self, flow: mitmproxy.websocket.WebSocketFlow):
def websocket_message(self, flow: mitmproxy.websocket.WebSocketFlow):
def websocket_error(self, flow: mitmproxy.websocket.WebSocketFlow):
def websocket_end(self, flow: mitmproxy.websocket.WebSocketFlow):

网络连接生命周期

#   客户端连接到了 mitmproxy。注意一条连接可能对应多个 HTTP 请求
def clientconnect(self, layer: mitmproxy.proxy.protocol.Layer):
# 客户端断开了和 mitmproxy 的连接
def clientdisconnect(self, layer: mitmproxy.proxy.protocol.Layer):
def serverconnect(self, conn: mitmproxy.connections.ServerConnection):
def serverdisconnect(self, conn: mitmproxy.connections.ServerConnection):
def next_layer(self, layer: mitmproxy.proxy.protocol.Layer):

参考

  • 官方文档

https://docs.mitmproxy.org/stable/concepts-filters/

  • 官网

https://www.mitmproxy.org/

  • 博客

https://www.cnblogs.com/grandlulu/p/9525417.html

https://blog.wolfogre.com/