Mitmproxy精华笔记

是啊,世界那么残酷,无论你怎么反抗它,它都沉默无声地运转着,根本不管你会怎么想。你在大使的沙拉里放入了鱼胆,苦得他落荒而逃,可他选中的小羊还是被宰杀了,剥了皮泡在胡椒和香叶汤里;你吓得那些红男绿女落荒而逃,可不久之后他们又会聚在你家的舞厅里,就着靡靡之音跳贴面舞,喝醉的男男女女搂在一起,在午夜里高声调笑;你吓走了种马老爹带回来的女明星,可是几天之后卧室里换了新的画作,又有新的女人从老爹的豪车上下来,袅袅婷婷地踏入你家的房门,袅袅婷婷地跟着他走向卧室,流水般的裸女在老爹的大床上滚过。那么多年过去了你还是那么弱小,你自以为足够叛逆了,可你根本不曾改变这个世界,你只是躲开不去看它那残酷的一面。现在你回想起来了吧?你那被愤怒和不甘支配的童年。

介绍

中间代理抓包处理库~类似与burpsuite,fidder,wireshark

安装方法:

pip3 install mitmproxy

安装好后,会有mitmproxy、mitmdump、mitmweb三个使用方法,在cmd命令下输入:

mitmdump --version

出现如下结果表示安装成功:

Mitmproxy: 4.0.4
Python:    3.6.3
OpenSSL:   OpenSSL 1.1.0g  2 Nov 2017
Platform:  Windows-10-10.0.16299-SP0

注意mitmproxy是不支持windos的噢~mitmweb是web界面的管理器。分别介绍他们的功能:

mitmproxy:提供一个命令行界面,实时显示发生的请求
mitmweb:web页面的获取发生的请求,可视乎查看过滤内容
mitmdump:没有界面,程序默默运行,所以 mitmdump 无法提供过滤请求、查看数据的功能,只能结合自定义脚本,默默工作。

基础使用

首先要开启抓包,使用命令:

mitmdump -p 1080
# 流量走本地代理的1080端口

如果发现需证书有问题,我们还需要安装 mitmproxy 提供的证书,要不抓包失败。打开网址 http://mitm.it , 选择匹配的平台,下载 HTTPS 证书。并按照对应的步骤进行安装

注意不要有端口冲突!!

当启动mitmproxy后,会在根目录下生成证书

C:\Users\langzi\.mitmproxy

这个时候安装证书即可mitmproxy-ca-cert.p12

资料来自 碳基体

要捕获https证书,就得解决证书认证的问题,因此需要在通信发生的客户端安装证书,并且设置为受信任的根证书颁布机构。下面介绍6种客户端的安装方法。

当我们初次运行mitmproxy或mitmdump时,

会在当前目录下生成 ~/.mitmproxy文件夹,其中该文件下包含4个文件,这就是我们要的证书了。

mitmproxy-ca.pem 私钥

mitmproxy-ca-cert.pem 非windows平台使用

mitmproxy-ca-cert.p12 windows上使用

mitmproxy-ca-cert.cer 与mitmproxy-ca-cert.pem相同,android上使用

1. Firefox上安装

preferences-Advanced-Encryption-View Certificates-Import (mitmproxy-ca-cert.pem)-trust this CA to identify web sites

2. chrome上安装

设置-高级设置-HTTPS/SSL-管理证书-受信任的根证书颁发机构-导入mitmproxy-ca-cert.pem

2. osx上安装

双击mitmproxy-ca-cert.pem - always trust

3.windows7上安装

双击mitmproxy-ca-cert.p12-next-next-将所有的证书放入下列存储-受信任的根证书发布机构

4.iOS上安装

将mitmproxy-ca-cert.pem发送到iphone邮箱里,通过浏览器访问/邮件附件

我将证书放在了vps上以供下载

http://tanjiti.com/crt/mitmproxy-ca-cert.pem mitmproxy iOS

http://tanjiti.com/crt/mitmproxy-ca-cert.cer mitmproxy android

http://tanjiti.com/crt/mitmproxy-ca-cert.p12 windows

http://tanjiti.com/crt/PortSwigger.cer BurpSuite (burpsuite的证书,随便附上)

5.iOS模拟器上安装

git clone https://github.com/ADVTOOLS/ADVTrustStore.gitcd ADVTrustStore/

DANI-LEE-2:ADVTrustStore danqingdani$ python iosCertTrustManager.py -a ~/iostools/mitmproxy-ca-cert.pem

subject= CN = mitmproxy, O = mitmproxyImport certificate to iPhone/iPad simulator v5.1 [y/N] yImporting to /Users/danqingdani/Library/Application Support/iPhone Simulator/5.1/Library/Keychains/TrustStore.sqlite3 Certificate added

