はじめに
QRadar SOARでは、あらゆる処理が「ケース」をベースに行われます。ケースはインシデントやチケットと呼ばれることもあります。
そのため、ケースを作成するという作業は、様々な自動化や連携の起点となる、非常に重要な処理となります。
ケースの作成方法には、いくつかの種類があります。
- Web UIからウィザードで手動作成
- Eメール受信をトリガーとして自動作成
- QRadar SuiteのUnified Analyst Experience (UAX)が持つアラート収集機能やThreat Intelligence Insights(TII:X-Force脅威インテリジェンスとログのマッチング機能)から自動作成
- EDRやSIEMなどのアドオン/プラグイン/アプリ等による自動作成
- カスタム・スクリプトなど外部からREST API経由で自動作成
今回ご紹介するのは、上記のいずれでもない、QRadar SOARの「内部」からケースを自動作成する方法です。具体的には、特定のケースを処理しているプレイブックから、新たに別のケースを作成するようなユースケースになります。
複数のケースをバッチ生成したい場合、通常は「カスタム・スクリプトなど外部からREST API経由で自動作成」する方法が容易です。しかし、SOARシステムの外部にスクリプト実行用のサーバー等を持ちたくない場合、同様の処理をプレイブックとしてSOAR内部に実装したいことがあります。
具体的な例を使って、ケースを自動作成する方法を見ていきましょう。
トップに戻る
自動ケース作成プレイブック
それでは、SOAR「内部」から自動でケースを作成するプレイブックの概要を見ていきましょう。
プレイブックの仕様
ケース作成データの取得
デモ用のダミー・ケースを作成したいなどの特殊な場合を除き、ケース作成には関連するデータが必要です。
例えば、以下のような情報が必要になります:
- ケースの名前
- ケースの説明
- 検出日
- 関連するアーティファクト(IPアドレスやハッシュ値などのIOC情報)
これらの情報源は何でしょうか。
一般的なユースケース、例えばEDRやSIEMがアラートを検知した場合は、専用のアドオン/プラグイン/アプリなどを利用することで、QRadar SOARのケースを作成できます。わざわざ今回紹介するような方法でケースを作成する必要はありませんし、ケース作成に必要な関連データも自動的にセットされます。
しかし、例えば脅威インテリジェンスからもたらされるIOC情報を、夜間バッチでSIEMのログとマッチングさせるようなカスタム処理があった場合、SIEMのリアルタイム検知ルールとは異なるユースケースであるため、通常のアラート発報の仕組みは動作しません。この場合に得られるのは、ログにマッチしたIOCのリストだけです。
このIOCのリストをSOAR上で分析したいと考えた場合も、やはりケースを作成する必要があります。
今回のサンプルでは、ケース作成に使用するデータはダミーの値となっています。(今回お伝えしたいポイントではないため、データ取得のロジック部分は割愛しています)
しかし、SOARに用意された各種のアプリを利用することで、SIEMなど外部から「ログにマッチしたIOCのリスト」などのデータを取り込むことができますので、プレイブックの中で様々な統合を実現することができます。
トップに戻る
複数のケースを作成する処理
自動的に複数のケースを作成する処理は、2つの重要な部分から構成されています。
- ループ処理
一般的なプレイブックは分岐を持つフローチャートのような形をしていますが、多数のケースを次々に生成する今回のユースケースでは、ループを回す必要があります。これを実現するために、プレイブックのプロパティーにカウンターなどの情報を保持します。
- ケース作成処理
SOARの「内部」から別のケースを作成する処理は、通常のスクリプトだけでは実現できません。App Exchangeから入手可能なSOAR Function Utilities for SOARアプリに含まれる「SOAR Utilities: Create Incident」関数を利用します。
トップに戻る
ケース作成後の処理
今回のサンプルでは、ケース作成後に成功・失敗のチェックだけを行っています。
本来であれば、SOARが未調査ケースで溢れてしまわないように、オープン中のケースを計画的にクローズしていく方法を検討する必要があります。特に誤検知の可能性を含む多数のケースをバッチ作成した場合、何らかの自動処理でクローズするロジックを組み込んでおかないと、すぐに管理不能なレベルになってしまいます。
このような自動クローズの実装方法としては、例えばQRadar Suite SOARに含まれる自動調査機能を呼び出して、判定スコアが低かった場合はケースをクローズするなどのアイデアが考えられます。
トップに戻る
プレイブックの全体像
自動ケース作成プレイブックの全体像は以下のとおりです。
この後、上記の構成要素を1つずつ見ていきます。
トップに戻る
プレイブックのトリガー
今回のプレイブックは、サンプルとしてテストしやすいことから、任意のタイミングで呼び出せる「手動」タイプにしています。
実際の運用で使用するプレイブックは、Scheduler for SOARアプリで定期的に呼び出したり、他のプレイブックの動作結果によって「自動」でトリガーされたりするように設計することが多いでしょう。
- アクティブ化のタイプ:手動
- オブジェクト・タイプ:インシデント
- アクティベーション・フォーム:なし
- 条件:なし
- 自動キャンセル:なし
トップに戻る
前処理スクリプト 「入力値」
実際のソリューションであれば、SOARに用意された各種アプリを利用して、SIEMなど外部からデータを取り込むようにプレイブックを設計します。
今回のサンプルではその部分を割愛し、決め打ちの固定値(ダミー)をケース作成データとして使用しています。
このスクリプト内で定義されているdata変数の値がダミーデータに相当しますが、外部から取得した情報から同様の辞書形式データを生成するようにプレイブックを改造すれば、実際のソリューションに置き換えることもできます。
期待される辞書形式は、QRadar SOARのIncidentREST API のリクエスト・ボディーであるFullIncidentDataDTO データタイプと同じです (name
と discovered_date
が必須フィールドです)。ドキュメントには https://<QRadar Suite SOARのベースURL>/api/respond/docs/rest-api/index.html
、https://<スタンドアロン版SOARのベースURL>/docs/rest-api/index.html
、または製品のオンライン・ヘルプ内のリンクからアクセスできます。
以下のサンプルスクリプトを見るだけでも、求められる辞書形式の最低限の構成要素は把握できるかと思います。
from datetime import datetime
now = datetime.now()
inc_discovered_date = int(now.timestamp() * 1000)
data = [
{
"name": "sample incident 1",
"description": "Sample Incident #1",
"discovered_date": inc_discovered_date,
"artifacts": [
{"type": "IP Address", "value": "1.2.3.4"},
{"type": "IP Address", "value": "2.2.2.2"},
{"type": "IP Address", "value": "3.3.3.3"}
]
},
{
"name": "sample incident 2",
"description": "Sample Incident #2",
"discovered_date": inc_discovered_date,
"artifacts": [
{"type": "IP Address", "value": "7.7.7.7"},
{"type": "IP Address", "value": "8.8.8.8"}
]
},
{
"name": "sample incident 3",
"description": "Sample Incident #3",
"discovered_date": inc_discovered_date,
"artifacts": [
{"type": "IP Address", "value": "1.1.1.1"},
{"type": "IP Address", "value": "9.9.9.9"}
]
}
]
# artifacts の type は、以下から選択
# DNS Name, IP Address, URL, Malware MD5 Hash, Malware SHA-1 Hash, Malware SHA-256 Hash
playbook.addProperty("input_data", { "values": data })
トップに戻る
ループ制御のためのスクリプト「Loop Controller」と条件ポイント「ループ終了?」
今のところQRadar SOARのプレイブック・デザイナーには、ループ専用の部品が用意されていないため、スクリプト、条件ポイント、プロパティーを組み合わせてループを実現します。
「プロパティー」を使用すると、パーツ間をまたがってプレイブック内でデータを共有することができますので、ループの実現に必要なカウンター値や処理対象のデータを保存しておくことができます。
以下に、ループ制御に関するロジックを図解します。
※今回は「元データの配列」の合計サイズがさほど大きくないと仮定した作りとなっています。もしデータ量が大きい場合は、外部からのデータ取得を「ループの内側」で「1件ずつ」行うように設計することが現実的です。
実現の鍵となるのは「Loop Controller」というスクリプトで、この中でカウンターの更新(終了判定含む)や次の処理対象データのセットを行っています。
「ループ終了?」という条件ポイントは、「Loop Controller」がセットしたカウンターの値が -1 かどうかをチェックし、その後のフローを分岐させています。
トップに戻る
スクリプト「Loop Controller」
# ループを制御するためのプロパティーの管理
# 全データのリストを取得して件数をセット
input_data = playbook.properties.input_data["values"] if playbook.properties.input_data else []
data_size = len(input_data)
# 既にループカウントがある場合はそれを取得、そうでない場合は初回と判断して 0 をセット
loop_index = int(playbook.properties.loop_index["value"]) if playbook.properties.loop_index else 0
# 今回のループで処理対象となるデータをプロパティーにセット
if loop_index < data_size:
target_data = input_data[loop_index]
loop_index += 1
# 100回以上は無限ループ判定される懸念があるため中断
if loop_index >= 100:
loop_index = -1
else:
target_data = None
loop_index = -1
playbook.addProperty("target_data", { "value": target_data })
playbook.addProperty("loop_index", { "value": loop_index })
トップに戻る
条件ポイント「ループ終了?」
- 条件設定:最初の true 条件
- 分岐1:「ループ終了」
loop_index = int(playbook.properties.loop_index["value"]) if playbook.properties.loop_index else -1
if loop_index >= 0:
result = False
else:
result = True
トップに戻る
関数 「SOAR Utilities: Create Incident」
ループ制御によって、与えられた元データの配列が1回分の辞書データにまで分解されていますので、ここでは素直にその1回分のデータだけを処理します。
import json
target_data = playbook.properties.target_data["value"]
json_str = json.dumps(target_data)
inputs.soar_utils_create_fields = json_str
ケースの作成に必要なすべてのデータ項目が辞書形式の「1回分のデータ」に含まれているため、あとはJSON形式に変換して関数のパラメーターとして渡すだけです。
トップに戻る
後処理スクリプト 「Post Function Note」
直前の関数の戻り値をチェックするスクリプトです。複雑なエラー処理は省いており、単に結果を「注記」に書き込む処理を行っています。
if playbook.functions.results.create_case_result.success:
link = u'<a href="#incidents/{0}">{0}: {1}</a>'.format(playbook.functions.results.create_case_result.content['id'], playbook.functions.results.create_case_result.content['name'])
incident.addNote(helper.createRichText(u"Incident successfully created: {}".format(link)))
else:
incident.addNote(u"Incident creation failed: {}".format(playbook.functions.results.create_case_result.reason))
トップに戻る
関数 「Timer」
ループのペース配分をするためのSleep関数を呼び出します。この関数は、Timer Function for SOAR アプリに含まれています。
この後のフローは、ループ制御用のスクリプト「Loop Controller」につながります。
トップに戻る
終了点
終了点(エンドポイント)は、プレイブック全体の完了、またはプレイブック内の特定のパスの完了を示します。どのプレイブックにも、1つ以上の終了点を配置する必要があります。
このプレイブックでは、条件ポイント「ループ終了?」の判定結果がTrueの場合に、終了点にフローがつながります。
トップに戻る
プレイブックの実行確認
プレイブックが完成したら、実際の動作を確認します。プレイブックを実行するためには、ベースとなる「ケース」が必要ですので、それを先に作成します。ケースは、シミュレーションでも構いません。
作成したケースのメニューから今回作成したプレイブックの名前を選択すると、プレイブックが実行されます。(以下の例では、「Blog: 自動ケース作成」がプレイブックの名前です)
今回のプレイブックは、事前にハードコードされたダミーデータをもとにケースを生成しますので、後は結果を待つだけです。
結果のサマリーは、後処理スクリプト 「Post Function Note」によってケースの「注記」に出力されますので、そこを見れば想定通りかどうかを確認することができます。(以下の例では、3つのケースが作成されています)
「注記」の画面
また、実際に作成された個々のケースにアクセスすると、指定した「名前」と「説明」がセットされ、指定した「アーティファクト」が追加されているかどうかを確認できます。
ケース一覧
各ケースのサマリー画面(1)
各ケースのサマリー画面(2)
各ケースのサマリー画面(3)
トップに戻る
おわりに
今回のサンプルでは、ケースの中から別のケースを作成する方法と、ループ処理、タイマーによるスリープ処理を取り上げました。
ケースのプレイブックの中から別のケースを作成したい場面はそれほど多くないかもしれませんが、例えば外部サービスからのデータ取得失敗時にスリープを入れてリトライするなど、ケース作成に限らずループ処理やスリープ処理を行いたい場面があるかもしれません。このような処理が必要になった際に、思い出していただければ幸いです。
参考文献