【入門編】 IDS/IPSログとネットワークフローの相関分析による攻撃検知 – インシデントレスポンス & フォレンジック(DFIR)防御ガイド

こんにちは!インシデントレスポンスやデジタルフォレンジックの世界へようこそ。
セキュリティの現場にいると、「なんだかサーバーが変な動きをしているぞ…?」というドキッとする瞬間に出会うことがあります。

今回は、ネットワークの番人である「IDS/IPS(不正侵入検知・防御システム)」のログと、交通量調査のような「NetFlow(ネットワークフロー)」のデータを組み合わせて、巧妙に隠れる攻撃者をあぶり出す「相関分析」のコツを、身近な例えを交えながら優しく紐解いていきたいと思います。
難しい用語が出てきても、「一歩ずつ対策を学んでいきましょう!」の精神で進めていきますので、どうぞリラックスして読んでくださいね。

—

1. 家の防犯に例えて理解する「IDS/IPS」と「NetFlow」

突然ですが、みなさんのご自宅の防犯対策を想像してみてください。

玄関のドアに「ピッキングしようとしたら大きな音でアラームが鳴るセンサー」を付けたとします。これがセキュリティの世界における IDS/IPS(Intrusion Detection/Prevention System) です。怪しい動き(シグネチャ)をパッと見つけて、「泥棒だ!」と警報を鳴らしてくれる頼もしい存在ですね。

一方で、マンションの管理人さんがエントランスの防犯カメラの映像を見ながら、「Aさんが何時何分にロビーを通り、エレベーターに乗って、何階で降りたか」という人の移動の足取り(メタデータ)を記録している帳簿があるとします。これが NetFlow(ネットワークフロー) です。NetFlowは「何を話したか(会話の内容)」までは記録しませんが、「誰と誰が、いつ、どれくらいの時間、どれだけのデータ量のやり取りをしたか」という通信の全貌を記録してくれます。

なぜ両方を組み合わせる必要があるの?

泥棒(攻撃者)も賢いので、警報器(IDS)が鳴らないようにコソコソと侵入したり、警報の目を盗んで裏口から忍び込んだりします。

IDSのログだけを見ていると、「あ、ここで何か攻撃の断片があったな」とは分かりますが、「そのあと泥棒はリビングのどこを荒らして、最終的にどの窓からお宝を持ち出したのか?」という全体のストーリーが見えなくなってしまいます。
ここでNetFlowの出番です。IDSが「危ない!」と検知した瞬間を起点にして、NetFlowの足取りデータを時系列でドミノ倒しのように繋ぎ合わせることで、攻撃の全体像(初期侵入からデータの持ち出しまで)を綺麗に描き出すことができるのです。

—

2. 攻撃のメカニズム:侵入から持ち出しまでのタイムライン

実際のサイバー攻撃は、映画のように一瞬で大金を持ち去るわけではありません。大体次のようなステップでじわじわと進んでいきます。

1. 初期侵入(Initial Access): 脆弱性をついて社内ネットワークへ侵入。
2. 内部探索(Discovery): 「他にどんなパソコンがあるかな?」と周囲をキョロキョロ見回す。
3. 横展開(Lateral Movement): パスワードを盗むなどして、より重要度の高いサーバーへ移動する。
4. データ持ち出し(Exfiltration): 大切なデータを外部の秘密のサーバーへこっそり送信する。

この一連の流れを、IDSの「アラートログ」とNetFlowの「通信量データ」を突き合わせながら追跡するのが、私たちアナリストの腕の見せ所です。

—

3. 実践!ログとフローの相関分析をやってみよう

それでは、実際に現場で使われているようなログの相関分析を、具体的な例を見ながら体験してみましょう。

今回は、Pythonを使って「怪しいIDSアラート」と「NetFlowの通信記録」を読み込み、時刻とIPアドレスを手がかりに相関(紐付け)させる簡単なスクリプトを書いてみます。
実務の開発やインフラ構築でも、こうしたスクリプトをベースに自動化の仕組みを作ることが多いので、ぜひ参考にしてみてくださいね。

相関分析を行うPythonスクリプトのサンプル

from datetime import datetime

# 模擬的なIDS/IPSのログ(警報データ)
# 誰が、いつ、どんな攻撃の気配を検知されたか
ids_alerts = [
    {
        "timestamp": "202X-10-10 14:22:10",
        "src_ip": "192.168.1.50",  # 社内の怪しい端末
        "dst_ip": "10.0.0.5",  # 社内の重要データベースサーバー
        "signature": "SQLインジェクションの試行を検知",
    }
]

