依存パッケージの検証と脆弱性管理
構成の同定と影響の調査を分け、確認できた範囲に沿って更新対象を決めます。
説明
依存ライブラリを調べるときは、名前だけでなく取得元、版、実際のアーカイブを対応付けます。構成一覧に載っていることと、問題のある処理がアプリから呼ばれることも別の情報です。
教材用example-parserの固定アーカイブと構成表を使います。ハッシュ、依存関係、仮のsample-advisory、呼出し経路を順に照合し、更新後の版まで追います。
構成の同定と影響の調査を分け、確認できた範囲に沿って更新対象を決めます。
1名前が同じでも取得元が異なる構成を読む
p1とp2は同名・同版ですが取得元が違う試料です。advisoryも教材用の仮の資料です。
from sd14 import *
db = inventory()
print(db.execute("SELECT * FROM packages取得元を含む構成情報を読む ORDER BY id").fetchall())安定IDで取得元まで識別します。パッケージ名だけを結合キーにすると、別の対象へ情報を誤って付ける場合があります。
2固定アーカイブの中身とダイジェストを確認する
版1.0.0を持つ小さなtarを作り、そのバイト列を固定値として扱います。
data = artifact("1.0.0")
locked = hashlib.sha256実アーカイブの同一性を計算する(data).hexdigest()
with tarfile.open(fileobj=io.BytesIO(data)) as tar:
print(tar.getnames())
print(tar.extractfile展開実行せず内部の版を読む("package/version.txt").read())
print(locked)アーカイブの版とSHA-256を確認できます。ハッシュは照合する固定値を信頼できる場所で管理して初めて、差替え検出に使えます。
3一バイト変わったアーカイブを検出する
受領データの末尾1バイトを変えて、固定した値と比較します。
changed = data[:-1]+bytes([data[-1]^1固定資料の1ビットを変える])
print(hashlib.sha256(changed).hexdigest()==locked)
print(hashlib.sha256(data).hexdigest()==locked)変更版はFalse、元はTrueです。版名が同じでも、バイト列が変われば検出できます。
4実際に採用している依存へ情報を結合する
web-appが使うのはp1です。
print(db.execute("SELECT p.name,p.version,p.source,n.id FROM edges e JOIN packages採用中の安定IDへ結合する p ON p.id=e.child LEFT JOIN notices n ON n.package=p.id AND n.version=p.version WHERE e.parent='p3'").fetchall())sample-advisoryはp1にだけ対応します。同名のp2や、採用していない構成を混ぜずに調査します。
5問題の入口が呼ばれるか経路を調べる
仮資料の対象入口はlegacy_decodeです。現在の呼出し図はrequest→parse→decodeです。
print(db.execute("SELECT * FROM calls").fetchall())
print(db.execute("WITH RECURSIVE入口から辿れる関数を列挙する path(fn) AS (SELECT 'request' UNION SELECT c.callee FROM calls c JOIN path p ON c.caller=p.fn) SELECT fn FROM path").fetchall())現在の固定図にはlegacy_decodeがありません。ただし、動的呼出しや未収集経路があれば、この図だけで到達不能を断定できません。
6呼出しを追加すると評価が変わることを確認する
追加機能がlegacy_decodeを呼ぶケースを反映します。
db.execute("INSERT INTO calls新機能の呼出し辺を加える VALUES('parse','legacy_decode')")
print(db.execute("WITH RECURSIVE path(fn) AS (SELECT 'request' UNION SELECT c.callee FROM calls c JOIN path p ON c.caller=p.fn) SELECT fn FROM path WHERE fn='legacy_decode'").fetchall())対象入口が列挙されます。依存の版が変わらなくても、呼出し方の変更で調査結果が変わり得ます。
7更新版の同一性と構成情報を更新する
版1.0.1は、この仮資料の影響版1.0.0とは異なります。
updated = artifact("1.0.1")更新版アーカイブを作る
print(hashlib.sha256(updated).hexdigest()!=locked)
db.execute("UPDATE packages実際に採用した版を記録する SET version='1.0.1' WHERE id='p1'")
print(db.execute("SELECT p.id,n.id FROM packages p LEFT JOIN notices n ON n.package=p.id AND n.version=p.version WHERE p.id='p1'").fetchall())ハッシュが変わり、この仮資料との版一致はなくなります。実運用では公式の影響範囲、修正情報、アプリの回帰結果も併せて確認します。
まとめ
名前・版・取得元・実バイト列を対応付けます。
構成の存在と問題処理への到達を分けて調べます。
更新後は構成表と回帰検査を更新します。
操作履歴と解説を振り返れます。
Python 3.15 / 固定の公開試料
from sd14 import *
db = inventory()
print(db.execute("SELECT * FROM packages ORDER BY id").fetchall())[('p1', 'example-parser', '1.0.0', 'https://packages.example.test'), ('p2', 'example-parser', '1.0.0', 'https://other.example.test'), ('p3', 'web-app', '2.0.0', 'local')]data = artifact("1.0.0")
locked = hashlib.sha256(data).hexdigest()
with tarfile.open(fileobj=io.BytesIO(data)) as tar:
print(tar.getnames())
print(tar.extractfile("package/version.txt").read())
print(locked)['package/version.txt', 'package/parser.py'] b'1.0.0' e849b95b8a7bd0d18300a3443aafb63fb743b0676ee1f68d314753d60c12f550
changed = data[:-1]+bytes([data[-1]^1])
print(hashlib.sha256(changed).hexdigest()==locked)
print(hashlib.sha256(data).hexdigest()==locked)False True
print(db.execute("SELECT p.name,p.version,p.source,n.id FROM edges e JOIN packages p ON p.id=e.child LEFT JOIN notices n ON n.package=p.id AND n.version=p.version WHERE e.parent='p3'").fetchall())[('example-parser', '1.0.0', 'https://packages.example.test', 'sample-advisory')]print(db.execute("SELECT * FROM calls").fetchall())
print(db.execute("WITH RECURSIVE path(fn) AS (SELECT 'request' UNION SELECT c.callee FROM calls c JOIN path p ON c.caller=p.fn) SELECT fn FROM path").fetchall())[('request', 'parse'), ('parse', 'decode')]
[('request',), ('parse',), ('decode',)]db.execute("INSERT INTO calls VALUES('parse','legacy_decode')")
print(db.execute("WITH RECURSIVE path(fn) AS (SELECT 'request' UNION SELECT c.callee FROM calls c JOIN path p ON c.caller=p.fn) SELECT fn FROM path WHERE fn='legacy_decode'").fetchall())[('legacy_decode',)]updated = artifact("1.0.1")
print(hashlib.sha256(updated).hexdigest()!=locked)
db.execute("UPDATE packages SET version='1.0.1' WHERE id='p1'")
print(db.execute("SELECT p.id,n.id FROM packages p LEFT JOIN notices n ON n.package=p.id AND n.version=p.version WHERE p.id='p1'").fetchall())True
[('p1', None)]演習完了