eql

EQL(Event Query Language)徹底解説

1. はじめに

1.1 本記事の目的

本記事では、Elasticsearchのイベントベース時系列データに特化したクエリ言語EQL(Event Query Language)について包括的に解説します。EQLは単一のイベントのマッチングだけでなく、複数のイベント間の時系列的な関係性やパターンを検出できる点が最大の特徴です。元々セキュリティの脅威ハンティングのために開発されましたが、ログ分析、インシデント調査、コンプライアンス監査など、あらゆるイベント駆動型データの分析に応用できます。

本記事を読み終えることで、以下の知識が得られます。

  • EQLのアーキテクチャと設計思想
  • 基本クエリ、シーケンスクエリ、サンプルクエリの構文と使い方
  • 演算子、関数、パイプの全リファレンス
  • EQL Search APIの使い方(同期・非同期)
  • セキュリティユースケースでの実践的な活用パターン
  • パフォーマンスとベストプラクティス

1.2 EQLの位置づけ

観点EQLQuery DSLES|QL
対象データイベントベース時系列データあらゆるデータあらゆるデータ
最大の強みイベント間の時系列パターン検出柔軟性と全機能アクセスパイプライン型のアドホック分析
シーケンス検出ネイティブサポート非対応非対応
学習コスト低い(SQL風の構文)中〜高い低い
主な用途脅威ハンティング、インシデント調査アプリケーション組み込みデータ探索、ログ分析

