🎯 この章で学ぶこと

  • ネットワーク層で起きる脅威(盗聴・改ざん・なりすまし・中間者攻撃)の正体
  • セグメンテーションで侵入後の横展開を止める設計
  • TLSハンドシェイクの中身と前方秘匿性、TLS 1.2と1.3の違い
  • ファイアウォール・IDS・IPS・WAFの役割分担と、Pythonでの証明書検証

6.1 ネットワーク層の脅威

アプリケーションがどれほど堅牢でも、その間を流れるパケットが平文であれば、経路上の誰かに中身を読まれ、書き換えられます。ネットワーク層の脅威は、大きく4つに整理できます。

脅威何が起きるかCIAのどれを侵すか代表的な防御
盗聴(Sniffing)経路上でパケットを読み取られる機密性通信の暗号化(TLS)
改ざん(Tampering)通信内容を書き換えられる完全性メッセージ認証・TLS
なりすまし(Spoofing)別のホストやユーザーに化ける機密性・完全性証明書による相手認証
中間者攻撃(MITM)通信の途中に割り込み両者に成りすます機密性・完全性サーバー証明書検証・ピンニング

特に危険なのが中間者攻撃(Man-in-the-Middle、MITM)です。攻撃者はクライアントとサーバーの間に入り込み、クライアントには「自分がサーバーだ」、サーバーには「自分がクライアントだ」と振る舞います。ARPスプーフィング、不正なWi-Fiアクセスポイント、DNSの汚染など、割り込みの入口はさまざまです。暗号化していない通信、あるいは証明書検証を省いた暗号化通信は、この攻撃に対して無力です。

💡 「社内ネットワークだから安全」という思い込み

かつては境界の内側は信頼できるとされました。しかし内部からの盗聴、持ち込み端末、侵害された端末を踏み台にした攻撃を考えれば、内部通信こそ暗号化すべきです。第3章のゼロトラストは、まさに「ネットワークの位置で信頼を決めない」という発想でした。

6.2 セグメンテーション — 横展開を止める

攻撃者が最初に侵入する地点(たとえばフィッシングで乗っ取った1台の端末)は、多くの場合、本当に狙いたい資産(顧客DBや基幹サーバー)とは別の場所です。そこから内部を渡り歩いて目的地に近づく動きを横展開(lateral movement)と呼びます。セグメンテーションは、この横展開を難しくするための分割です。

VLANとサブネット分割

ネットワークを用途ごとに分けます。たとえば「業務端末」「サーバー」「来客用Wi-Fi」「開発環境」を別セグメントにし、セグメント間の通信はファイアウォールやアクセス制御リストで必要最小限だけ許可します。来客用Wi-Fiから基幹サーバーへ到達できない、という当たり前の状態を意図的に作ります。

マイクロセグメンテーション

従来のセグメント分割は「ネットワークの区画」単位でしたが、マイクロセグメンテーションはワークロード(サーバーやコンテナ)単位で通信を制御します。「Webサーバーはアプリサーバーの8080番にだけ接続できる。DBサーバーはアプリサーバーからの5432番だけ受け付ける」というように、必要な経路だけを開けます。設計の原則は最小到達性——到達できる先を必要な範囲に絞ることです。

🛡️ セキュリティの視点 — 侵入は防げなくても、被害は封じ込められる

「侵入される前提」で設計するとき、セグメンテーションは決定的な役割を果たします。1台が乗っ取られても、そこから到達できる範囲が狭ければ、被害はそのセグメントに封じ込められます。逆に、フラットで何にでも到達できるネットワークは、1台の侵害が全社の侵害に直結します。「もし目の前の端末が攻撃者に握られていたら、そこから何に触れるか?」を各セグメントで自問してください。

6.3 TLSハンドシェイクの中身

TLS(Transport Layer Security)は、盗聴・改ざん・なりすましをまとめて防ぐための仕組みです。中身をブラックボックスにせず、ハンドシェイクで何が起きているかを理解しておくと、証明書エラーやプロトコル選択の判断が的確になります。

TLS 1.2の簡略化した流れは次の通りです。

