Elastic Securityで始める検知エンジニアリング — EQLとES|QLで攻撃を自動検出・集計する(第4回)

BLOG

少し間が空きましたが、シリーズを再開します。 第1〜3回でデータの取り込み・最初のルール作成・Timelineでの 攻撃チェーン調査まで進みました。第4回はその続きです。 EQLのsequenceで「失敗のあとに成功した」攻撃パターンを 自動検出し、ES|QLで「どのIPが最も危険か」「何バイト送られたか」 を数値で把握する方法を紹介します。CaseとNotesを使った 調査記録の残し方も扱います。

シリーズのこれまでの流れ:

  • 1回目 環境構築とログの取り込み
  • 2回目 KQLでログを読んで最初のルールを作る
  • 3回目 Timelineで攻撃の全体像を追う ← ここまで完了している前提

※本シリーズで使用するデータセットは、第1回の記事からダウンロードできます。


この記事を読むと何ができるか

  • EQLのsequence構文で攻撃の順序パターンを自動検出できる
  • ES|QLでIPごとのログイン失敗件数・大容量通信を集計できる
  • CaseとNotesで調査内容をチームで共有・記録できる

EQL(Correlation)で攻撃の「順序」を自動検出する

なぜ使うのか

第3回では「目でイベントを追って」攻撃の流れを確認しました。しかし実務では、1日に何千件ものイベントが流れる中から「特定の順番で起きたイベントの組み合わせ」を手動で見つけるのは現実的ではありません。

EQL(Event Query Language)の sequence 構文を使うと、「AのあとにBが起きた組み合わせ」を自動で検出できます。

EQLのsequenceクエリを書く

このサンプルデータでは event.category の値が "iam" です。Elastic SecurityのEQLパーサーが期待する authentication カテゴリとは名称が異なるため、any where event.category == "iam" という書き方を使います。

TimelineのCorrelationタブを開き、次のクエリを入力します。

sequence by source.ip
  [ any where event.category == "iam"
    and event.action == "user-login"
    and event.outcome == "failure" ]
  [ any where event.category == "iam"
    and event.action == "user-login"
    and event.outcome == "success"
    and user.name in ("admin_root", "admin", "root", "administrator") ]

このクエリの意味を整理します。確認ポイント:

  • source.ip: 192.168.1.100 のシーケンスが検出されているか
  • 1つ目のイベント(failure)と2つ目のイベントが時系列順に並んでいるか
  • 45.33.21.11 のシーケンスも検出されているか

EQLでできること・できないことを整理しておきます。

できることできないこと
イベントの順序を条件にする集計・合計値の計算
複数イベントをまとめて1件として扱うフィールドの加工・変換
時間的な近接(maxspan)を条件にする複数インデックスにまたがる複雑な結合

集計や加工が必要なときは、次のES|QLを使います。


ES|QLで集計・分析する

KQLは「条件に一致するイベントを見つける」検索ツールです。一方ES|QLは「見つけたイベントを集計・加工・ランキングする」分析ツールです。「どのIPが一番多く失敗しているか」「何バイト送られたか」を素早く把握したいときに使います。

ES|QLはパイプ(|)でコマンドをつなげる構造です。「FROMでデータを取り出し、WHEREで絞り込み、STATSで集計し、SORTで並べる」という順番で読むと理解しやすいです。

TimelineのES|QLタブを開きます。

クエリ1:ログイン失敗をIPごとに集計する

FROM training-security-logs
| WHERE event.action == "user-login" AND event.outcome == "failure"
| STATS failure_count = COUNT(*) BY source.ip
| SORT failure_count DESC

期待される結果:

source.ipfailure_count
45.33.21.118
192.168.1.1004

45.33.21.11 が外部から8回、192.168.1.100 が内部から4回失敗していることが一目でわかります。

クエリ2:攻撃対象のユーザー名を集計する

FROM training-security-logs
| WHERE event.action == "user-login" AND event.outcome == "failure"
| STATS attempt_count = COUNT(*) BY user.name
| SORT attempt_count DESC

期待される結果:

user.nameattempt_count
guest5
admin_root3
administrator2
admin1
root1

guest が最も多く試されています。攻撃者が「存在しそうなデフォルトアカウント名」から順番に試している典型的なパターンです。

クエリ3:大容量通信を探す

FROM training-security-logs
| WHERE event.action == "data-transfer"
| STATS max_bytes = MAX(network.bytes),
        total_bytes = SUM(network.bytes)
        BY source.ip, destination.ip
| WHERE max_bytes > 10000000
| SORT max_bytes DESC

期待される結果:

source.ipdestination.ipmax_bytestotal_bytes
192.168.1.100103.10.10.10085,000,00085,000,000以上

103.10.10.100 への85MB超の転送が浮かび上がります。このようなデータ量ベースの異常検知はKQLでは書けず、ES|QLが得意とする用途です。


Caseに調査内容を登録する

セキュリティ調査は個人作業ではありません。「何を見つけたか」「誰が対応しているか」「どう判断したか」をチームで共有し、記録として残すことが実務では不可欠です。

Security → Alerts を開き、source.ip: 192.168.1.100 のアラートをクリックして flyout を開きます。「Add to existing case → Add to new case」を選択します。

Case の記入例:

項目入力例
タイトル内部ブルートフォース攻撃 from 192.168.1.100
SeverityMedium(ログイン成功・データ持ち出しが確認されているため)
Tagsbrute-force, data-exfiltration, prod-srv-01

説明欄のテンプレート(初動調査メモ):

## 概要
2025-03-18 UTC 10:01〜10:10 の間に、192.168.1.100 から
PROD-SRV-01 に対する一連の攻撃を確認。

## 確認された活動
- 10:01台:ポートスキャン(445, 139, 80, 3389, 22)
- 10:03台:admin_root へのブルートフォース → 10:03:06 に成功
- 10:03〜10:04台:whoami, net user, net localgroup による偵察
- 10:05〜10:07台:スケジュールタスク・レジストリによる永続化
- 10:06〜10:10台:103.10.10.100 への C2 接続・85MB のデータ持ち出し

## 次のアクション
- [ ] 192.168.1.100 の所有者・用途を確認する
- [ ] admin_root アカウントのパスワードを即時変更する
- [ ] 103.10.10.100 への通信をブロックする
- [ ] PROD-SRV-01 のスケジュールタスク・レジストリを精査する

CaseのSeverityをアラートのSeverityより高くした理由:第2回で作ったルールはSeverity: Low(単純なログイン失敗の検知)でした。しかしTimelineで調査した結果、ログイン成功・永続化・データ持ち出しまで確認できました。ルールのSeverityはイベントの性質、CaseのSeverityは調査で判明した実際の影響度を反映させます。


Notesで調査メモを残す

Timelineは分析ツールですが、「自分が何を考えながら調査したか」はTimelineには残りません。Notesを使うことで、「事実(ログ)」「解釈(何が起きているか)」「仮説(次に何を確認すべきか)」を時系列のイベントに紐づけて残せます。

Timelineで気になるイベントの行にカーソルを合わせ、行左端のアクションアイコンから「Add note」をクリックします。

良いNotesには3つの要素があります。

要素内容
事実ログから読み取れること10:03:06 に admin_root で user-login success
解釈そのイベントが意味することブルートフォース成功。この時点から侵害が始まっている
仮説・次のアクション次に確認すること10:03:06 以降の admin_root の行動を追う

実際のメモ例(10:03:06の成功イベントに紐づけて記録):

【事実】
192.168.1.100 から admin_root での user-login が success。
直前の 10:03:00〜10:03:05 に同 IP から失敗が4件続いている。

【解釈】
ブルートフォース攻撃が成功し、PROD-SRV-01 への侵入が完了した
と判断できる。この時点が攻撃の「侵入成功ライン」。

【次のアクション】
admin_root として実行されたプロセスを 10:03:06 以降で追う。
特に cmd.exe, powershell.exe, schtasks.exe の実行を確認する。

ノートを追加しているものに赤い点が付きます。


この章のまとめ

機能使う場面
KQL(第3回)イベントを絞り込んで目で読む
Correlation(EQL)イベントの順序パターンを自動検出する
ES|QL集計・ランキング・異常値の発見
Case調査内容をチームで共有・記録する
Notes調査の思考プロセスを時系列に紐づける

第4回チェックリスト

  • [ ] EQLのsequenceクエリを実行し、192.168.1.100 のブルートフォースシーケンスが検出されている
  • [ ] ES|QLでIPごとのログイン失敗件数を集計し、45.33.21.11(8件)と 192.168.1.100(4件)が確認できている
  • [ ] 調査内容をCaseに登録し、タイトル・説明・次のアクションが入力されている
  • [ ] 10:03:06 の成功イベントにNotesを追加し、事実・解釈・次のアクションが記録されている

次回は: ルールを「作る」だけで終わらせない最終回です。Alert suppressionとExceptionsを正しく使い分けてノイズを減らし、ルールタイプ(Custom query / Threshold / ES|QL)を目的に応じて使い分ける方法を学びます。