【実務・中級編】 VPCフローログにおける拒否(REJECT)パケットの特定とセキュリティ分析 – クラウドインフラと仮想化ネットワーク実践ガイド

はじめに:夜中に鳴り響くアラートと、VPCフローログという「無言の証人」

夜中の3時。PagerDutyの甲高いアラート音で叩き起こされ、目の下にクマを作った状態でAWSマネジメントコンソールを開く――SREやクラウドインフラエンジニアであれば、誰もが一度は経験する胃の痛むシチュエーションです。

「またどこかのボットネットが、我が社のWeb APIエンドポイントを総当たりで叩いている……」

セキュリティグループ(SG)やネットワークACL(NACL)が適切にパケットをドロップしてくれているおかげで、実害は防げているものの、ダッシュボードのメトリクスには不審なトラフィックの嵐。ここで多くのエンジニアが「ファイアウォールが弾いてるから大丈夫」と油断してログの精査をサボりがちですが、実はここに次世代のセキュリティインシデントを防ぐための黄金の鉱脈が眠っています。

それが、VPCフローログ(VPC Flow Logs)における REJECT パケットの分析です。

今回は、AWSの仮想ネットワーク空間を駆け巡るパケットが、どのようにセキュリティの関所で弾かれ、その事実がどのようにログという名の「足跡」として残されるのか。シニアSREの私から、現場のリアルな知見と共にお伝えしましょう。

—

1. パケットが裁かれる瞬間:AWSネットワークの検問所とフローログの仕組み

AWSのVPC内において、インスタンスやコンテナが出入りするパケットは、いくつかの厳重な検問所を通過します。具体的には、サブネットレベルのネットワークACL(NACL)と、ENI(Elastic Network Interface)レベルのセキュリティグループ(SG)です。

ここで重要なのは、VPCフローログが「どこで」記録されるかという点です。VPCフローログは、ENIのネットワークインターフェイスレベルでトラフィックをキャプチャします。つまり、NACLやSGによって拒否(REJECT)されたパケットであっても、ENIの直前・直後でしっかりとキャプチャされるため、「誰が、どのポートに向かって、どんな悪意あるスキャンを仕掛けたのか」を完全にトレースできるのです。

標準的なRFCとステートレス・ステートフル挙動の罠

ネットワークの基本に立ち返りましょう。NACLはステートレス(Stateless)です。インバウンドで許可した通信であっても、アウトバウンドで明示的にルールを書かなければレスポンスパケットはNACLで REJECT されます。
一方、SGはステートフル(Stateful)です。インバウンドで許可された通信の戻りパケットは、アウトバウンド側のルールを気にせず自動的に通過します。

この挙動の違いを理解していないと、「なぜかNACLのログに REJECT が大量発生しているが、アプリケーションのバグなのか外部からの攻撃なのか判別がつかない」という泥沼にハマることになります。

—

2. VPCフローログ(バージョン5)の構造と注目すべきパラメーター

AWSのVPCフローログ(特に現在の主流であるバージョン5)は、デフォルトで非常にリッチなフィールドを出力します。不正アクセス検知において、私たちが血眼になって見るべき主要なパラメーターを整理しておきましょう。

  • version: ログのフォーマットバージョン(通常は 5)
  • account-id: AWSアカウントID
  • interface-id: 対象のENI ID(どのリソースへの通信か特定可能)
  • srcaddr / dstaddr: 送信元IPアドレス と 宛先IPアドレス
  • srcport / dstport: 送信元ポート と 宛先ポート
  • protocol: IANAプロトコル番号(TCPは 6、UDPは 17、ICMPは 1)
  • packets / bytes: パケット数とバイト数
  • start / end: 通信の開始・終了タイムスタンプ(Unix時間)
  • action: 通信結果。許可された場合は ACCEPT、拒否された場合は REJECT
  • log-status: ログが正常に記録されたか(OK、NODATA、SKIP)

特に action=REJECT となっているレコードは、不正アクセスの試行、ポートスキャン、あるいは誤設定による通信断の決定的な証拠となります。

—

3. 実践:CloudWatch Logs Insightsを使った「REJECTパケット」のハンティング

マネジメントコンソールやAthenaを叩くのも良いですが、リアルタイムかつアドホックな調査には CloudWatch Logs Insights が最も手軽で強力です。

以下のクエリは、直近の時間帯において、どの外部IPアドレスがどの宛先ポートに対して最も多く REJECT を食らっているかを炙り出すための実戦用クエリです。

# CloudWatch Logs Insights用クエリ
# 拒否されたパケットを集計し、攻撃者のIPとターゲットポートを特定する
filter action = "REJECT" and not is_in_subnet(srcaddr, "10.0.0.0/16")
| stats count(*) as reject_count by srcaddr, dstport, protocol
| sort reject_count desc
| limit 20