Client                                Server
  |  ClientHello (対応する暗号スイート一覧)  |
  | -----------------------------------> |
  |  ServerHello (選んだ暗号スイート)        |
  |  Certificate (サーバー証明書)           |
  |  ServerKeyExchange (ECDHE公開値)        |
  | <----------------------------------- |
  |  ClientKeyExchange (ECDHE公開値)        |
  |  Finished                            |
  | -----------------------------------> |
  |  Finished                            |
  | <----------------------------------- |
  |   === 以降はセッション鍵で暗号化 ===      |

鍵交換(ECDHE)と前方秘匿性

現代のTLSはECDHE(楕円曲線ディフィー・ヘルマン鍵共有・一時鍵)で共通鍵を作ります。ポイントは「一時(Ephemeral)」の部分です。ハンドシェイクごとに使い捨ての鍵ペアを生成するため、たとえ後日サーバーの秘密鍵が漏れても、過去に記録された通信を復号できません。この性質を前方秘匿性(PFS、Perfect Forward Secrecy)と呼びます。RSA鍵交換にはこの性質がないため、現在は非推奨です。

サーバー証明書の検証

証明書は「この公開鍵は確かにこのドメインのものだ」と、信頼された認証局(CA)が署名で保証するものです。クライアントは、(1)証明書の署名を信頼済みCAでたどれるか、(2)有効期限内か、(3)接続先ホスト名が証明書のドメインと一致するか(SAN)、を検証します。この検証こそがMITMを防ぐ要です。暗号化していても検証しなければ、攻撃者の証明書を受け入れてしまいます。

TLS 1.2と1.3の違い

項目TLS 1.2TLS 1.3
ハンドシェイクの往復2往復(2-RTT)1往復(1-RTT、再開時は0-RTTも)
鍵交換RSAも選べてしまう(EC)DHEのみ=常に前方秘匿性
暗号スイート古い危険な方式も残る脆弱な方式を廃止・簡素化
ハンドシェイクの暗号化一部平文証明書などを暗号化

実務ではTLS 1.2以上のみを許可し、TLS 1.3を優先、SSLv3やTLS 1.0/1.1は無効化するのが基本です。

6.4 ファイアウォール・IDS・IPS・WAFの役割分担

「どれを入れればいい?」ではなく「それぞれ何を見て、何を止めるか」を理解すると、多層防御として組み合わせられます。

機器・機能主に見る層判断の基準できること・限界
ファイアウォールL3/L4(IP・ポート)送信元/宛先IP・ポート・プロトコル通信の可否を制御。中身の攻撃は見ない
次世代FW(NGFW)L3〜L7アプリ識別・ユーザー・脅威情報アプリ単位の制御。設定が複雑
IDSL3〜L7既知の攻撃パターン・異常検知して警告するが止めない
IPSL3〜L7既知の攻撃パターン・異常インラインで遮断する。誤検知で正常通信を止める危険
WAFL7(HTTP)SQLi・XSS等のWeb攻撃パターンWebアプリを守る。ロジックの欠陥は防げない

IDS(検知)とIPS(防御)の違いは「止めるかどうか」です。IPSはインラインに配置して攻撃を遮断できますが、誤検知が業務停止に直結します。WAFはHTTPの中身を見てWeb特有の攻撃を弾きますが、あくまで一時しのぎであり、アプリ本体の修正(第4章)を置き換えるものではありません。

✅ WAFは「時間を稼ぐ盾」と捉える

脆弱性が見つかって修正リリースまで時間がかかるとき、WAFのルールで攻撃パターンを一時的に遮断できます。これは有効な緊急対応です。ただし恒久対策はコード修正であり、WAFに頼り切ってはいけません。攻撃者はWAFの回避手法(エンコーディング等)も研究しています。

6.5 実務の勘所

⚠️ TLSのverifyを無効化してはいけない

「証明書エラーが出て面倒だから」と検証を無効化するのは、TLSの意味を根こそぎ捨てる行為です。暗号化はされても、相手が本物かを確認しなくなるため、MITMに対して丸裸になります。エラーが出たら、正しい原因(期限切れ・自己署名・ホスト名不一致・CA未登録)を突き止めて解消してください。開発環境でも安易に無効化する癖をつけると、本番にそのまま混入します。