实际上上面的操作就是给 ~/Library/Application\ Support/iPhone\ Simulator/5.1/Library/Keychains/TrustStore.sqlite3 数据库中表tsettings表中插入证书数据

6.Android上安装

将mitmproxy-ca-cert.cer 放到sdcard根目录下

选择设置-安全和隐私-从存储设备安装证书

然后再浏览器中设置本地代理,方法如下:

这个时候浏览器的请求都会被mitmproxy捕获噢~

mitmdump 命令最大的特点就是可以自定义脚本,你可以在脚本中对请求或者响应内容通过编程的方式来控制,实现数据的解析、修改、存储等工作

使用方法为:

mitmdump -p 1080 -s script.py

你的script.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 = [
    Counter()
]

然后在浏览器打开网站,浏览页面,这个时候显示数据如下:

说明捕获到数据了噢~

还能这样这样写script.py,获取每次请求头:

def request(flow): 
    print(flow.request.headers) # 打印请求头

这里我会重点说说自定义脚本的使用方法与功能

生命周期

即发起一次http请求的全部过程,从发起连接http_connect到获取到发起连接的响应头requestheaders,然后获取到想要发起连接的内容request。

发起连接后,得到获取内容的响应头responseheaders,然后获取到返回相应的内容response。

上面的script文件中的函数名,request都是mitm定义好的了函数名,我们每次要写对应功能的脚本时候,需要找到相关功能提供的函数名即可。

http_connect

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

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

requestheaders

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

(Called when) 来自客户端的 HTTP 请求的头部被成功读取。此时 flow 中的 request 的 body 是空的。

request

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

(Called when) 来自客户端的 HTTP 请求被成功完整读取。

responseheaders

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

(Called when) 来自服务端的 HTTP 响应的头部被成功读取。此时 flow 中的 response 的 body 是空的。

response

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

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

通用周期

即每次发起连接或者收到相应内容,都可以使用下面的函数

error

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

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

configure

def configure(self, updated: typing.Set[str]):

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

done

def done(self):

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

load

def load(self, entry: mitmproxy.addonmanager.Loader):

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

log

def log(self, entry: mitmproxy.log.LogEntry):

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

running

def running(self):

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

update

def update(self, flows: typing.Sequence[mitmproxy.flow.Flow]):

(Called when) 一个或多个 flow 对象被修改了,通常是来自一个不同的 addon。

功能使用

response 1

获取返回信息

from mitmproxy import http
def response(flow:http.HTTPResponse)->None:
    print('-'*30)
    print(flow.request)
    # Request(GET langzi.fun:80/upload/TIM%E6%88%AA%E5%9B%BE20190422162029.png)
    # 请求发送 请求的url
    print(flow.response)
    # Response(200 OK, image/png, 61.29k)
    # 返回状态码 返回的信息类型 返回的文件大小
    print(flow.client_conn)
    # <ClientConnection: 127.0.0.1:28055>
    # 本地发起连接
    print(flow.server_conn)
    # <ServerConnection: langzi.fun:80>
    # 服务器端口
    print('-'*30)

response 2

获取请求头,主机,查询

import mitmproxy
def response(flow: mitmproxy.http.HTTPFlow)->None:
    print(flow.request.headers)
    # Headers[(b'Host', b'www.syztfj.com'), (b'User-Agent', b'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:49.0) Gecko/20100101 Firefox/49.0'), (b'Accept', b'*/*'), (b'Accept-Language', b'zh-CN,zh;q=0.8,en-US;q=0.5,en;q
    # =0.3'), (b'Accept-Encoding', b'gzip, deflate'), (b'Referer', b'http://www.syztfj.com/css/stylelunbo_nei.css'), (b'Cookie', b'online_service_status=1; ASPSESSIONIDCQTTRCBC=HDPJBKAAAFJOACLEDEGCNDAF'), (b'DNT',
    # b'1'), (b'X-Forwarded-For', b'8.8.8.8'), (b'Connection', b'keep-alive')]

    print(flow.request.host)
    # www.syztfj.com

    print(flow.request.query)
    # 访问网址 http://c.cnzz.com/core.php?web_id=1271804123&t=z ,返回的结果如下:
    # MultiDictView[('web_id', '1271804123'), ('t', 'z')]
    # 可以使用flow.request.query.keys()获取所有的键

resnose 3

获取url动态链接

