暗号化の裏側を暴く:JA3フィンガープリンティングでC2通信を狩り出す技術
ネットワークエンジニアとして現場に立っていると、「HTTPSで通信しているから安全だ」という言葉がいかに脆い幻想か、痛感させられる瞬間がある。現代の攻撃者は、TLSという強力な暗号化の盾を悪用し、C2(Command & Control)サーバーへの指示出しを「正規のHTTPSトラフィック」の中に巧妙に隠蔽する。
ファイアウォールでドメインを遮断しても、IPをブロックしても、彼らは次々と新しいインフラへ逃げ込む。この「いたちごっこ」に終止符を打つため、我々守備側が頼るべきは「通信の中身」ではなく、「通信の作法(フィンガープリント)」だ。
今回は、TLSハンドシェイクの機微を突く「JA3/JA3S」という技術に焦点を当て、実務の現場でどう活用すべきかを紐解いていこう。
—
なぜ「暗号化の中身」を見ずに識別できるのか?
TLSハンドシェイクの冒頭、クライアントはサーバーに対して「私はこんな暗号スイートに対応していて、こんな拡張機能が使えますよ」という意思表示を行う。これが ClientHello メッセージだ。
面白いことに、OS標準のブラウザと、攻撃者が自作したマルウェアの通信ライブラリでは、この ClientHello の組み立て方が微妙に異なる。
- サポートしている暗号スイートの順序
- 使用するTLSバージョンの範囲
- 拡張フィールド(
Server Name IndicationやSupported Groupsなど)の構成
これらを特定の文字列として連結し、MD5ハッシュ化したものが JA3 だ。たとえ通信内容が強力に暗号化されていようと、この「握手の癖」までは隠せない。これこそが、C2通信をプロファイリングする強力な武器となる。
—
実際に指紋を採取してみる:Pythonでの実装例
理屈はわかった。では、どうやってこの「指紋」を採取するのか。難しく考える必要はない。scapy を使ってパケットをキャプチャし、ハンドシェイクのフィールドを抽出するスクリプトを書いてみよう。
from scapy.all import *
from scapy.layers.tls.all import *
# 現場で使うなら、特定のインターフェースを監視するスニファを作る
def extract_ja3(pkt):
if pkt.haslayer(TLSClientHello):
# 必要なフィールドを抽出
version = pkt[TLSClientHello].version
ciphers = [c.val for c in pkt[TLSClientHello].ciphers]
extensions = [e.type for e in pkt[TLSClientHello].ext]
# JA3の仕様に従いカンマ区切りで連結
# フォーマット: Version,Ciphers,Extensions,Groups,EcPointFormats
ja3_string = f"{version},{','.join(map(str, ciphers))},{','.join(map(str, extensions))}"
# 最後にMD5ハッシュ化
import hashlib
ja3_hash = hashlib.md5(ja3_string.encode()).hexdigest()
print(f"Detected JA3 Hash: {ja3_hash}")
# ens33インターフェースでパケットをキャプチャ開始
sniff(iface="ens33", filter="tcp port 443", prn=extract_ja3)
このコードを動かせば、ブラウザの通信と、curl や Pythonの requests が生成する通信で、全く異なるハッシュ値が出ることがわかるはずだ。マルウェアが WinHTTP を使っているのか、あるいは軽量な mbedTLS で構築されているのか。その「匂い」が、このハッシュ値に凝縮されている。
—
実務で直面する「JA3」の死角と限界
JA3は強力だが、銀の弾丸ではない。エンジニアとして、以下の限界を理解しておく必要がある。
1. ハッシュの衝突: 異なるライブラリでも、たまたま同じ設定値を持っていれば同じJA3値になることがある。
2. アップデートによる変動: ライブラリのバージョンが上がると、対応する暗号スイートが変わる可能性がある。つまり、一度登録した「悪意のあるハッシュ」が永遠に使えるわけではない。
3. JA3Sとの組み合わせ: クライアント側のJA3だけでなく、サーバー側からのレスポンスの癖を捉える JA3S とセットで判断するのが定石だ。通信は「対話」である。クライアントとサーバー、両方の指紋が一致したとき、その信頼性は飛躍的に高まる。
—
運用への落とし込み:IDS/IPSとログ分析
現場のインフラ運用においては、個別のスクリプトを回すだけでなく、既存のセキュリティスタックにこの知見を組み込むことが重要だ。
Zeek (旧Bro) のようなネットワークIDSを使っているなら、標準でJA3がログ出力される。
# Zeekのログファイル(conn.logやssl.log)から確認できるフィールド例
# JA3ハッシュを抽出して、既知のマルウェアDBと照合する
event ssl_client_hello(c: connection, version: count, record_version: count, possible_ts: time, client_hello: table[count] of count, cipher_suites: vector of count, compression_methods: vector of count, extensions: table[count] of string)
{
# ここで抽出した情報をインテリジェンス・フィードと突き合わせる
# 怪しいハッシュならアラートを飛ばす運用を構築する
}
最後に:ネットワークの「解像度」を上げろ
ネットワークエンジニアの仕事は、単に「パケットを通すこと」ではない。パケットが何を語っているか、その行間を読むことだ。
JA3フィンガープリンティングは、暗号化という「不可視の壁」に対して、我々が持ちうる数少ない「可視化のレンズ」の一つだ。まずは手元のサーバーやコンテナから出る通信をハッシュ化し、普段のトラフィックがどんな「指紋」を描いているのかを確認することから始めてほしい。
異常を検知する力は、正常を誰よりも深く知っている者にこそ宿る。あなたのネットワークの守りを、一歩先へ進めていこう。
コメント