6.6 Pythonで証明書検証を確認する

Pythonの標準ライブラリsslを使って、TLS接続とサーバー証明書の中身を確認してみましょう。既定で検証が有効になっている点に注目してください。

import ssl
import socket

hostname = "example.com"

# 既定のコンテキストは「証明書検証あり・ホスト名チェックあり」
context = ssl.create_default_context()

with socket.create_connection((hostname, 443), timeout=5) as sock:
    with context.wrap_socket(sock, server_hostname=hostname) as tls:
        print("プロトコル:", tls.version())          # 例: TLSv1.3
        print("暗号スイート:", tls.cipher()[0])
        cert = tls.getpeercert()
        print("発行先:", dict(x[0] for x in cert["subject"]))
        print("発行者:", dict(x[0] for x in cert["issuer"]))
        print("有効期限:", cert["notAfter"])

もしホスト名が証明書と一致しなかったり、期限切れだったりすると、wrap_socketの時点でssl.SSLCertVerificationErrorが送出されます。これは「バグ」ではなく正しく守られている証拠です。次のような無効化は、緊急のデバッグ以外で書いてはいけません。

# 絶対に本番で使わないこと(MITMに対して無防備になる)
context = ssl.create_default_context()
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE   # 検証を捨てている

同じことはrequestsライブラリのverify=Falseにも当てはまります。警告が出るのは、それが危険だからです。証明書の問題は「無効化」ではなく「解決」してください。

まとめ

練習問題

問題 6-1

あるチームが「通信はHTTPSにしているので中間者攻撃は心配ない」と言っています。しかし彼らのクライアントは接続エラーを避けるため証明書検証を無効化していました。この構成にどんな問題があるか、MITMの観点で説明してください。

解答を見る

証明書検証を無効化した時点で、HTTPSの防御はほぼ意味を失います。TLSは「暗号化」と「相手が本物かの認証」の2本柱で成り立っており、検証を切ると後者が失われます。攻撃者は自分の証明書を提示してクライアントとの間にTLSセッションを張り、内容を復号・改ざんしたうえで本物のサーバーへ中継できます。通信は暗号化されて見えますが、暗号化している相手が攻撃者という状態です。正しい対応は、エラーの根本原因(期限切れ・自己署名・ホスト名不一致・CA未登録)を特定して解消することです。

問題 6-2

フラットな(全端末が相互に到達できる)社内ネットワークで、1台の業務端末がマルウェアに感染しました。この構成が被害を拡大させやすい理由と、セグメンテーションによってどう改善できるかを述べてください。

解答を見る

フラットなネットワークでは、感染端末から他の端末やサーバーへ制限なく到達できるため、攻撃者は容易に横展開し、認証情報を集めながら基幹サーバーや顧客DBへ近づけます。1台の侵害が全体の侵害に直結します。
セグメンテーションでネットワークを用途別に分け、セグメント間の通信を必要最小限に絞れば(最小到達性)、感染はそのセグメントに封じ込められます。マイクロセグメンテーションでワークロード単位に「Webサーバーはアプリサーバーの特定ポートにだけ接続可」と制御すれば、感染端末が到達できる範囲がさらに狭まり、被害を局所化できます。

問題 6-3

前方秘匿性(PFS)とは何か、そしてなぜ「一時鍵(Ephemeral)」であることが重要なのかを説明してください。

解答を見る

前方秘匿性とは、将来サーバーの長期秘密鍵が漏えいしても、過去に記録された通信を復号できないという性質です。ECDHEはハンドシェイクごとに使い捨ての鍵ペア(一時鍵)を生成してセッション鍵を導出し、その一時鍵はセッション終了後に破棄されます。セッション鍵はサーバーの長期秘密鍵からは復元できないため、長期鍵が後日漏れても過去の通信は守られます。対照的に、RSA鍵交換ではセッション鍵がサーバーの秘密鍵で保護されるため、秘密鍵が漏れると過去に盗聴・記録された全通信を復号され得ます。だからこそ一時鍵による鍵交換が重要で、TLS 1.3では(EC)DHEのみが使われます。