# 模擬的なNetFlowデータ(交通量の足取りデータ)
# どのIP間通信が、どれくらいのデータ量をやり取りしたか
netflow_records = [
    {
        "start_time": "202X-10-10 14:22:05",
        "end_time": "202X-10-10 14:22:15",
        "src_ip": "192.168.1.50",
        "dst_ip": "10.0.0.5",
        "bytes_transferred": 1500,
        "protocol": "TCP",
    },
    {
        "start_time": "202X-10-10 14:30:00",
        "end_time": "202X-10-10 14:45:00",
        "src_ip": "10.0.0.5",  # 重要データベースサーバー
        "dst_ip": "203.0.113.88",  # 外部の怪しい宛先(見知らぬ海外IP)
        "bytes_transferred": 500000000,  # 500MBという大量のデータ!
        "protocol": "HTTPS",
    },
]


def correlate_logs(alerts, flows):
    print("=== インシデント相関分析レポート ===")
    print("IDSの警報を起点に、ネットワークの足取りを追跡します...\n")

    for alert in alerts:
        alert_time = datetime.strptime(alert["timestamp"], "%Y-%m-%d %H:%M:%S")
        print(f"[!] IDS検知: {alert['timestamp']}")
        print(
            f"    内容: {alert['signature']} ({alert['src_ip']} -> {alert['dst_ip']})"
        )
        print("    --- 関連するNetFlowの動きを検索中 ---")

        # 警報を発した端末に関連するネットワークフローを探す
        for flow in flows:
            flow_start = datetime.strptime(
                flow["start_time"], "%Y-%m-%d %H:%M:%S"
            )

            # 条件1: IDSの検知前後の通信であること
            # 条件2: 攻撃元または宛先が関与していること
            if (
                flow["src_ip"] == alert["src_ip"]
                or flow["dst_ip"] == alert["dst_ip"]
            ):
                if flow_start >= alert_time or abs((flow_start - alert_time).seconds) < 30:
                    print(f"    -> [発見!] フロー情報マッチ:")
                    print(
                        f"       時間: {flow['start_time']} 〜 {flow['end_time']}"
                    )
                    print(
                        f"       経路: {flow['src_ip']} ==> {flow['dst_ip']} ({flow['protocol']})"
                    )
                    print(
                        f"       通信量: {flow['bytes_transferred'] / 1024 / 1024:.2f} MB"
                    )

                    # もし外部への大量通信であれば警告を強調する
                    if flow["bytes_transferred"] > 100000000:  # 100MB超え
                        print(
                            "       [!] 警告: 外部への不審な大容量データの持ち出しの可能性があります!"
                        )
                print()


if __name__ == "__main__":
    correlate_logs(ids_alerts, netflow_records)

このコードを実行すると、IDSが「SQLインジェクションだ!」と叫んだ瞬間だけでなく、そのサーバーがのちに外部の怪しいIPアドレスへ向けて 500MBもの大量データ を送信しているという「決定的な足取り」までを一本のストーリーとして繋ぎ合わせることができます。

—

4. 現場で役立つ!分析をスムーズに進めるためのヒント

実際のインシデント対応では、ログの量があまりにも膨大で、目視だけで確認するのは至難の業です。現場で泥臭く調査を行うための実用的なアプローチをいくつかご紹介します。

タイムゾーンの統一(ズレは大敵)

IDSのログはUTC(協定世界時)、NetFlowのルーターはJST(日本標準時)で記録されていた……なんてことは現場では日常茶飯事です。相関分析を行う前に、必ずすべてのログのタイムスタンプを基準となる時間(通常はUTC)に綺麗に揃えるクセをつけましょう。ここがズレていると、犯人のアリバイを完全に誤認してしまいます。

「閾値(スレッショルド)」の設定

NetFlowデータを活用する際は、通常の通信量ベースライン(普段どれくらい通信しているか)を知っておくことが大切です。「普段は1日数MBしかやり取りしない経理用サーバーから、深夜に突然1GBの通信が発生した」といった異常な流量のスパイク(急上昇)を検知できるように、アラートの条件を設定しておくと、IDSが検知し損ねた攻撃の尻尾も掴むことができます。

—

まとめ

いかがでしたでしょうか?
IDS/IPSログが教えてくれるのは「事件の瞬間(断片)」であり、NetFlowが教えてくれるのは「犯人の足取り(全体の流れ)」です。

この二つを時系列で組み合わせることで、新人エンジニアのみなさんでも、単なるアラートの確認作業から一歩進んで、「攻撃がどこから侵入し、どこへ向かったのか」という全体像を鮮やかに描き出すことができるようになります。

セキュリティの世界は奥が深く、最初は難しく感じるかもしれませんが、こうした小さなロジックの積み重ねが組織を守る大きな盾になります。
ぜひ、日々の運用や検証環境でのテストを通じて、一歩ずつスキルを磨いていってくださいね!応援しています!

コメント

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