暗号の壁の裏側を暴く:JA3/JA3sフィンガープリンティングでC2通信を炙り出す技術
おい、最近のSOC(Security Operations Center)のチャットログを見たか?「また見慣れないIPへのHTTPS通信だ。中身は当然暗号化されていて分からない。SSL/TLS復号(インスペクション)を有効にするか?」なんて慌てているジュニアエンジニアの姿が目に浮かぶ。
気持ちは痛いほど分かる。現代のウェブはほぼ100%がTLS(Transport Layer Security)で覆われ、プライバシー保護の観点からも、すべての通信をファイアウォールの手前でスッポンポンに復号して覗き見るなんてアプローチは、パフォーマンス的にもプライバシー法的にも現実的じゃない。
だが、ここで諦めるようなシニアエンジニアじゃないはずだ。
「中身が見えなくても、『身なり』や『振る舞いの癖』を見れば、誰が通信しているのかは一発で分かる」——今回は、暗号化されたパケットの海からマルウェアのCommand and Control(C2)通信を特定する、JA3およびJA3sフィンガープリンティングの核心に迫ろう。
—
1. なぜ「中身が見えなくても」マルウェアが特定できるのか?
私たちインフラエンジニアやWeb API設計者が日常的に扱うHTTPS。ブラウザや各種言語のHTTPクライアントは、サーバーと安全な通信を確立するために、最初の一歩として必ずTLSハンドシェークを行う。
ここで思い出してほしい。ハンドシェークの最初のステップである Client Hello メッセージには、クライアントがサポートする暗号化方式(暗号スイート)や拡張機能(Extension)のリストが、特定の順序で並べられて送信される。
ここに大きな落とし穴(そして我々のチャンス)がある。
正規のブラウザ(ChromeやFirefox)や、一般的なライブラリ(Pythonの requests や curl)は、それぞれ独自のTLSスタックやライブラリ(OpenSSL, BoringSSLなど)を使っており、その「提示するアルゴリズムの種類とその並び順」が微妙に異なる。
ましてや、攻撃者が独自に組み上げた、あるいは特定のフレームワーク( Cobalt Strike など)で生成されたマルウェアのC2クライアントはどうだろう? 標準的ではない独自の暗号スイートの組み合わせや、特定のバージョン特有の拡張機能の並び順を持っていることが多い。
JA3(Client用) および JA3s(Server用) は、この Client Hello(または Server Hello)に含まれる特定のフィールドを抽出し、MD5ハッシュ化することで「通信の指紋(フィンガープリン))」に変換する画期的な手法だ。通信の中身を1バイトも復号することなく、「お前、本当にブラウザか? それともあのマルウェアの通信じゃないのか?」と見破ることができる。
—
2. JA3フィンガープリンティングの内部構造と計算ロジック
では、具体的に Client Hello のどこをどう見ているのか。Salesforceのセキュリティチームが提唱したオリジナルのJA3仕様では、以下の5つのフィールドを抽出し、カンマ区切りで結合した文字列を作る。
1. SSLVersion (TLSのバージョン)
2. Cipher(s) (サポートする暗号スイートのリスト)
3. Extension(s) (TLS拡張機能のリスト)
4. EllipticCurve (楕円曲線のリスト)
5. EllipticCurvePointFormat (楕円曲線ポイントフォーマットのリスト)
例えば、あるクライアントが送信した Client Hello から抽出されたパラメータが以下のような文字列になったとする。
771,4865-4866-4867-49195-49199,0-23-65281-10-11-35-16-5-13-18-51-43-13-45,29-23-24,0
これをそのままパケット解析器やSIEM(SplunkやElasticsearchなど)に投げると重いので、最後にMD5アルゴリズムでハッシュ化する。すると、たった32文字の文字列(例:e7d705a3286e19ea42f587b355ef68b2)に変換される。これがJA3フィンガープリンティングの正体だ。
—
3. シーケンスと通信フローの裏側
実際のネットワーク上で、このフィンガープリンティングがどのように機能するのか、パケットの往復をイメージしてみよう。
[C2クライアント (マルウェア)] [リバースプロキシ / WAF / IDS] [C2サーバー]
| | |
| ----- (1) TLS Client Hello ----------------->| |
| (暗号スイート、拡張機能などのリスト) | |
| |-- [ここでJA3ハッシュを算出] |
| |-- [既知のC2シグネチャと比較] |
| | |
| | ----- (2) TLS Client Hello ----->|
| | |
| | <---- (3) TLS Server Hello ----- |
| | (JA3sハッシュ算出も可能) |
| <---- (4) TLS Server Hello ----------------- | |
| | |
| ----- (5) 暗号化されたC2通信 (トンネル確立) -->|=================================>|
ポイントは、インラインでパケットを検査するセキュリティアプライアンスや、SNI(Server Name Indication)やTLSハンドシェークをログに記録する次世代ファイアウォール(NGFW)が、手順(1)の段階で通信の正体を暴いている点だ。サーバーとの間で鍵交換が完了する前(ハンドシェークの最中)に判定を下せるため、悪意ある通信を即座にドロップまたはアラート化できる。
—
4. 実務で使える!PythonとcurlにおけるJA3の挙動差分
百聞は一見に如かず。実際に異なるクライアントからHTTPSリクエストを飛ばしたとき、いかにTLSの「身なり」が違うかをコードを通じて体感してもらいたい。
以下のPythonスクリプトは、標準の requests ライブラリ(内部でurllib3とOpenSSLを使用)を使ってリクエストを送信する例だ。
import requests
# テスト用のエコーサーバーや自前のキャプチャ環境のエンドポイント
TARGET_URL = "https://internal-api.example.com/healthcheck"
try:
# 標準的なリクエスト送信
# 内部のOpenSSLバージョンやデフォルトの暗号スイート順序に依存してJA3が決まる
response = requests.get(TARGET_URL, timeout=5)
print(f"ステータスコード: {response.status_code}")
print(f"レスポンスヘッダー: {response.headers.get('Server', 'Unknown')}")
except requests.exceptions.RequestException as e:
print(f"通信エラーが発生しました: {e}")
次に、インフラのデバッグや簡易的なAPIテストでよく使う curl コマンドを見てみよう。実は、同じLinux環境であっても、システムにリンクされているSSL/TLSライブラリ(OpenSSLなのか、GnuTLSなのか、あるいはAppleのSecure Transportなのか)によって、生成されるJA3ハッシュは全く異なるものになる。
# デバッグ用にTLSハンドシェークの詳細(暗号スイートや拡張機能)を表示させるcurlコマンド
# -v オプションで詳細なTLSセッション情報を確認できる
curl -Iv https://internal-api.example.com/healthcheck
もし自社のWeb APIやサーバーサイドアプリケーションの前段に、JA3ベースのWAFや検知システムを導入する場合、「正当なクライアント(公式スマホアプリや特定のSDK)がどのようなJA3ハッシュを生成するか」のホワイトリストを正確にプロファイリングする作業が絶対に必要になる。マルウェア対策だけでなく、APIスクレイピング対策としてもこの手法は非常に強力だ。
—
5. 現場のインフラエンジニアが直面する罠と運用上のTips
さて、綺麗事ばかり言ってもいられないのが現場のつらいところだ。実務でJA3/JA3sフィンガープリンティングを導入・運用する際、シニアとして後輩に必ず伝えている「泥臭いTips」をいくつか共有しておこう。
トラブルシューティングTips 1: ライブラリのアップデートでハッシュが変わる
「昨日まで正常に通っていた社内連携スクリプトが、今朝から突然ブロックされた!」というトラブルの原因の多くはこれだ。OSのアップデートやコンテナベースイメージ(AlpineやUbuntuなど)の更新に伴い、内包されているOpenSSLのバージョンが上がると、Client Hello の構造やデフォルトの暗号スイートが変わり、JA3ハッシュ値が変化してしまう。
- 対策: アプリケーションコンテナのベースイメージや利用するTLSライブラリのバージョンは厳格に固定(Pinning)し、CI/CDパイプラインで変更検知できるようにすること。
トラブルシューティングTips 2: 攻撃者による「JA3スプーフィング(偽装)」
攻撃者もバカではない。セキュリティ製品がJA3によるブロックを始めていると知るやいなや、オープンソースのツールや自作のTLSスタックを使い、正規のブラウザ(Chromeの最新版など)と全く同じJA3ハッシュを偽装してC2通信を行う技術(JA3Spoofなど)を使いはじめている。
- 対策: JA3だけでセキュリティの安全神話を信じ込んではいけない。JA3はあくまで「初動のフィルタリングやトリアージを高速化するスクリーニング手法」の一つと捉え、長期的なC2検知には、DNSクエリの挙動(DGAドメインの検出)や、通信の頻度・データ量(ビーコン通信の周期性分析)といった振る舞い検知(EDRやNDRの連携)と組み合わせる多層防御が不可欠だ。
—
6. おわりに
暗号化通信の裏側を暴くJA3/JA3sフィンガープリンティングは、境界防御が曖昧になったゼロトラスト時代において、ネットワークとセキュリティの境界線上でひそかに輝く強力な武器だ。
「中身が見えないから仕方ない」と諦めるのではなく、パケットが持つ「メタデータや振る舞いの癖」から真実を導き出す。これこそが、私たちネットワーク・セキュリティエンジニアの醍醐味だと言えるだろう。
さあ、今すぐ君の環境のログを見直し、自社のシステムが発している「TLSの指紋」を確認してみようじゃないか。新しい発見が、きっとそこにあるはずだ。
コメント