一覧SC-01-3 · 守る対象とセキュリティ特性

セキュリティ対策の種類と役割

対策の有無だけでなく、いつ何を止めたか、何がすでに起きたかを数量と状態で説明します。

説明

予防は操作を成立させないための対策、検知は起きた事象を把握するための対策です。警告が出るだけでは、その後の処理は止まりません。封じ込めで被害の拡大を抑え、復元で業務状態を戻す工程も分けて考えます。

時刻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 / 固定業務・監査資料

1
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)]
2
print(db.execute("SELECT SUM(records) FROM events WHERE kind='download'").fetchone())
(500,)
3
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,)
4
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)]
5
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)]
6
print(db.execute("SELECT l.id,l.value,b.value FROM live l JOIN backup b USING(id)").fetchall())
[(1, 'changed', 'original')]
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())
[(1, 'original')]
(200,)

演習完了