from mitmproxy import http
import re
from urllib.parse import urljoin
def response(flow:http.HTTPFlow):
    if "Content-Type" in flow.response.headers and flow.response.headers["Content-Type"].find("text/html") != -1:
        pageUrl = flow.request.url
        pageText = flow.response.text
        pattern = (r"<a\s+(?:[^>]*?\s+)?href=(?P<delimiter>[\"'])"
                   r"(?P<link>(?!https?:\/\/|ftps?:\/\/|\/\/|#|javascript:|mailto:).*?)(?P=delimiter)")
        rel_matcher = re.compile(pattern, flags=re.IGNORECASE)
        rel_matches = rel_matcher.finditer(pageText)
        for match_num, match in enumerate(rel_matches):
            (delimiter, rel_link) = match.group("delimiter", "link")
            abs_link = urljoin(pageUrl, rel_link)
            print('LINKS:'+abs_link)
            # LINKS:http://bbs.kjj.com/space.php?username=%BB%F9%BD%F0%CA%D6
            # LINKS:http://bbs.kjj.com/redirect.php?tid=655201&amp;goto=lastpost#lastpost
            # LINKS:http://bbs.kjj.com/space-username-gtthc.html
            # LINKS:http://bbs.kjj.com/forum-154-1.html
            # LINKS:http://bbs.kjj.com/forum-154-1.html
            # LINKS:http://bbs.kjj.com/space.php?username=%B4%BA%C8%D5%D4%D8%D1%F4
            # LINKS:http://bbs.kjj.com/redirect.php?tid=968702&amp;goto=lastpost#lastpost
            # LINKS:http://bbs.kjj.com/space-username-%C0%B6%CC%EC%B7%E3.html
            # LINKS:http://bbs.kjj.com/forum-144-1.html
            # LINKS:http://bbs.kjj.com/forum-144-1.html
            # LINKS:http://bbs.kjj.com/space.php?username=%D4%C2%D7%ED%BB%A8%D2%F5

response 4

获取相关结果

def parser_data(self):
    result = dict()
    result['url'] = self.flow.request.url
    # 当前网址
    result['path'] = '/{}'.format('/'.join(self.flow.request.path_components))
    # 当前路径
    result['host'] = self.flow.request.host
    # 当前主机
    result['port'] = self.flow.request.port
    # 主机端口
    result['scheme'] = self.flow.request.scheme
    # http or https
    result['method'] = self.flow.request.method
    # 请求方式
    result['status_code'] = self.flow.response.status_code
    # 状态码
    result['content_length'] = int(self.flow.response.headers.get('Content-Length', 0))
    # 返回内容长度
    result['request_header'] = self.parser_header(self.flow.request.headers)
    # 请求头
    result['request_content'] = self.flow.request.content
    # 返回内容
    result['query'] = self.flow.request.query
    # 查询参数
    return result

重定向

from mitmproxy import http
def request(flow: http.HTTPFlow) -> None:
    if flow.request.pretty_host == "example.org":
        flow.request.host = "mitmproxy.org"
        # 在这里把提交的主机地址修改成Mitmproxy.org

使用日志

使用日志的时候,不能再print,因为log.xxx自动输出内容

from mitmproxy import ctx
def load(l):
    ctx.log.info("This is some informative text.")
    ctx.log.warn("This is a warning.")
    ctx.log.error("This is an error.")

写入文件

import random
import sys
from mitmproxy import io, http
import typing  # noqa


class Writer:
    def __init__(self, path: str) -> None:
        self.f: typing.IO[bytes] = open(path, "wb")
        self.w = io.FlowWriter(self.f)

    def response(self, flow: http.HTTPFlow) -> None:
        if random.choice([True, False]):
            self.w.add(flow)

    def done(self):
        self.f.close()


addons = [Writer(sys.argv[1])]

读取文件

from mitmproxy import io
from mitmproxy.exceptions import FlowReadException
import pprint
import sys


with open(sys.argv[1], "rb") as logfile:
    freader = io.FlowReader(logfile)
    pp = pprint.PrettyPrinter(indent=4)
    try:
        for f in freader.stream():
            print(f)
            print(f.request.host)
            pp.pprint(f.get_state())
            print("")
    except FlowReadException as e:
        print("Flow file corrupted: {}".format(e))

获取路径

from pathod import pathoc

p = pathoc.Pathoc(("google.com", 80))
p.connect()
print(p.request("get:/"))
print(p.request("get:/foo"))

使用案例

官方文档

参考链接 1

参考连接 2

坚持原创技术分享,您的支持将鼓励我继续创作!
------ 本文结束 ------

版权声明

LangZi_Blog's by Jy Xie is licensed under a Creative Commons BY-NC-ND 4.0 International License
由浪子LangZi创作并维护的Langzi_Blog's博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证
本文首发于Langzi_Blog's 博客( http://langzi.fun ),版权所有,侵权必究。

0%