一覧SP-14-1 · SSPM:SaaS設定の継続評価

SaaSのセキュリティ設定の評価

設定の評価に先立って、取得の完全性と権限不足を確認します。

説明

SSPMはSaaSの設定や権限を取得し、組織の基準から外れた状態を見つけます。APIの取得権限やページングに抜けがあると、未取得を正常と誤判定するおそれがあります。

Graph型のページングを持つ固定API資料を使い、取得した設定をSQLへ保存します。前回との差を調べた後、取得できていない項目を別に抽出します。

設定の評価に先立って、取得の完全性と権限不足を確認します。

1先頭ページと継続先を読む

valueには設定行、nextLinkには次ページの場所があります。

from sp14 import * from sp02 import show db=connect() print(PAGES先頭ページと継続先を読む[0])

先頭にはa1とa2だけです。2行取得できたことは全件取得の証拠ではありません。

2先頭ページだけを保存する

idとmfaの真偽値を保存します。

db.executemany先頭ページだけを保存する("INSERT INTO current VALUES (?,?)",[(x['id'],int(x['mfa'])) for x in PAGES[0]['value']]) show(db,"SELECT * FROM current ORDER BY id")

a1は1、a2は0です。まだa3は取り込まれていません。

3継続ページを取り込む

最後のページにはnextLinkがありません。

print(PAGES[1]) db.executemany継続ページを取り込む("INSERT INTO current VALUES (?,?)",[(x['id'],int(x['mfa'])) for x in PAGES[1]['value']]) show(db,"SELECT COUNT(*) AS fetched FROM current")

3行になりました。継続リンクはサーバーが返した形を使い、クライアント側で適当にページ番号を推測しません。

4前回から変わった設定を抽出する

同じIDが両方にあるものだけを比較します。

show(db,"SELECT p.id,p.mfa AS before,c.mfa AS after FROM previous p JOIN current c USING(id) WHERE前回から変わった設定を抽出する p.mfa<>c.mfa")

a2が1から0へ変わっています。今回の基準はmfa=1なので、この変更が調査対象です。

5取得できていない項目を抽出する

前回にはあり今回にないIDを探します。

show(db,"SELECT p.id,c.mfa FROM previous p LEFT JOIN取得できていない項目を抽出する current c USING(id) WHERE c.id IS NULL")

a4はNULLです。削除されたのか、権限や取得対象が変わったのかを追加確認する必要があります。

6権限不足の応答を空の正常結果にしない

APIの固定エラー応答を開きます。

print(ERROR権限不足の応答を空の正常結果にしない['error']['code']) print('value' in ERROR)

Authorization_RequestDeniedでvalueがありません。正常な0件応答に変換せず、取得失敗として扱います。

7不適合と未取得を別々に表示する

0という値とNULLを別の状態へ分類します。

show(db,"SELECT p.id,CASE不適合と未取得を別々に表示する WHEN c.id IS NULL THEN 'not_fetched' WHEN c.mfa=0 THEN 'fails_baseline' ELSE 'passes_baseline' END AS status FROM previous p LEFT JOIN current c USING(id) ORDER BY p.id")

a2は基準不適合、a4は未取得です。未取得を合格件数へ含めると評価を過大に見せてしまいます。

まとめ

継続ページと取得権限を確認します。

前回との差と未取得を分けます。

不明な値を基準適合へ変換しません。

操作履歴と解説を振り返れます。

Python 3.15 / SQLite / 固定の主体・権限・操作記録

1
from sp14 import * from sp02 import show db=connect() print(PAGES[0])
{'value': [{'id': 'a1', 'mfa': True}, {'id': 'a2', 'mfa': False}], '@odata.nextLink': 'https://graph.example.test/settings?page=2'}
2
db.executemany("INSERT INTO current VALUES (?,?)",[(x['id'],int(x['mfa'])) for x in PAGES[0]['value']]) show(db,"SELECT * FROM current ORDER BY id")
id | mfa
a1 | 1
a2 | 0
3
print(PAGES[1]) db.executemany("INSERT INTO current VALUES (?,?)",[(x['id'],int(x['mfa'])) for x in PAGES[1]['value']]) show(db,"SELECT COUNT(*) AS fetched FROM current")
{'value': [{'id': 'a3', 'mfa': True}]}
fetched
3
4
show(db,"SELECT p.id,p.mfa AS before,c.mfa AS after FROM previous p JOIN current c USING(id) WHERE p.mfa<>c.mfa")
id | before | after
a2 | 1 | 0
5
show(db,"SELECT p.id,c.mfa FROM previous p LEFT JOIN current c USING(id) WHERE c.id IS NULL")
id | mfa
a4 | NULL
6
print(ERROR['error']['code']) print('value' in ERROR)
Authorization_RequestDenied
False
7
show(db,"SELECT p.id,CASE WHEN c.id IS NULL THEN 'not_fetched' WHEN c.mfa=0 THEN 'fails_baseline' ELSE 'passes_baseline' END AS status FROM previous p LEFT JOIN current c USING(id) ORDER BY p.id")
id | status
a1 | passes_baseline
a2 | fails_baseline
a3 | passes_baseline
a4 | not_fetched

演習完了