一、mitmproxy介绍
mitmproxy 就是用于 MITM 的 proxy,MITM 即中间人攻击(Man-in-the-middle attack)。用于中间人攻击的代理首先会向正常的代理一样转发请求,保障服务端与客户端的通信,其次,会适时的查、记录其截获的数据,或篡改数据,引发服务端或客户端特定的行为。
mitmproxy可以拦截http和https请求,如果需要拦截https请求需要安装证书
二、mitmproxy安装(windows下)
1. 运行下面命令进行安装
pip install mitmproxy
安装成功后在Python\Scripts的目录下生成下面三个程序
2. 运行mitmdump命令
运行了上述命令后,在个人电脑用户目录下会生产一个.mitmproxy文件夹
windows配置证书,双击mimproxy-ca.p12,然后一路点击下一步即可。
3.设置浏览器代理
直接在浏览器设置代理,或者使用谷歌插件SwitchyOmega_Chromium.crx设置代理
4. 启动mitmproxy
mitmweb.exe和mitmproxy.exe是可视化的代理程序
mitmproxy.exe打开会在控制台显示所有代理请求,但是windows下不可用,
mitmweb.exe 会打开默认的浏览器并显示所有代理请求
mitmdump是mitmprxoy的命令行接口,同时还可以对接Python对请求进行处理,有了它我们可以不用手动截获和分析HTTP请求和响应,值需要写好请求和响应的处理逻辑即可
-p 端口号 可指定代理的端口号
三、mitmdump
1. 把截获的数据保存到文件中,使用-w参数即可
mitmdump -w 文件名
2. 指定脚本scripts.py来处理截获的数据,使用-s参数即可:
mitmdump -s xxx.py
3. 输出日志
ctx.log.info(str("这是一条info日志"))
四、代码示例
1. 拦截请求
from mitmproxy import http, ctx
from multiprocessing import Lock
class Filter:
def __init__(self, filter_info):
self.log_info = ""
self.mutex = Lock()
self.filter_info = filter_info
self.response_file = None
self.switch_on = False
self.log_file = "log.txt"
def log(self, info) -> None:
self.log_info += f"{info}\n\n"
def write_log(self, mode="w+") -> None:
self.mutex.acquire()
with open(self.log_file, mode) as f:
f.write(self.log_info)
self.mutex.release()
def is_target_flow(self, flow: http.HTTPFlow) -> bool:
for info in self.filter_info:
if info["str_in_url"] in flow.request.url:
self.log_file = info["log_file"]
self.switch_on = info["switch_on"]
if info["response_file"] != None:
self.response_file = info["response_file"]
return True
else:
return False
def modify_response(self, flow: http.HTTPFlow) -> http.HTTPFlow:
if self.switch_on and self.response_file:
with open(self.response_file, "r", encoding="utf-8") as f:
flow.response.content = f.read().encode()
return flow
def request(self, flow: http.HTTPFlow) -> None:
if self.is_target_flow(flow):
self.log_info = ""
self.log(f"——METHOD——\n{flow.request.method}")
self.log(f"——HOST——\n{flow.request.pretty_host}")
self.log(f"——URL——\n{flow.request.pretty_url}")
query = [i + ":" + flow.request.query[i] + "\n" for i in flow.request.query]
self.log(f"——QUERY STRING——\n{''.join(query)}")
if flow.request.urlencoded_form:
form = [i + ":" + flow.request.urlencoded_form[i] + "\n" for i in flow.request.urlencoded_form]
self.log(f"——FORM——\n{''.join(form)}")
self.write_log()
def response(self, flow: http.HTTPFlow) -> None:
if self.is_target_flow(flow):
self.log_info = ""
self.log(f"——RESPONSE before modified——\n{flow.response.content.decode(encoding='UTF-8')}")
flow = self.modify_response(flow)
self.log(f"——RESPONSE after modified——\n{flow.response.content.decode(encoding='UTF-8')}")
self.write_log(mode="a")
filter_info = [
{
"str_in_url": "getSimpleNews",
"log_file": "getSimpleNews_log.txt",
"switch_on": True,
"response_file": "getSimpleNews_response.txt",
},
{
"str_in_url": "getQQNewsComment",
"log_file": "getQQNewsComment_log.txt",
"switch_on": True,
"response_file": None,
}
]
addons = [
Filter(filter_info)
]
运行mitmproxy指定使用该脚本和端口号即可:
mitmdump -p 8888 -s xxx.py
在mitmdump运行时:
-
会拦截url中包含str_in_url字符串的请求
-
会把response.content修改为当前mitm运行所在目录下的response_file文件中的内容
-
打印信息在当前mitm运行所在目录下的log_file文件中
-
如果无需修改response设置switch_on为False即为开关关闭
-
如果不修改response的话response_file需要写None
2. 脚本2
实现如下需求:
因为百度搜索是不靠谱的,所有当客户端发起百度搜索时,记录下用户的搜索词,再修改请求,将搜索词改为“360 搜索”;
因为 360 搜索还是不靠谱的,所有当客户端访问 360 搜索时,将页面中所有“搜索”字样改为“请使用谷歌”。
因为谷歌是个不存在的网站,所有就不要浪费时间去尝试连接服务端了,所有当发现客户端试图访问谷歌时,直接断开连接。
# 1. 需要篡改客户端请求,所以实现一个 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搜索"])
# 2.需要篡改服务端响应,所以实现一个 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)
# 3.需求需要拒绝客户端请求,所以实现一个 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(),
]
将三个文件放在相同的文件夹,在该文件夹内启动命令行,运行:
mitmdump -p 8888 -s addons.py
关闭所有 Chrome 窗口,从命令行中启动 Chrome 并指定代理且忽略证书错误。
五、常用接口
#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()#修改返回内容,不需要转码