依存パッケージとシークレットの検査
依存と秘密の検出候補について、根拠・対象範囲・再確認の方法を説明します。
説明
SCAは依存する部品と版を調べ、秘密検出は資格情報らしい文字列を探します。どちらも名前や形式への一致だけでは、実際に影響する部品か、有効な秘密かまでは確定しません。
架空パッケージの固定lock資料で版と取得元を照合します。公開のLABKEY試料を検索し、対象外ファイルと誤検知の候補を分けて調べます。
依存と秘密の検出候補について、根拠・対象範囲・再確認の方法を説明します。
1依存版と取得元を開く
同じ名前・版でもregistryが異なる2部品です。
from sp19 import *
print(json.dumps依存版と取得元を開く(LOCK,indent=2))public-registryとinternal-registryです。名前だけで異なる配布元の部品を同一の脆弱性資料へ結び付けません。
2版文字列を単純比較した誤りを確認する
この試料は数字3要素のリリース版に限定します。
print('1.10.0'<'1.9.0')
print(release版文字列を単純比較した誤りを確認する('1.10.0')<release('1.9.0'))文字列比較はTrue、数値要素ではFalseです。実パッケージではエポックやプレリリースを含む版規則に対応した実装を使います。
3取得元と影響範囲を合わせて照合する
教材用の影響条件はpublic-registryのsample-parser、1.0.0以上1.10.0未満とします。
affected=[x for x in LOCK['dependencies'] if x['registry']=='public-registry' and x['name']=='sample-parser' and release取得元と影響範囲を合わせて照合する('1.0.0')<=release(x['version'])<release('1.10.0')]
print(affected)架空の影響条件に該当するのは、公開側の1.9.0だけです。
4更新した依存を同じ条件で確認する
公開側の版を1.10.0へ変更します。
LOCK['dependencies'][0]['version']='1.10.0'
print(release更新した依存を同じ条件で確認する(LOCK['dependencies'][0]['version'])<release('1.10.0'))Falseです。実際の更新ではlockだけでなく、ビルドや配布物に入った版も照合します。
5公開の鍵形式を全ファイルから検索する
LABKEYで始まる文字列はすべて教材用の無効な試料です。
pattern=re.compile(r'LABKEY_[A-Z0-9]{8}')
hits=[(path,m.start(),hashlib.sha256(m.group().encode()).hexdigest()[:12]) for path,text in FILES.items() for m in pattern.finditer公開の鍵形式を全ファイルから検索する(text)]
print(hits)settingsとテスト資料の2件が同じ指紋です。形式一致は候補であり、用途や発行元で有効性を確認します。
6テスト資料を除外した影響を確認する
tests配下を対象外へ変えます。
included=[h for h in hits if not h[0].startswithテスト資料を除外した影響を確認する('tests/')]
print(included)1件だけになります。通知が減った理由が修正なのか除外なのかを記録し、対象外に有効な値が紛れる可能性も評価します。
7ファイル修正と資格情報の失効を分ける
ソース中の試料を環境変数参照へ置き換えて検索します。
FILES['src/settings.py']='key = os.environ["SERVICE_KEY"]\n'
print(pattern.findallファイル修正と資格情報の失効を分ける(FILES['src/settings.py']))
print(pattern.findall(FILES['tests/sample.txt']))ソースでは0件、別ファイルには試料が残ります。実際に漏れた資格情報なら、検索結果から消すだけでなく発行側で失効・更新する必要があります。
まとめ
名前、取得元、版規則を合わせて依存を照合します。
秘密検出の候補と有効性を分けます。
除外・修正・失効は別の状態として確認します。
操作履歴と解説を振り返れます。
Python 3.15 / AST・SQLite・WSGIの固定アプリ
from sp19 import *
print(json.dumps(LOCK,indent=2)){
"name": "public-app",
"version": "1.0.0",
"dependencies": [
{
"name": "sample-parser",
"version": "1.9.0",
"registry": "public-registry"
},
{
"name": "sample-parser",
"version": "1.9.0",
"registry": "internal-registry"
}
]
}print('1.10.0'<'1.9.0')
print(release('1.10.0')<release('1.9.0'))True False
affected=[x for x in LOCK['dependencies'] if x['registry']=='public-registry' and x['name']=='sample-parser' and release('1.0.0')<=release(x['version'])<release('1.10.0')]
print(affected)[{'name': 'sample-parser', 'version': '1.9.0', 'registry': 'public-registry'}]LOCK['dependencies'][0]['version']='1.10.0'
print(release(LOCK['dependencies'][0]['version'])<release('1.10.0'))False
pattern=re.compile(r'LABKEY_[A-Z0-9]{8}')
hits=[(path,m.start(),hashlib.sha256(m.group().encode()).hexdigest()[:12]) for path,text in FILES.items() for m in pattern.finditer(text)]
print(hits)[('src/settings.py', 7, '8e7faa9ba018'), ('tests/sample.txt', 8, '8e7faa9ba018')]included=[h for h in hits if not h[0].startswith('tests/')]
print(included)[('src/settings.py', 7, '8e7faa9ba018')]FILES['src/settings.py']='key = os.environ["SERVICE_KEY"]\n'
print(pattern.findall(FILES['src/settings.py']))
print(pattern.findall(FILES['tests/sample.txt']))[] ['LABKEY_ABCD1234']
演習完了