1.3 動作環境

  • Elasticsearch: 7.12以降(8.x / 9.x推奨)
  • 必須フィールド: タイムスタンプフィールド(デフォルト: @timestamp)、イベントカテゴリフィールド(デフォルト: event.category
  • 推奨スキーマ: Elastic Common Schema (ECS)

2. EQLのアーキテクチャ

2.1 設計思想

EQLは以下の3つの原則に基づいて設計されています。

  1. イベント間の関係性の表現: 多くのクエリ言語が個々のイベントの検索に限定されるのに対し、EQLは異なるカテゴリ・異なるタイムスパンにまたがるイベント間の関係を表現できる
  2. 低い学習コスト: SQLに似た構文で直感的にクエリを構築でき、反復的な検索が迅速に行える
  3. セキュリティ特化の設計: IOC(侵害の痕跡)検索とTTP(戦術・技術・手順)ベースの脅威検出の両方をサポート

2.2 必須フィールド

EQLクエリを実行するためには、対象インデックスに以下の2つのフィールドが必要です。

フィールドデフォルト値説明
タイムスタンプ@timestampdateイベントの発生時刻(date_nanos は非対応)
イベントカテゴリevent.categorykeywordイベントの種別分類

カスタマイズも可能です。

GET /my-logs/_eql/search
{
  "timestamp_field": "my_timestamp",
  "event_category_field": "my_category",
  "query": """
    my_event_type where host.name == "server-01"
  """
}

2.3 Elastic Common Schema (ECS)

EQLはECSの採用を強く推奨しています。ECSに準拠することで以下のメリットがあります。

  • EQLクエリの互換性と再利用性が最大化される
  • Elastic Securityのビルトインルールとの互換性
  • event.category のバリエーション(process, file, network, authentication など)が標準化される
  • コミュニティ間でのクエリの共有が容易になる

2.4 実行フロー

EQLクエリ → パース → 最適化 → シャード分散実行 → イベント収集 → シーケンス/サンプル結合 → レスポンス
  1. パース: EQLクエリ文字列がAST(抽象構文木)に変換される
  2. Query DSLフィルタの適用: filter パラメータのQuery DSL条件で事前にドキュメントを絞り込み
  3. シャード分散実行: 各シャードでイベントの検索が並列実行される
  4. シーケンス/サンプル結合: シーケンスクエリの場合、収集されたイベントが時系列的に結合される
  5. レスポンス構築: 結果がクライアントに返却される

3. 基本クエリ

3.1 構文

EQLの基本クエリは、イベントカテゴリ条件where で結合します。

<イベントカテゴリ> where <条件>
process where process.name == "powershell.exe"

このクエリは、event.categoryprocess で、process.namepowershell.exe であるイベントにマッチします。デフォルトでは直近10件が時系列順で返されます。

3.2 any キーワード

any を使用すると、イベントカテゴリに関係なく全イベントを対象にできます。

any where host.name == "server-01" and message : "*error*"

3.3 比較演算子

演算子説明
==等しい(大文字小文字区別)status == "active"
!=等しくないstatus != "inactive"
<より小さいport < 1024
<=以下severity <= 3
>より大きいbytes > 10000
>=以上count >= 5
:大文字小文字無視の等値比較user.name : "admin"

3.4 : 演算子(大文字小文字無視 + ワイルドカード)

: 演算子は特に重要で、以下の機能を提供します。

// 大文字小文字を無視した比較
process where process.name : "PowerShell.exe"

// ワイルドカード検索
file where file.path : "C:\\Users\\*\\Downloads\\*.exe"

// リストマッチ
process where process.name : ("cmd.exe", "powershell.exe", "wscript.exe")
パターン意味
*0文字以上の任意の文字列
?任意の1文字

3.5 パターンマッチング

// like(ワイルドカードベース)
process where process.command_line like "* -enc *"

// like~(大文字小文字無視)
process where process.command_line like~ "* -ENC *"

// regex(正規表現ベース)
process where process.name regex """[a-z]{3,5}\.exe"""

// regex~(大文字小文字無視)
process where process.name regex~ """power[a-z]+\.exe"""

3.6 論理演算子

// AND
process where process.name == "cmd.exe" and process.args : "whoami"

// OR
process where process.name : ("cmd.exe", "powershell.exe") or process.parent.name == "explorer.exe"

// NOT
process where process.name == "svchost.exe" and not process.parent.name == "services.exe"

3.7 IN演算子

process where process.name in ("cmd.exe", "powershell.exe", "bash", "sh")

process where process.name not in ("svchost.exe", "csrss.exe", "lsass.exe")

// 大文字小文字無視
process where process.name in~ ("CMD.exe", "PowerShell.EXE")

3.8 算術演算子

network where source.bytes + destination.bytes > 1000000

process where process.args_count % 2 == 0

4. シーケンスクエリ

シーケンスクエリはEQLの最も強力かつ差別化された機能です。順序付けられた複数のイベントが時系列に沿って発生するパターンを検出します。

4.1 基本構文

sequence
  [process where process.name == "cmd.exe"]
  [file where file.extension == "bat"]
  [network where destination.port == 443]

この例では、以下の3つのイベントがこの順番で発生した場合にマッチします。

  1. cmd.exe プロセスの実行
  2. .bat ファイルへのアクセス
  3. ポート443への通信

4.2 maxspan(時間制約)

sequence with maxspan=5m
  [authentication where event.outcome == "failure"]
  [authentication where event.outcome == "failure"]
  [authentication where event.outcome == "failure"]
  [authentication where event.outcome == "success"]

maxspan はシーケンス全体(最初のイベントから最後のイベントまで)の時間窓を指定します。この例は5分以内に3回の認証失敗と1回の認証成功が連続するパターンを検出します。

サポートされる時間単位: s(秒)、m(分)、h(時間)、d(日)

4.3 by キーワード(イベント相関)

// 各イベントごとに個別のby条件
sequence
  [process where process.name == "cmd.exe"] by process.pid
  [file where file.extension == "ps1"] by process.pid
  [network where destination.port == 443] by process.pid

by キーワードは指定フィールドの値が共通するイベント同士を相関させます。

// sequence by で全イベントに共通条件を適用
sequence by user.name with maxspan=10m
  [authentication where event.outcome == "failure"]
  [authentication where event.outcome == "failure"]
  [authentication where event.outcome == "success"]
  [process where process.name == "whoami"]

sequence byby 条件をシーケンスの全イベントに一括で適用します。

4.4 欠落イベント(! 演算子)

sequence with maxspan=1h
  [process where event.action == "start"] by process.pid
  ![process where event.action == "end"] by process.pid

! を使用すると、特定のイベントが発生しなかったことを条件にできます。この例は、プロセスが開始されたが1時間以内に終了しなかったケースを検出します。maxspan が必須です。

4.5 until キーワード(終了条件)

sequence by process.pid with maxspan=1h
  [process where event.action == "start"]
  [network where destination.port != 80]
until [process where event.action == "end"]

until は、シーケンスの追跡を打ち切るイベントを定義します。終了イベントが発生すると、その時点でそのプロセスIDに対するシーケンスの追跡は中止されます。

4.6 with runs(繰り返し)

sequence with maxspan=10m
  [authentication where event.outcome == "failure"] with runs=5
  [authentication where event.outcome == "success"]

with runs を使用すると、同じ条件のイベントが指定回数繰り返されるパターンを検出できます(1〜100回)。この例は5回の認証失敗後に認証成功が発生するパターンです。

4.7 実践的なシーケンスパターン

ブルートフォース攻撃の検出

sequence by source.ip with maxspan=5m
  [authentication where event.outcome == "failure"] with runs=10
  [authentication where event.outcome == "success"]

ラテラルムーブメントの検出

sequence by user.name with maxspan=30m
  [authentication where event.action == "logged-in" and source.ip != "10.0.0.0/8"]
  [process where process.name : ("psexec.exe", "wmic.exe", "winrm.exe")]
  [network where destination.port in (445, 135, 5985)]

ランサムウェアの振る舞い検出

sequence by process.entity_id with maxspan=5m
  [process where process.name : ("*.exe", "*.scr")]
  [file where event.action == "creation" and file.extension : ("encrypted", "locked", "crypto")]
  [file where event.action == "deletion" and file.name : "*readme*"]

権限昇格の検出

sequence by host.name with maxspan=15m
  [process where process.name == "whoami"]
  [process where process.name : ("net.exe", "net1.exe") and process.args : "localgroup"]
  [process where process.name : ("runas.exe", "sudo")]

5. サンプルクエリ

5.1 概要

サンプルクエリは、タイムスタンプの順序に依存せずにイベントを相関させます。シーケンスクエリとは異なり、イベントの発生順序は問わず、結合キーの値が共通するイベントグループを見つけます。

5.2 構文

sample by host.name
  [file where file.extension == "exe"]
  [network where destination.port == 22]
  [process where process.name == "bash"]

5.3 制約

  • 最大5つのフィルタを指定可能
  • maxspanrunsuntil は使用不可
  • パイプはサポートされない
  • 結果は時系列順にソートされない
  • max_samples_per_keysize で結果数を制御

5.4 ユースケース

// 同一ホストでの疑わしい活動パターン(順序不問)
sample by host.name
  [authentication where event.outcome == "failure"]
  [process where process.name : ("mimikatz*", "procdump*")]
  [network where destination.port not in (80, 443)]

6. 関数リファレンス

6.1 文字列関数

関数説明
concat(s1, s2, ...)文字列結合concat(user.domain, "\\", user.name)
substring(s, start, end)部分文字列substring(file.name, 0, 5)
length(s)文字列の長さlength(process.command_line) > 200
startsWith(s, prefix)プレフィックス判定startsWith(file.path, "C:\\Windows")
endsWith(s, suffix)サフィックス判定endsWith(file.name, ".exe")
stringContains(s, sub)部分文字列の存在判定stringContains(process.args, "-enc")
indexOf(s, sub, start)部分文字列の位置indexOf(url.path, "/api/") >= 0
between(s, left, right)区切り文字間の抽出between(email, "", "@")
string(val)文字列への変換string(process.pid)

大文字小文字を無視するバリアント: startsWith~, endsWith~, stringContains~

6.2 数学関数

関数説明
add(a, b)加算add(source.bytes, destination.bytes)
subtract(a, b)減算subtract(event.end, event.start)
multiply(a, b)乗算multiply(price, quantity)
divide(a, b)除算divide(total_bytes, 1024)
modulo(a, b)剰余modulo(process.pid, 2) == 0
number(s, base)数値への変換number("0xFF", 16)

6.3 ネットワーク関数

関数説明
cidrMatch(ip, cidr1, ...)CIDR範囲内の判定cidrMatch(source.ip, "10.0.0.0/8", "172.16.0.0/12")

6.4 関数の使用例

// 長いコマンドラインでBase64エンコーディングを使用するプロセス
process where length(process.command_line) > 500
  and stringContains(process.command_line, "-enc")

// 内部ネットワーク以外からの接続
network where not cidrMatch(source.ip, "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16")
  and destination.port : (22, 3389, 5985)

// 拡張子が偽装されたファイル
file where endsWith(file.name, ".pdf.exe")
  or (endsWith(file.name, ".exe") and stringContains(file.name, ".doc"))

7. パイプ

パイプはEQLクエリの結果を後処理するための機能です。

7.1 head パイプ

最も古いイベントから指定件数を返します。

process where process.name == "powershell.exe"
| head 5

7.2 tail パイプ

最も新しいイベントから指定件数を返します。

process where process.name == "svchost.exe"
| tail 10

注意: パイプはサンプルクエリではサポートされていません。


8. EQL Search API

8.1 基本的なリクエスト

GET /security-events-*/_eql/search
{
  "query": """
    process where process.name == "regsvr32.exe"
      and process.args : "scrobj.dll"
  """,
  "size": 20
}

8.2 主要パラメータ

パラメータデフォルト説明
querystring(必須)EQLクエリ文字列
sizeinteger10返却するマッチングイベント/シーケンスの最大数
timestamp_fieldstring@timestampタイムスタンプフィールド名
event_category_fieldstringevent.categoryイベントカテゴリフィールド名
tiebreaker_fieldstringなしタイムスタンプ同値時のソートフィールド
filterobjectなし事前フィルタリング用のQuery DSL
fieldsarrayなし返却する特定フィールドの指定
runtime_mappingsobjectなしランタイムフィールドの定義
keep_alivestring5d非同期検索結果の保持期間
keep_on_completionbooleanfalse同期検索結果の保持
wait_for_completion_timeoutstringなし非同期検索のタイムアウト

8.3 Query DSLフィルタの組み合わせ

GET /logs-*/_eql/search
{
  "query": """
    process where process.name == "cmd.exe"
  """,
  "filter": {
    "bool": {
      "filter": [
        { "range": { "@timestamp": { "gte": "now-24h" } } },
        { "term": { "host.os.family": "windows" } }
      ]
    }
  }
}

8.4 ランタイムフィールド

GET /logs-*/_eql/search
{
  "runtime_mappings": {
    "day_of_week": {
      "type": "keyword",
      "script": "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ENGLISH))"
    }
  },
  "query": """
    process where process.name == "powershell.exe" and day_of_week : ("Saturday", "Sunday")
  """,
  "fields": ["day_of_week", "process.name", "@timestamp"]
}

8.5 非同期検索

// 1. 非同期検索の開始
GET /logs-*/_eql/search
{
  "query": """
    sequence by process.pid with maxspan=10m
      [process where process.name == "cmd.exe"]
      [file where file.extension == "ps1"]
      [network where destination.port == 443]
  """,
  "wait_for_completion_timeout": "2s"
}

// レスポンス(タイムアウト時)
// {
//   "id": "FmNJRUZ1YWZCU3dHY1BIOUhaenVSRkEaaXFlZ3h4c1RTWFNocDdnY2FSS...",
//   "is_partial": true,
//   "is_running": true,
//   ...
// }
// 2. 結果の取得
GET /_eql/search/FmNJRUZ1YWZCU3dHY1BIOUhaenVSRkEaaXFlZ3h4c1RTWFNocDdnY2FSS...

// 3. ステータスの確認
GET /_eql/search/status/FmNJRUZ1YWZCU3dHY1BIOUhaenVSRkEaaXFlZ3h4c1RTWFNocDdnY2FSS...

// 4. 検索の削除
DELETE /_eql/search/FmNJRUZ1YWZCU3dHY1BIOUhaenVSRkEaaXFlZ3h4c1RTWFNocDdnY2FSS...

8.6 クロスクラスタ検索

GET /cluster_one:security-events-*,cluster_two:security-events-*/_eql/search
{
  "query": """
    sequence by user.name with maxspan=15m
      [authentication where event.outcome == "failure"] with runs=5
      [authentication where event.outcome == "success"]
  """
}

9. 実践的なユースケース

9.1 MITRE ATT&CKフレームワークに基づく検出ルール

T1059.001 - PowerShellの悪用

process where process.name == "powershell.exe"
  and (
    process.args : "*-EncodedCommand*"
    or process.args : "*-enc*"
    or process.args : "*-WindowStyle Hidden*"
    or process.args : "*-ExecutionPolicy Bypass*"
    or length(process.command_line) > 500
  )

T1003 - 資格情報のダンプ

sequence by host.name with maxspan=5m
  [process where process.name : ("mimikatz*", "procdump*", "taskmgr.exe")]
  [file where file.name : ("*.dmp", "lsass*")]

T1021.002 - SMBを使用したラテラルムーブメント

sequence by source.ip with maxspan=30m
  [authentication where event.action == "logged-in" and event.outcome == "success"]
  [network where destination.port == 445]
  [process where process.name : ("psexec*", "wmic*")]

9.2 異常検出パターン

営業時間外の管理者活動

process where user.name : ("admin*", "root") 
  and not cidrMatch(source.ip, "10.0.0.0/8")

異常な親子プロセス関係

process where process.parent.name == "explorer.exe"
  and process.name : ("cmd.exe", "powershell.exe", "wscript.exe", "cscript.exe", "mshta.exe")
  and process.args : ("*http*", "*ftp*", "*\\\\*")

9.3 インシデントレスポンス

ファイルの改ざん追跡

sequence by file.path with maxspan=1h
  [file where event.action == "modification"]
  [file where event.action == "modification"]
  [file where event.action == "modification"]

永続化メカニズムの検出

sequence by host.name with maxspan=1h
  [process where event.action == "start"]
  [registry where registry.path : "*\\CurrentVersion\\Run*"]

10. パフォーマンスとベストプラクティス

10.1 パフォーマンス最適化

テクニック説明
filter パラメータQuery DSLフィルタで事前にデータを絞り込む
maxspan の適切な設定シーケンスの時間窓を可能な限り短くする
by キーワードの活用結合キーを指定してマッチング候補を絞り込む
tiebreaker_fieldタイムスタンプ同値時の一貫したソート
非同期検索大規模データセットには非同期検索を使用
ECSの採用標準的なフィールドマッピングでインデックス効率を最大化

10.2 制限事項

制限説明
date_nanos 非対応タイムスタンプフィールドは date 型のみ
text フィールド非対応EQLはtext型フィールドを直接検索できない
nested フィールドの制限ネストされたフィールドはタイムスタンプ/カテゴリに使用不可
フィールド間比較不可2つのフィールドの値を直接比較できない
サンプルクエリでのパイプ不可サンプルクエリではパイプがサポートされない
シングルクォート非対応文字列はダブルクォートのみ

10.3 クエリ設計のベストプラクティス

  1. ECSに準拠する: フィールド名をECSに合わせることで、クエリの互換性と再利用性を最大化
  2. シーケンスにはmaxspanを必ず設定: パフォーマンスとメモリ使用量の制御
  3. byキーワードで結合: 誤検出を減らし、パフォーマンスを向上させる
  4. filterで事前絞り込み: Query DSLフィルタで時間範囲やホストを事前に限定
  5. sizeの適切な設定: 必要以上の結果を返さない
  6. 大規模検索は非同期で: タイムアウトを防ぐ

11. まとめ

11.1 EQLの強み

強み説明
シーケンス検出複数イベント間の時系列パターンをネイティブに検出
セキュリティ特化MITRE ATT&CKフレームワークとの親和性
低学習コストSQL風の直感的な構文
サンプル相関時系列に依存しないイベントの相関
クロスクラスタ複数クラスタを横断した検索
非同期検索大規模データセットへの対応

11.2 適用場面ガイド

シナリオ推奨言語
攻撃チェーンの検出(複数ステップ)EQL
IOC(侵害の痕跡)の検索EQL または Query DSL
ログのアドホック分析ES|QL
アプリケーション組み込みの検索Query DSL
ダッシュボードのフィルタリングKQL

11.3 参考リソース