
Burpなどを通じて非HTTPプロトコルをインターセプトおよび変更する、ややハッキング的でない方法
Burpなどを通じて非HTTPプロトコルを傍受・改変する、ややハック的でない方法。SSL/TLS傍受に対応。 本ツールは、リッチクライアントのセキュリティ評価を行う研究者やアプリケーション侵入テスト担当者向けです。
素晴らしいmitm_relayプロジェクトの改良版です。
CyberArk Labsの研究部門での業務の一環として、TCP上のSSL/TLS通信を検査し、パケットの内容をリアルタイムで変更できる方法が必要でした。これを実現する方法は多数あります(例:Burp Suite拡張のNoPE)が、いくつかのケースではうまく機能しませんでした。最終的にmitm_relayに行き着きました。
mitm_relayは、Burp Suiteのプロキシのような既存のHTTP傍受ソフトウェアを介して、任意のTCPベースのプロトコルに対してMITMを実行するための迅速かつ簡単な方法です。特にリッチクライアントのセキュリティ評価に役立ちます。しかし、完全には私たちの用途に合わなかったため、カスタマイズが必要でした。多くのカスタマイズを重ねるうちに、新しい変更ごとに多大な労力がかかるようになり、最終的にはよりモジュール化された方法ですべてを書き直すことにしました。
このスクリプトが他の人にも役立ち、機能追加が容易になることを願っています。
まず、リスナーのアドレスとポートを設定する必要があります。各リスナーには、ターゲット(アドレスとポート)も設定する必要があります。リスナーから受信したすべてのデータは、URLに「CLIENT_REQUEST」を含むHTTP POSTリクエストの本文にラップされます。ターゲットから受信したすべてのデータは、URLに「SERVER_RESPONSE」を含むHTTP POSTリクエストの本文にラップされます。これらのリクエストはローカルのHTTP傍受サーバーに送信されます。
HTTPプロキシを設定し、Burp SuiteのようなツールをHTTP傍受ツールとして使用して、そこでメッセージを表示するオプションもあります。これにより、Burpの「Match and Replace」、拡張機能、または手動でメッセージを簡単に変更できます(ただし、傍受対象プロトコルのタイムアウトは非常に短い場合があることに注意してください)。
別の方法として、HTTP傍受サーバーがメッセージを受信したときに実行するPythonスクリプトを使用してメッセージを変更することもできます。
HTTP傍受サーバーに送信されたメッセージの本文はシェルに表示されます。変更スクリプトが指定されている場合、変更後のメッセージが表示されます。すべての変更後、傍受サーバーはHTTPレスポンスの本文としてエコーバックします。
SSL/TLS通信を復号するには、クライアントがリスナーとのハンドシェイク時に受け入れる証明書と鍵をmitm_interceptに提供する必要があります。ターゲットサーバーがハンドシェイクに特定の証明書を必要とする場合、証明書と鍵を指定するオプションがあります。
典型的なトラフィックフローを示す小さな図:

mitm_interceptは新しいバージョンのPython 3(Python 3.9)と互換性があり、Windowsとも互換性があります(例えば、Windowsにはsocket.MSG_DONTWAITが存在しません)。「STARTTLS」を使用するオプションは維持しており、「Mixed」モードと呼んでいます。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 SSLハンドシェイクなしでTCPリレーを実行します。リレー側のいずれかが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ウェブサーバーが待ち受けるインターフェースとポートを指定します。省略した場合のデフォルトは127.0.0.1:49999です。
-p <addr>:<port>, --proxy <addr>:<port>
InterceptionServerウェブサーバーとSSLInterceptServerの間のプロキシのアドレスとポートを指定します。Burpのようなローカルプロキシを経由するように設定できます。省略した場合、通信はシェルにのみ表示されます。
-s <script_path>, --script <script_path>
InterceptionServerウェブサーバーが実行するスクリプトへのパス。メッセージをターゲットに送信する前に実行される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アドレスを使用する場合、ホストファイルを変更してリスナーのアドレスを解決するだけで十分なこともあります。アドレスがハードコードされている場合は、より創造的な方法(通常、ルーティングテーブルの変更、クライアントのパッチ、またはVMと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プロセスを傍受するには、2つの場当たり的な変更スクリプトが必要です。
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通信の一部は確認できますが、その後クライアントは失敗します(これらのハック的なスクリプトでは、TLS部分を除いてTDS通信を大幅に変更してしまうため)。
Copyright (c) 2022 CyberArk Software Ltd. All rights reserved
このリポジトリはApache-2.0ライセンスの下でライセンスされています。詳細はLICENSEをご覧ください。