このクエリを走らせたとき、もし dstport が 22(SSH)や 3389(RDP)、あるいはデータベースのデフォルトポート(3306, 5432)に集中していれば、それは世界中からボットネットによるブルートフォース攻撃を受けている動かぬ証拠です。SGがそれを完全に弾いているため実害はありませんが、ログのノイズが増えることで真のインシデント(アプリケーション層の脆弱性突いた攻撃など)が埋もれるリスクがあります。

—

4. 自動化:PythonとAWS SDK(Boto3)を用いたログ分析とアラート連携

SREの仕事は「見つけること」ではなく「自動で検知し、未然に防ぐ仕組みを作ること」です。ここでは、Python(Boto3)を使用してCloudWatch Logsから REJECT ログを定期的に集計し、Slack等のチャットツールへアラートを飛ばすスクリプトの骨組みを紹介します。

実務でそのまま組み込めるよう、適切なコメントを付与しています。

import boto3
import time
from datetime import datetime, timedelta

def analyze_vpc_rejects(log_group_name):
    """
    指定したCloudWatch Logsグループから、直近1時間以内のREJECTパケットを集計する
    """
    client = boto3.client('logs')
    
    # 調査時間の設定(過去1時間)
    end_time = int(time.time())
    start_time = end_time - 3600
    
    # Logs Insightsのクエリ定義
    query = """
        filter action = "REJECT"
        | stats count(*) as reject_count by srcaddr, dstport
        | sort reject_count desc
        | limit 5
    """
    
    # クエリの実行開始
    start_query_response = client.start_query(
        logGroupName=log_group_name,
        startTime=start_time,
        endTime=end_time,
        queryString=query
    )
    
    query_id = start_query_response['queryId']
    
    # クエリの完了をポーリングで待機
    print(f"Query started (ID: {query_id}). Waiting for results...")
    response = None
    while True:
        response = client.get_query_results(queryId=query_id)
        status = response['status']
        if status == 'Complete':
            break
        elif status in ['Failed', 'Cancelled']:
            raise Exception(f"Log Insights query failed with status: {status}")
        time.sleep(1)
        
    # 結果のパースとコンソール出力(実務ではここでSlack Webhook等にJSONを投げる)
    print("\n--- Top 5 REJECT Sources ---")
    for row in response['results']:
        # rowは [{'field': 'srcaddr', 'value': '...'}, {'field': 'dstport', 'value': '...'}, ...] の形式
        data = {item['field']: item['value'] for item in row}
        print(f"Source IP: {data.get('srcaddr')} -> Port: {data.get('dstport')} (Rejected: {data.get('reject_count')} times)")

if __name__ == "__main__":
    # 実際の環境に合わせてロググループ名を変更してください
    LOG_GROUP_NAME = "/aws/vpc/flow-logs/production"
    analyze_vpc_rejects(LOG_GROUP_NAME)

このようなスクリプトをAWS Lambdaにデプロイし、Amazon EventBridge(CloudWatch Events)で1時間おきにキックするように設定しておくだけで、インフラのセキュリティ耐性を常に可視化できます。

—

5. シニアSREからの現場のTips:ノイズの削減と真の脅威の見極め

最後に、現場で数々のインシデント対応を行ってきた私から、VPCフローログ運用におけるいくつかの重要なTipsを共有します。

1. ログのコストとサンプリングに注意する
VPCフローログは非常に強力ですが、トラフィックが多い環境ではCloudWatch LogsやS3のストレージコストがバカになりません。プロダクション環境では、すべてのパケットを記録するのではなく、必要に応じてS3へ直接出力させたり、集約間隔(Aggregation Interval)をデフォルトの1分から10分に延ばすことでコストを最適化しましょう。
2. パブリックIPを持つリソースの棚卸し
REJECT ログの dstaddr を見たときに、「本来外部に公開すべきでない内部用EC2インスタンス(プライベートサブネットにあるべきものなど)」宛ての通信が拒否されている場合、それはネットワーク構成の致命的なミス(パブリックサブネットへの誤配置やEIPの付与)のシグナルです。ファイアウォールが弾いてくれているとはいえ、根本的なアーキテクチャの不備を直ちに修正すべきです。
3. AWS WAFやGuardDutyとの連携
VPCフローログはネットワーク層(L3/L4)の挙動です。ここでの REJECT 分析に加え、アプリケーション層(L7)の挙動を監視するAWS WAFや、AWS環境全体の脅威を機械学習で検知するAmazon GuardDutyと組み合わせることで、多層防御(ディフェンス・イン・ディープ)の精度は飛躍的に向上します。

—

おわりに

「ログを取っているから安心」ではなく、「ログを読み解き、攻撃者の意図を理解し、インフラの構造的欠陥を早期に塞ぐ」。これこそが、モダンなSREに求められるプロフェッショナルな姿勢です。

VPCフローログの REJECT は、ただの「捨てられたパケットの残骸」ではありません。それは、あなたのシステムを守り抜いたファイアウォールたちの勲章であり、サイバー空間の脅威を映し出す鏡なのです。今夜、あなたのAWS環境のフローログにも、新しい発見が眠っているかもしれません。ぜひ、今回のクエリやスクリプトを試してみてください。

コメント

タイトルとURLをコピーしました