セキュリティ対策の種類と役割
対策の有無だけでなく、いつ何を止めたか、何がすでに起きたかを数量と状態で説明します。
説明
予防は操作を成立させないための対策、検知は起きた事象を把握するための対策です。警告が出るだけでは、その後の処理は止まりません。封じ込めで被害の拡大を抑え、復元で業務状態を戻す工程も分けて考えます。
時刻1〜5に100件ずつ取得される固定の時系列を使います。警告だけ、時刻3から拒否、開始前に拒否という条件を比較し、最後に変更されたデータをバックアップから戻します。
対策の有無だけでなく、いつ何を止めたか、何がすでに起きたかを数量と状態で説明します。
1固定の事案を読む
時刻0にログインし、1から5まで各100件を取得するシナリオです。時刻の単位はこの資料内の順序付き区間です。
from sc01 import connect
db = connect("response")固定の事案と対策条件を用意する
print(db.execute("SELECT * FROM events ORDER BY t事象を時刻順に読む").fetchall())ダウンロードは5回あります。対策の評価はこの同じ時系列に対して行います。
2対策がない場合の取得件数
downloadのrecordsを合計します。
print(db.execute("SELECT SUM(records)取得件数を合計する FROM events WHERE kind='download'ダウンロードの記録に絞る").fetchone())合計500件です。これは固定シナリオの件数であり、観測していないデータ量を推測して足してはいません。
3警告だけを出す対策
detectは時刻2に警告を出しますが、deny_atがNULLなので処理を拒否しません。
print(db.execute("SELECT * FROM controls検知と拒否の時刻を読む WHERE name='detect'").fetchone())
print(db.execute("SELECT SUM(e.records)拒否されなかった取得件数を計算する FROM events e JOIN controls c ON c.name='detect' WHERE c.deny_at IS NULL OR e.t<c.deny_at").fetchone())警告時刻2でも、取得は500件です。検知の記録があることを、被害を防いだ証拠として扱えません。
4時刻3から拒否する対策
containは時刻2に警告し、時刻3以降を拒否します。ちょうど時刻3の要求も拒否対象です。
print(db.execute("SELECT e.t,e.records FROM events e JOIN controls c ON c.name='contain' WHERE e.kind='download' AND e.t<c.deny_at拒否開始より前の事象だけを選ぶ ORDER BY e.t").fetchall())残るのは時刻1と2の各100件です。応答までに起きた取得を、後からなかったことにはできません。
5各対策の取得件数を比較する
同じ集計で4条件を比較します。
print(db.execute("SELECT c.name,COALESCE(SUM(e.records),0)該当する取得がない場合も0として集計する FROM controls c LEFT JOIN events e対策の拒否開始前の事象を対応付ける ON c.deny_at IS NULL OR e.t<c.deny_at GROUP BY c.name ORDER BY c.name").fetchall())containは200、detectとnoneは500、preventは0です。予防と検知を単に「対策あり」とまとめると、この違いを失います。
6バックアップと現在値を比較する
現在の値はchanged、バックアップはoriginalです。復元する前に対象IDと値を照合します。
print(db.execute("SELECT l.id,l.value,b.value現在値と復元元を同じIDで比較する FROM live l JOIN backup b USING(id)").fetchall())同じID1で値が異なります。何をどの時点の値へ戻すのかが確認できました。
7業務データを復元する
バックアップの値へ戻し、取得済み件数も再確認します。
db.execute("UPDATE live SET value=バックアップの値へ戻す(SELECT value FROM backup WHERE backup.id=live.id)")
print(db.execute("SELECT * FROM live復元後の状態を読む").fetchall())
print(db.execute("SELECT SUM(records)封じ込め前の取得件数を再確認する FROM events WHERE kind='download' AND t<3").fetchone())現在値はoriginalへ戻りますが、封じ込め前の200件は取得済みです。完全性の復元と機密性の被害への対応は同じ処理ではありません。
まとめ
警告だけでは、後続の処理が止まるとは限りません。
拒否を開始した時刻によって、残る被害の範囲が変わります。
データを復元しても取得済みの情報を回収したことにはなりません。
操作履歴と解説を振り返れます。
Python 3.15 / SQLite / 固定業務・監査資料
from sc01 import connect
db = connect("response")
print(db.execute("SELECT * FROM events ORDER BY t").fetchall())[(0, 'login', 0), (1, 'download', 100), (2, 'download', 100), (3, 'download', 100), (4, 'download', 100), (5, 'download', 100)]
print(db.execute("SELECT SUM(records) FROM events WHERE kind='download'").fetchone())(500,)
print(db.execute("SELECT * FROM controls WHERE name='detect'").fetchone())
print(db.execute("SELECT SUM(e.records) FROM events e JOIN controls c ON c.name='detect' WHERE c.deny_at IS NULL OR e.t<c.deny_at").fetchone())('detect', None, 2)
(500,)print(db.execute("SELECT e.t,e.records FROM events e JOIN controls c ON c.name='contain' WHERE e.kind='download' AND e.t<c.deny_at ORDER BY e.t").fetchall())[(1, 100), (2, 100)]
print(db.execute("SELECT c.name,COALESCE(SUM(e.records),0) FROM controls c LEFT JOIN events e ON c.deny_at IS NULL OR e.t<c.deny_at GROUP BY c.name ORDER BY c.name").fetchall())[('contain', 200), ('detect', 500), ('none', 500), ('prevent', 0)]print(db.execute("SELECT l.id,l.value,b.value FROM live l JOIN backup b USING(id)").fetchall())[(1, 'changed', 'original')]
db.execute("UPDATE live SET value=(SELECT value FROM backup WHERE backup.id=live.id)")
print(db.execute("SELECT * FROM live").fetchall())
print(db.execute("SELECT SUM(records) FROM events WHERE kind='download' AND t<3").fetchone())[(1, 'original')] (200,)
演習完了