認証ログを使った検知ルール
固定の認証列へ「成功前に失敗が2件」という条件を適用します。集約キーと時間幅を変え、別人の失敗が混ざる例と、短い時間幅では拾えない例を確認します。
説明
検知ルールでは、一連の行動とみなす条件を検索式に記述します。端末だけでまとめるか、利用者の一致も求めるか、何秒以内の失敗を数えるかによって、検知結果が変わります。
固定の認証列へ「成功前に失敗が2件」という条件を適用します。集約キーと時間幅を変え、別人の失敗が混ざる例と、短い時間幅では拾えない例を確認します。
1認証の元記録を読む
tは発生時刻、ingestedは取込時刻です。truthはこの試料を作る際に付けた評価用の正解で、検知条件には使いません。
from oplab import *
db=connect(14)
show(db, 'SELECT認証の元記録を読む id,host,user,t,kind FROM events ORDER BY host,t')ws8の失敗はbobとcarolの別人です。ws9は失敗の間隔が長くなっています。
2端末単位で先行失敗を数える
成功前30秒以内に同じ端末で発生した失敗を数えます。
show(db, 'SELECT端末単位で先行失敗を数える s.id,COUNT(f.id) AS failures FROM events s JOIN events f ON s.host=f.host AND f.kind="failure" AND f.t>=s.t-30 AND f.t<s.t WHERE s.kind="success" GROUP BY s.id HAVING COUNT(f.id)>=2')e3に加えてe6が検知されます。e6では別人carolの失敗がbobの認証列へ混ざっています。
3同じ利用者の条件を加える
端末と利用者を合わせたキーへ変更します。
show(db, 'SELECT同じ利用者の条件を加える s.id,COUNT(f.id) AS failures FROM events s JOIN events f ON s.host=f.host AND s.user=f.user AND f.kind="failure" AND f.t>=s.t-30 AND f.t<s.t WHERE s.kind="success" GROUP BY s.id HAVING COUNT(f.id)>=2')e3だけになります。誤った結合は減りましたが、e9の長い間隔の試料はまだ検知できません。
4時間幅を120秒へ広げる
主体の条件は保ち、時間条件だけを変えます。
show(db, 'SELECT時間幅を120秒へ広げる s.id,COUNT(f.id) AS failures FROM events s JOIN events f ON s.host=f.host AND s.user=f.user AND f.kind="failure" AND f.t>=s.t-120 AND f.t<s.t WHERE s.kind="success" GROUP BY s.id HAVING COUNT(f.id)>=2')e3とe9になります。長い窓は分散した活動を拾いますが、通常の失敗が偶然集まる可能性も別の正常試料で確認します。
5検知の根拠となった行を表示する
件数だけでなく、e9に対応する失敗イベントを出します。
show(db, 'SELECT検知の根拠となった行を表示する s.id AS success,f.id AS failure,f.t,s.t-f.t AS age FROM events s JOIN events f ON s.host=f.host AND s.user=f.user AND f.kind="failure" AND f.t>=s.t-120 AND f.t<s.t WHERE s.id="e9" ORDER BY f.t')e7は110秒前、e8は10秒前です。アラートに根拠の行を残すと、条件が意図どおりか再確認できます。
6取込みの遅れを調べる
発生時刻で集計する検索でも、データが届く前には検知できません。
show(db, 'SELECT取込みの遅れを調べる id,ingested-t AS ingest_delay FROM events WHERE kind="success" ORDER BY id')成功記録は2〜5秒遅れて届きます。実運用では遅着を考慮した再検索や重複通知の扱いも設計対象になります。
まとめ
成功記録は2〜5秒遅れて届きます。実運用では遅着を考慮した再検索や重複通知の扱いも設計対象になります。
表示された証拠IDと検索条件を残すと、調査結果を元の記録までたどれます。
操作履歴と解説を振り返れます。
Python 3.15 / SQLite・固定の調査記録
from oplab import *
db=connect(14)
show(db, 'SELECT id,host,user,t,kind FROM events ORDER BY host,t')id | host | user | t | kind e1 | ws7 | alice | 0 | failure e2 | ws7 | alice | 10 | failure e3 | ws7 | alice | 20 | success e4 | ws8 | bob | 0 | failure e5 | ws8 | carol | 10 | failure e6 | ws8 | bob | 20 | success e7 | ws9 | dave | 0 | failure e8 | ws9 | dave | 100 | failure e9 | ws9 | dave | 110 | success
show(db, 'SELECT s.id,COUNT(f.id) AS failures FROM events s JOIN events f ON s.host=f.host AND f.kind="failure" AND f.t>=s.t-30 AND f.t<s.t WHERE s.kind="success" GROUP BY s.id HAVING COUNT(f.id)>=2')id | failures e3 | 2 e6 | 2
show(db, 'SELECT s.id,COUNT(f.id) AS failures FROM events s JOIN events f ON s.host=f.host AND s.user=f.user AND f.kind="failure" AND f.t>=s.t-30 AND f.t<s.t WHERE s.kind="success" GROUP BY s.id HAVING COUNT(f.id)>=2')id | failures e3 | 2
show(db, 'SELECT s.id,COUNT(f.id) AS failures FROM events s JOIN events f ON s.host=f.host AND s.user=f.user AND f.kind="failure" AND f.t>=s.t-120 AND f.t<s.t WHERE s.kind="success" GROUP BY s.id HAVING COUNT(f.id)>=2')id | failures e3 | 2 e9 | 2
show(db, 'SELECT s.id AS success,f.id AS failure,f.t,s.t-f.t AS age FROM events s JOIN events f ON s.host=f.host AND s.user=f.user AND f.kind="failure" AND f.t>=s.t-120 AND f.t<s.t WHERE s.id="e9" ORDER BY f.t')success | failure | t | age
show(db, 'SELECT id,ingested-t AS ingest_delay FROM events WHERE kind="success" ORDER BY id')id | ingest_delay e3 | 5 e6 | 2 e9 | 5
演習完了