Arbiter¶
Arbiter は SIEM のデータ分析エンジンです。
SIEM 検出ルールの作成と編集において、Arbiter は記述されたスクリプトを実行し、データを処理してイベントを生成します。Arbiter は一連の組み込み関数を提供します。スクリプトの入出力に関連する関数としては、dql 関数(Guance からデータをクエリする)、trigger 関数(イベントをトリガーする)、printf 関数(情報を標準出力に出力する)などがあります。
クイックスタート¶
今日の新規アクセス IP を昨日と比較する例を以下に示します。スクリプトは次のとおりです。
DQL ステートメント R::`resource`:(distinct(`ip`) as ip) [2d:1d] を使用して、1 日前のユーザーアクセスにおける重複排除された IP データをクエリします。
スクリプトを実行した結果は次のとおりです。
{"series":[[{"columns":{"ip":"120.20.000.79","time":1747041737200},"tags":null},{"columns":{"ip":"120.130.000.85","time":1747031791143},"tags":null},{"columns":{"ip":"153.30.000.2","time":1747030318384},"tags":null}]],"status_code":200}
by ステートメントを使用してグループ化していない場合、DQL クエリ結果の series リストには通常 1 つの要素のみが含まれます(つまり、1 つの時系列のみを含みます)。
IP リストを取得するには、元の結果を処理する必要があります。具体的には、次の 2 つの方法で実現できます。
この関数はすべての時系列を取得し、2 次元リストを返します。フィールドが存在しない場合は、nil がプレースホルダーとして使用されます。
- スクリプト:
result_dql = dql("R::`resource`:(distinct(`ip`) as ip) [2d:1d]")
ips = dql_series_get(result_dql, "ip")
printf("%v", ips)
- 標準出力:
- スクリプト:
result_dql = dql("R::`resource`:(distinct(`ip`) as ip) [2d:1d]")
ips = []
for series in result_dql["series"] {
for elem in series {
# columns には ip フィールドがあり、tags にはないことがわかっている
if "columns" in elem && "ip" in elem["columns"] {
ips = append(ips, elem["columns"]["ip"])
} else {
# dql_series_get 関数では、該当フィールドがない場合、nil がプレースホルダーとして追加される
ips = append(ips, nil)
}
}
}
printf("%v", ips)
- 標準出力:
IP リストを取得したら、今日のデータと昨日の IP リストデータの比較を開始できます。スクリプトの参考例は次のとおりです。
# 昨日の
result_dql = dql("R::`resource`:(distinct(`ip`) as ip) [2d:1d]")
ip_yesterday = dql_series_get(result_dql, "ip")
# 今日の
result_dql = dql("R::`resource`:(distinct(`ip`) as ip) [1d]")
ip_today = dql_series_get(result_dql, "ip")
# len(ip_today) > 0 のチェックを推奨。インデックスが範囲外の場合、実行時にエラーが発生する
new_ips = []
for s in ip_today[0] {
if s == nil {
continue
}
if !(s in ip_yesterday[0]) {
new_ips = append(new_ips, s)
}
}
# 新規 IP 検出イベントをトリガー
trigger(
result=new_ips,
status="info",
dimension_tags={
"user_cron_job_": "new_ips_check",
"data_category": "rum"
},
related_data={
"IPs": new_ips
}
)
trigger 関数は複数回のトリガーをサポートしています。今回は 1 回だけ実行されたため、現在の結果には 1 つの要素のみが含まれています。その結果は、Web ページ上でテンプレート変数として使用できます。たとえば、{{Result}} は以下の result に対応します。
[
{
"result": [
"116.60.000.211",
"14.150.000.99",
"58.240.000.245"
],
"status": "info",
"dimension_tags": {
"data_category": "rum",
"user_cron_job_": "new_ips_check"
},
"related_data": {
"IPs": [
"116.60.000.211",
"14.150.000.99",
"58.240.000.245"
]
}
}
]
ユースケース例¶
異常ログによる地理的位置の変更の特定¶
# クエリ時間範囲は自動的に設定されます。定期トリガーの場合
user_id_data = dql("L::access_log:(distict(user_id) as user_id)")
user_ids = dql_series_get(user_id_data, "user_id")
# user_ids の値は [["user_id_1", "user_id_2", "user_id_3"]] のようになる
user_ids = user_ids[0]
for uid in user_ids {
# ユーザーアクセス国の情報をクエリし、重複を排除
country_data = dql(strfmt(
"L::access_log:(distict(country) as country) {user_id = \"%s\"}", uid))
country_list = dql_series_get(country_data, "country")
# 複数の国がある場合
if len(country_list) == 1 && len(country_list[0]) > 1 {
trigger( # イベントを 1 回トリガー
result=uid,
related_data = country_list[0]
)
}
}
URL アクセス順序の検出¶
# 検出期間内のすべてのセッション ID を取得
session_id_data = dql("R::view:(distinct(session_id) as session_id)")
session_id_list = dql_series_get(session_id_data, "session_id")
session_id_list = session_id_list[0]
for session_id in session_id_list {
# 指定された session_id に対するすべての view_path を取得
view_data = dql(strfmt("R::view:(view_path) {session_id = \"%s\"} order by time asc", session_id))
view_path = dql_series_get(view_data, "view_path")
view_path = view_path[0]
pos_login_api = -1
pos_send_msg_api = -1
for i = 0; i < len(view_path); i++ {
if pos_login_api < 0 && view_path[i] == "/login" {
pos_login_api = i
}
if pos_send_msg_api < 0 && view_path[i] == "/send_message" {
pos_send_msg_api = i
}
if pos_login_api == -1 && pos_send_msg_api >= 0 {
trigger(
result= strfmt("この session(%s) では、login にアクセスする前に send_msg にアクセスしました", session_id)
)
}
# 昇順のため、最初の要素が最も古いアクセス時間
if pos_login_api > pos_send_msg_api {
# イベントをトリガー
trigger(
result= strfmt("この session(%s) では、login が send_msg より後にアクセスされました", session_id)
)
}
}
}
特定の Token が複数の IP からアクセスされている¶
token_data = dql("L::access_log:(distict(token) as token)")
token_list = dql_series_get(token_data, "token")
token_list = token_list[0]
for token in token_list {
token_ip_data = dql(strfmt(
"L::access_log:(distinc(ip) as ip) {token = \"%s\"}", token)
ip_list = dql_series_get(token_ip_data, "ip")
if len(ip_list) == 0 && len(ip_list[0]) > 1 {
# イベントをトリガー
trigger(
result = strfmt("token %s が複数の ip からアクセスされました, IPs: %s", token, str_join(ip_list[0], ", "))
)
}
}
