CASBの落とし穴を回避せよ:証明書ピンニングとSSLインスペクションの「泥沼」を乗り切る技術
現場のエンジニア諸君、今日もパケットの海に溺れていないだろうか?
昨今のSASE(Secure Access Service Edge)導入ブームで、CASB(Cloud Access Security Broker)の「インラインプロキシモード」を導入する企業が増えている。通信をすべて復号化して中身を覗き、脅威をブロックする……理屈は完璧だ。しかし、この「全通信復号化」という魔法の杖は、時としてモバイルアプリ開発チームからの悲鳴を引き起こす。
特に厄介なのが、「証明書ピンニング(Certificate Pinning)」が実装された独自クライアントだ。今日は、この「CASBとピンニング」という、現場で最も頭を抱えるトラブルの原因と、その現実的な解決策を叩き込んでいく。
—
なぜ「ピンニング」がCASBと相容れないのか
まず、基本のおさらいだ。SSLインスペクション(復号化)は、CASBがクライアントになりすましてサーバーと通信し、その過程でサーバー証明書を「CASB自身が発行した偽の証明書」に差し替えることで成立する。
しかし、堅牢なアプリは「信頼できるサーバーの公開鍵(または証明書)のフィンガープリント」をアプリ内にハードコードしている。これを「証明書ピンニング」と呼ぶ。
パケットの視点で見る「断絶」の瞬間
1. Client (App): サーバーへHelloを投げる。
2. CASB (Proxy): 「おっ、通信来たな。俺が中身を見てやる」と、自前のCA証明書で署名したサーバー証明書を提示する。
3. Client (App): 「あれ? サーバーの公開鍵が、俺が知ってるのと違うぞ。これ、中間者攻撃(MITM)じゃねえか!」
4. Client (App): 安全のために即座にTCPコネクションを RST で切断。
結果、アプリ側には「SSLハンドシェイクエラー」が返り、開発者は「ネットワークが繋がらない!」と泣きついてくる。通信ログを見ても、CASB側には Decrypt Error や Handshake Failure が並ぶだけだ。この「泥沼」から脱出するには、論理的な例外処理が必要になる。
—
回避策:CASBにおける「バイパス」の設計
CASBの管理コンソールには、通常「SSLインスペクションの除外リスト(Bypass List)」が存在する。ここをどう設定するかが腕の見せ所だ。
1. ドメインベースの除外
最も一般的だが、注意が必要だ。api.example.com のようにFQDN単位で除外する。しかし、アプリがCDNを使っている場合、ドメインが頻繁に変わる可能性がある。その場合はワイルドカード (*.example.com) を使うことになるが、セキュリティ強度が下がるリスクを忘れてはいけない。
2. 送信元IP/クライアント証明書ベースの除外
特定のクライアント(社内モバイル端末や社内サーバー)からの通信であれば、その送信元IPをバイパスリストに追加するのが最も確実だ。
—
現場で役立つ検証コマンドとTips
トラブルシューティングの際、ブラウザ越しに curl を打っても「なぜか繋がる」という経験はないだろうか。それはブラウザやOSの証明書ストアが、CASBのルートCAを信頼してしまっているからだ。
真の挙動を知るには、証明書のチェーンを直接確認する必要がある。
# CASBが介入している場合、サーバー証明書のIssuer(発行者)がCASBの名称になっているはずだ
# 以下は、OpenSSLを用いて証明書チェーンを詳細に出力するコマンド
openssl s_client -showcerts -connect api.example.com:443 </dev/null 2>/dev/null | openssl x509 -noout -text | grep -A 2 "Issuer:"
Pythonでのテストコード例(ピンニング模擬)
アプリチームが実装しているピンニングが正しく機能しているか、または「今の環境でどう見えるか」を確認するための簡易スクリプトだ。
import ssl
import socket
# ピンニング検証用の簡単なスクリプト
hostname = 'api.example.com'
context = ssl.create_default_context()
try:
with socket.create_connection((hostname, 443)) as sock:
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
# サーバー証明書のフィンガープリントを取得
cert = ssock.getpeercert(binary_form=True)
print("SSLハンドシェイク成功。証明書を取得しました。")
except ssl.SSLCertVerificationError as e:
print(f"エラー発生: ピンニングあるいは証明書不一致の可能性大 -> {e}")
except Exception as e:
print(f"接続失敗: {e}")
—
インフラエンジニアへの提言:泥臭い解決策
最後に、実務における「ベストプラクティス」を授けよう。
1. 「通信ログの可視化」を信じすぎない: CASBの管理画面で「許可」になっているからといって、アプリ層のピンニングが通るとは限らない。バイパス設定後は、必ずアプリ側でパケットキャプチャを行い、TLS Client Hello がそのままサーバーへ届いているか(あるいは復号化せずにトンネリングされているか)を確認しろ。
2. 証明書の更新プロセスを把握せよ: サーバー証明書の更新時にピンニングの設定を忘れると、サービス全体が死ぬ。インフラ側でピンニングの除外設定を入れたなら、サーバー側の証明書更新サイクルと連携する「運用フロー」をドキュメント化しておくことが、真のプロの仕事だ。
3. 独自クライアントなら「クライアント証明書認証」への移行を促せ: SSLインスペクションによるMITMを回避したいなら、そもそも通信経路を復号化なしで保護できる mTLS(相互TLS認証)の実装を開発チームに提案せよ。これが根本解決だ。
ゼロトラストの時代であっても、ネットワークの基礎は「パケットの気持ちになること」だ。CASBがどこで何をしているのか、その挙動をパケットレベルで把握し、時には「セキュリティの例外」を勇気を持って設定する。それができる者だけが、複雑なエンタープライズ環境を統御できる。
現場からは以上だ。また次のトラブルで会おう。
コメント