一种稍微不那么笨拙的方式,通过Burp等工具拦截和修改非HTTP协议,并支持SSL和TLS拦截。 本工具面向研究人员和应用程序渗透测试人员,用于执行厚客户端安全评估。
这是出色的 mitm_relay 项目的改进版本。
作为 CyberArk Labs 研究部门工作的一部分,我们需要一种方法来检查 TCP 上的 SSL 和 TLS 通信,并能够实时修改数据包内容。有很多方法可以做到这一点(例如,知名的 Burp Suite 扩展 NoPE),但在某些情况下,它们都对我们不起作用。最后我们偶然发现了 mitm_relay。
mitm_relay 是一种快速简便的方法,可以通过现有的 HTTP 拦截软件(如 Burp Suite 的代理)对任何基于 TCP 的协议执行 MITM。它对于厚客户端安全评估特别有用。但它并没有完全满足我们的需求,因此我们需要对其进行定制。经过大量定制后,每次新的更改都需要大量工作,我们最终以更加模块化的方式重写了所有内容。
我们希望其他人会发现这个脚本有用,并且希望添加功能会很容易。
首先,需要配置监听器的地址和端口。每个监听器还需要配置一个目标(地址和端口)。从监听器接收到的每个数据将被包装到一个 HTTP POST 请求的正文中,其 URL 包含“CLIENT_REQUEST”。从目标接收到的每个数据将被包装到一个 HTTP POST 请求的正文中,其 URL 包含“SERVER_RESPONSE”。这些请求被发送到本地的 HTTP 拦截服务器。
可以选择配置一个 HTTP 代理,并使用像 burp suite 这样的工具作为 HTTP 拦截工具,并在那里查看消息。这样,就可以通过 Burp 的“Match and Replace”、扩展甚至手动方式轻松修改消息(注意:被拦截协议的超时机制可能非常短)。
另一种修改消息的方法是使用一个 Python 脚本,HTTP 拦截服务器在接收到消息时会运行该脚本。
发送到 HTTP 拦截服务器的消息正文将打印到 shell 中。如果提供了修改脚本,则消息将在更改后打印。在所有修改之后,拦截服务器也会将消息作为 HTTP 响应正文回显。
要解密 SSL/TLS 通信,mitm_intercept 需要提供一个客户端在与监听器握手时能接受的证书和密钥。如果目标服务器需要特定的证书进行握手,可以选择提供证书和密钥。
一个小图展示典型的流量流程:

mitm_intercept 兼容更高版本的 Python 3(Python 3.9),同时也兼容 Windows(例如,Windows 上没有 socket.MSG_DONTWAIT)。我们保留了使用“STARTTLS”的选项,并称之为“混合”模式。SSL 密钥日志文件的使用已更新(内置选项自 Python 3.8 起可用),我们还添加了更改 sni 头部 的选项。现在,管理传入和传出通信由 socketserver 完成,所有数据都发送到 ThreadingHTTPServer 的子类,该子类处理数据的表示和修改。这样,就可以在响应中看到修改脚本应用的更改(方便使用 Burp)。此外,我们现在还可以使用 OpenSSL 密码列表格式 更改脚本使用的可用密码。
$ python -m pip install requestsusage: mitm_intercept.py [-h] [-m] -l [u|t:]<interface>:<port> [[u|t:]<interface>:<port> ...] -t
[u|t:]<addr>:<port> [[u|t:]<addr>:<port> ...] [-lc <cert_path>]
[-lk <key_path>] [-tc <cert_path>] [-tk <key_path>] [-w <interface>:<port>]
[-p <addr>:<port>] [-s <script_path>] [--sni <server_name>]
[-tv <defualt|tls12|tls11|ssl3|tls1|ssl2>] [-ci <ciphers>]
mitm_intercept 版本 1.6
选项:
-h, --help 显示此帮助信息并退出
-m, --mix-connection 执行 TCP 中继而不进行 SSL 握手。如果中继的任何一侧开始 SSL 握手,则用 SSL 包裹连接并拦截通信。必须提供监听器证书和私钥。
-l [u|t:]<interface>:<port> [[u|t:]<interface>:<port> ...], --listen [u|t:]<interface>:<port> [[u|t:]<interface>:<port> ...]
创建 SSLInterceptServer 监听器,监听指定接口和端口。可以通过参数间的空格创建多个监听器。在地址前添加 "u:" 将使监听器侦听 UDP 协议。默认是 TCP 协议,但添加 "t:" 以保持整洁也是可以的。监听器数量必须与目标数量匹配。第 i 个监听器将中继到第 i 个目标。
-t [u|t:]<addr>:<port> [[u|t:]<addr>:<port> ...], --target [u|t:]<addr>:<port> [[u|t:]<addr>:<port> ...]
将每个 SSLInterceptServer 监听器定向到转发通信的目标地址和端口。可以通过参数间的空格创建多个目标。在地址前添加 "u:" 将使目标使用 UDP 协议通信。默认是 TCP 协议,但添加 "t:" 以保持整洁也是可以的。监听器数量必须与目标数量匹配。第 i 个监听器将中继到第 i 个目标。
-lc <cert_path>, --listener-cert <cert_path>
当客户端联系监听器时,监听器使用的证书。如果客户端接受,可以是自签名证书。
-lk <key_path>, --listener-key <key_path>
监听器证书的私钥路径。
-tc <cert_path>, --target-cert <cert_path>
用于与目标建立连接的证书。如果目标接受,可以是自签名证书。如果目标不需要特定证书,则不是必需的。
-tk <key_path>, --target-key <key_path>
目标证书的私钥路径。
-w <interface>:<port>, --webserver <interface>:<port>
指定 InterceptionServer Web 服务器监听的接口和端口。如果省略,默认为 127.0.0.1:49999
-p <addr>:<port>, --proxy <addr>:<port>
指定 InterceptionServer Web 服务器和 SSLInterceptServer 之间的代理地址和端口。可以配置以使通信通过本地代理(如 Burp)。如果省略,通信将仅打印到 shell 中。
-s <script_path>, --script <script_path>
InterceptionServer Web 服务器执行的脚本路径。必须包含函数 handle_request(message),该函数将在消息发送到目标之前运行,以及 handle_response(message),该函数在从目标接收消息后运行。如果不需要,可以省略。
--sni <server_name> 如果需要更改与目标 SSL 握手时的服务器名称。如果省略,则使用与监听器握手时的服务器名称。
-tv <defualt|tls12|tls11|ssl3|tls1|ssl2>, --tls-version <defualt|tls12|tls11|ssl3|tls1|ssl2>
如果需要,可以指定特定的 TLS 版本。
-ci <ciphers>, --ciphers <ciphers>
为 TLS 握手设置不同于 Python 默认值的密码。应为 OpenSSL 密码列表格式的字符串
(https://www.openssl.org/docs/manmaster/man1/ciphers.html)。
要将 SSL(预)主密钥转储到文件,请设置环境变量 SSLKEYLOGFILE 为文件路径。对 Wireshark 有用。
通信需要定向到监听器以拦截任意协议。具体方式取决于客户端的工作方式。有时客户端使用 DNS 地址,更改 hosts 文件就足以解析监听器地址。如果地址是硬编码的,则需要更具创意的方法(通常包括修改路由表、修补客户端,或使用虚拟机和 iptables)。
HTTP 拦截服务器可以使用 -s 标志给定的脚本运行。该脚本在接收到 HTTP 请求时运行。HTTP 拦截服务器的响应是运行脚本后收到的请求。
当配置了代理(如 Burp)时,请求的修改将在脚本运行之前发生,响应的修改将在脚本运行之后发生。代理或修改脚本对请求和响应的更改将在消息发送到目的地之前修改原始消息。
脚本必须包含函数 handle_request(message) 和 handle_response(message)。当消息从客户端到服务器时,HTTP 拦截服务器将调用 handle_request(message);当消息从服务器到客户端时,调用 handle_response(message)。
一个在消息末尾添加空字节的脚本示例:
def handle_request(message):
return message + b"\x00"
def handle_response(message):
# 两个函数都必须返回一条消息。
return message
该工具需要服务器证书和私钥用于 SSL 拦截。有关生成自签名证书或 Burp 证书的信息,请参见此处。
如果服务器需要特定证书,可以向工具提供证书和密钥。
下面的演示展示了如何拦截与 MSSQL 的连接(此演示在 DVTA 上执行):
与 MSSQL 的连接是通过 TCP 之上的 TDS 协议 建立的。身份验证本身是通过 TDS 协议之上的 TLS 执行的。要查看该 TLS 过程的拦截,我们需要两个补丁式的修改脚本。
demo_script.py:
from time import time
from struct import pack
from pathlib import Path
def handle_request(message):
if message.startswith(b"\x17\x03"):
return message
with open("msg_req" + str(time()), "wb") as f:
f.write(message[:8])
return message[8:]
def handle_response(message):
if message.startswith(b"\x17\x03"):
return message
path = Path(".")
try:
msg_res = min(i for i in path.iterdir() if i.name.startswith("msg_res"))
data = msg_res.read_bytes()
msg_res.unlink()
except ValueError:
data = b'\x12\x01\x00\x00\x00\x00\x01\x00'
return data[:2] + pack(">h", len(message)+8) + data[4:] + message
demo_script2.py:
from time import time
from struct import pack
from pathlib import Path
def handle_request(message):
if message.startswith(b"\x17\x03"):
return message
path = Path(".")
try:
msg_req = min(i for i in path.iterdir() if i.name.startswith("msg_req"))
data = msg_req.read_bytes()
msg_req.unlink()
except ValueError:
data = b'\x12\x01\x00\x00\x00\x00\x01\x00'
return data[:2] + pack(">h", len(message)+8) + data[4:] + message
def handle_response(message):
if message.startswith(b"\x17\x03"):
return message
with open("msg_res" + str(time()), "wb") as f:
f.write(message[:8])
return message[8:]
使用这些补丁式脚本,我们将看到部分 TLS 通信,但随后客户端将失败(因为通过这些拼凑的脚本,我们严重篡改了 TDS 通信,除了 TLS 部分)。
版权所有 (c) 2022 CyberArk Software Ltd. 保留所有权利
本仓库基于 Apache-2.0 许可证 - 详情请参阅 LICENSE