バックアップからの復旧と業務再開
最初に観測した実行より前のb1と、後のb2を比較します。最初の観測時刻を侵入開始そのものと決めつけず、復旧の判断に必要な追加検査と監視の欠落を確認します。
説明
バックアップのハッシュが一致しても、その内容が侵害前の正常な状態とは限りません。復旧候補の時点、内容の検査、業務データ、再発を観測する仕組みをそれぞれ確認します。
最初に観測した実行より前のb1と、後のb2を比較します。最初の観測時刻を侵入開始そのものと決めつけず、復旧の判断に必要な追加検査と監視の欠落を確認します。
1バックアップ候補を比較する
hash_okは保管後の同一性確認です。malware_checkは別の内容検査です。
from oplab import *
db=connect(15)
show(db, 'SELECTバックアップ候補を比較する * FROM backups ORDER BY t')どちらもハッシュは一致していますが、b2の検査にはflaggedがあります。同一性と安全性は別の条件です。
2観測した事案の時刻を確認する
first observed executionは取得資料内の最初の観測であり、真の侵入開始時刻とは限りません。
show(db, 'SELECT観測した事案の時刻を確認する * FROM incident ORDER BY t')実行の観測は20、封じ込めは30です。それ以前の資料が欠けていれば、侵入はもっと前だった可能性が残ります。
3観測前の候補を絞る
ハッシュ照合済みで、最初の観測より前の候補を出します。
show(db, 'SELECT観測前の候補を絞る b.id,b.t,b.malware_check FROM backups b WHERE b.hash_ok=1 AND b.t<(SELECT MIN(t) FROM incident WHERE action="first observed execution")')b1が候補です。時刻だけでは正常な復旧点と確定せず、起動設定やファイル内容などを検査します。
4選んだバックアップを別のデータベースへ復旧する
b1のデータベースファイルを読み込み、別の空のSQLiteデータベースへbackup APIでコピーします。復旧先の検査は、このコピーに対して行います。
import sqlite3
backup=sqlite3.connect(":memory:")
backup.deserialize((ROOT/"OP-15/backup-b1.sqlite").read_bytes())
restored=sqlite3.connect(":memory:")
backup.backup選んだバックアップを別のデータベースへ復旧する(restored)
print(restored.execute("PRAGMA integrity_check").fetchone()[0])okはSQLiteの構造整合性検査に合格した結果です。正常な業務データか、侵害を含まないかは別の検査になります。
5復旧先の業務データを確認する
ordersの件数と金額を読み、b1の業務確認値である2件・合計300と照合します。
print(restored.execute("SELECT id,amount FROM orders ORDER BY id").fetchall())
print(restored.execute("SELECT COUNT(*),SUM(amount)復旧先の業務データを確認する FROM orders").fetchone())
backup.close()
restored.close()コピー先で2行と合計300を確認できました。ファイルが開けたことに加え、業務で使う値まで確認して復旧を評価します。
6復旧候補の検査結果を読む
バックアップIDを合わせ、どの候補に対する確認かを明示します。
show(db, 'SELECT復旧候補の検査結果を読む check_name,result FROM checks WHERE backup="b1" ORDER BY id')起動設定、業務クエリー、センサーの確認があります。各検査が対象にした範囲を越えて、全て安全と広げないようにします。
7復旧後に観測できるログを調べる
監視のenabledと最終到着時刻を確認します。
show(db, 'SELECT復旧後に観測できるログを調べる * FROM monitoring ORDER BY source')プロセスは有効ですが通信ログは無効です。センサーのheartbeatがあっても、全ログ種別を取得できているとは限りません。
8監視の未充足を抽出する
時刻40時点で、無効または5秒を超えて更新されていない種類を探します。
show(db, 'SELECT監視の未充足を抽出する source,enabled,40-last_seen AS age FROM monitoring WHERE enabled=0 OR last_seen<35')networkが残ります。復旧したサービスの業務確認に加え、再発を確認できる観測条件をそろえて判断します。
まとめ
networkが残ります。復旧したサービスの業務確認に加え、再発を確認できる観測条件をそろえて判断します。
表示された証拠IDと検索条件を残すと、調査結果を元の記録までたどれます。
操作履歴と解説を振り返れます。
Python 3.15 / SQLite・固定の調査記録
from oplab import *
db=connect(15)
show(db, 'SELECT * FROM backups ORDER BY t')id | t | hash_ok | malware_check | orders b1 | 10 | 1 | not_tested | 2 b2 | 25 | 1 | flagged | 3
show(db, 'SELECT * FROM incident ORDER BY t')id | t | action i1 | 20 | first observed execution i2 | 30 | contained
show(db, 'SELECT b.id,b.t,b.malware_check FROM backups b WHERE b.hash_ok=1 AND b.t<(SELECT MIN(t) FROM incident WHERE action="first observed execution")')id | t | malware_check b1 | 10 | not_tested
import sqlite3
backup=sqlite3.connect(":memory:")
backup.deserialize((ROOT/"OP-15/backup-b1.sqlite").read_bytes())
restored=sqlite3.connect(":memory:")
backup.backup(restored)
print(restored.execute("PRAGMA integrity_check").fetchone()[0])ok
print(restored.execute("SELECT id,amount FROM orders ORDER BY id").fetchall())
print(restored.execute("SELECT COUNT(*),SUM(amount) FROM orders").fetchone())
backup.close()
restored.close()[(1, 100), (2, 200)] (2, 300)
show(db, 'SELECT check_name,result FROM checks WHERE backup="b1" ORDER BY id')check_name | result startup paths | clean business query | ok sensor heartbeat | ok
show(db, 'SELECT * FROM monitoring ORDER BY source')source | enabled | last_seen network | 0 | 20 process | 1 | 40
show(db, 'SELECT source,enabled,40-last_seen AS age FROM monitoring WHERE enabled=0 OR last_seen<35')source | enabled | age network | 0 | 20
演習完了