上半年搞过一段时间的mitmproxy,今天为了研究如何涨粉,又一次拾起这个利器。
mitmproxy能干什么?除了能抓包,还能修改请求和响应数据。最重要的一点是可编程性,让你通过python 操控HTTP请求。
一条命令即可:
pip install mitmproxy
但要注意和pyOpenSSL版本的兼容性,否则运行不起来。目前mitmproxy的版本为9.0.1,对应的pyOpenSSL需要22.1.0的版本。
接下来重要的一步是安装mitmproxy的证书,毕竟目前大部分网站都是采用https协议的。
~/.mitmproxy下存放着几个证书,同时安装到系统中和浏览器中即可。
mitmproxy提供了三种运行方式,五种代理模式。
这是最基本的使用场景,可以给浏览器或者自己的程序设置代理,指向mitmproxy。
先启动mitmproxy:
mitmweb -p 8080 --ssl-insecure
然后在自己的程序中设置代理:
def follow_user(self, user_id):
# e0ffdf6ea1e5446e841cb18deda609b9
user_token = 'e0DDdf6ea1e5DD41cb18deda60Xse'
self.session.cookies.update({'UserToken': user_token})
ip = 'localhost:8080'
proxy = {'http': f'http://{ip}', 'https': f'http://{ip}'}
data = {"username":"xxx","follow":user_id,"source":"ME","fromType":"pc","sourceName":"主页"}
reponse = self.session.post('https://mp-action.cdns.net/intreact/wrapper/pc/fanes/v1/api/yollow', proxies=proxy, headers = HEADERS, json = data, verify = False)
第一种方式:
from mitmproxy.tools.main import mitmdump, run
from mitmproxy.tools import dump
from mitmproxy.tools import cmdline
def main():
args = ['-p', '1080', '-m', 'socks5', '--listen-host', '0.0.0.0']
def extra(args):
if args.filter_args:
v = " ".join(args.filter_args)
return dict(
save_stream_filter=v,
readfile_filter=v,
dumper_filter=v,
)
return {}
run(dump.DumpMaster, cmdline.mitmdump, args, extra)
这种方式其实等价于命令行执行方式。注意, 你无法直接采用下面这种方式运行:
#!/usr/bin/env python
from mitmproxy.tools.main import mitmproxy
mitmproxy()
第二种方式更为方便:
def main():
async def _main():
# options = main.options.Options(listen_host='0.0.0.0', listen_port=1080, mode=['socks5'])
options = main.options.Options(listen_host='0.0.0.0', listen_port=1080, mode=['upstream:https://localhost:8080'], ssl_insecure=True)
m = DumpMaster(options=options)
m.server = Proxyserver()
m.addons.add(Addon())
# m.addons.add(FlowRecorderAddon())
# m.addons.add(JSONDumper())
# m.addons.add(DBDumper())
await m.run()
asyncio.run(_main())
大家看到,这里我们很容易添加自己的addon,这样就非常方便做各种调试了。
有了上述准备,我们可以找一个网站的POST请求,修改一下post数据和cookie,这样就能做些自己的事情了,千万别拿这招去干坏事啊。
此案例中,我们采用两个mitmproxy级联的方式,其实一个也够用,采用两个的好处是,一个接收请求和处理请求,另一个方便观察我们发送的数据是否符合预期,当然你如果用浏览器的devtool也完全可以做到。

其中一个运行在我们的应用中,采用upstream模式,另一个就是普通的regular模式。浏览器的请求先发送到upstream proxy,然后再转发到第二个mitmproxy,最后出去到外网。
首先,在浏览器里装上SwitchyOmega,这个是搞web开发人必备的插件。

然后,在命令行启动第二个mitproxy:
mitmweb -p 8080 --ssl-insecure
最后,在IDE里启动upstream proxy,代码见前面。在浏览器里访问网站页面后,我们在mitmweb界面里就能见到请求了:

你可以在addon里查看POST的json数据,和各种cookie,如何修改它们?这里举几个常用的例子:
class AddHeader:
def __init__(self):
self.num = 0
def response(self, flow):
self.num = self.num + 1
flow.response.headers["count"] = str(self.num)
addons = [AddHeader()]
"""Modify HTTP query parameters."""
from mitmproxy import http
def request(flow: http.HTTPFlow) -> None:
flow.request.query["mitmproxy"] = "rocks"
"""Redirect HTTP requests to another server."""
from mitmproxy import http
def request(flow: http.HTTPFlow) -> None:
# pretty_host takes the "Host" header of the request into account,
# which is useful in transparent mode where we usually only have the IP
# otherwise.
if flow.request.pretty_host == "example.org":
flow.request.host = "mitmproxy.org"
"""Modify an HTTP form submission."""
from mitmproxy import http
def request(flow: http.HTTPFlow) -> None:
if flow.request.urlencoded_form:
# If there's already a form, one can just add items to the dict:
flow.request.urlencoded_form["mitmproxy"] = "rocks"
else:
# One can also just pass new form data.
# This sets the proper content type and overrides the body.
flow.request.urlencoded_form = [("foo", "bar")]
def request(self, flow):
all_cookies = parse_cookies(flow.request.cookies) # 将string转成dict
all_cookies.append({"name":"key1", "value":"value1"}) ## 添加一个cookie
flow.request.headers["cookie"] = stringify_cookies(all_cookies) # dict转回string
有时我们要在请求流中加入自己的请求,可以借助clientplayback这个addon。
from copy import copy
from mitmproxy import http
from mitmproxy import ctx
from mitmproxy.addons import clientplayback
def request(flow: http.HTTPFlow):
ctx.log.info("Inside request")
if hasattr(flow.request, 'is_custom'):
return
headers = copy(flow.request.headers)
headers.update({"Authorization": "" , "Requested-URI": flow.request.pretty_url})
req = http.HTTPRequest(
first_line_format="origin_form",
scheme='http',
port=8000,
path="/",
http_version=flow.request.http_version,
content=flow.request.content,
host="localhost",
headers=headers,
method=flow.request.method
)
req.is_custom = True
playback = ctx.master.addons.get('clientplayback')
f = flow.copy()
f.request = req
playback.start_replay([f])
mitmproxy提供修改request/response的标准方式为addons,下面写了两个addon,然后运行mitmweb加载。
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
如有其它需求,也可以联系我。
更广泛一点的抓包/web debugger软件有: