リクエストの送信元の検証
要求元の信号を使う防御の範囲と、認証やトークン検証が別に必要な理由を説明します。
説明
ブラウザが付けるOriginやFetch Metadataは要求元を判断する材料になります。ヘッダーが欠ける場合や値がnullの場合、Refererを補助に使う条件を明示しておく必要があります。
同じ残高APIでOriginの完全一致、Refererの補助、Fetch Metadataの条件を切り替えます。CookieのSameSite属性も読み、同一サイトと同一オリジンの違いを整理します。
要求元の信号を使う防御の範囲と、認証やトークン検証が別に必要な理由を説明します。
1Cookie属性の役割を読む
SameSiteはブラウザのCookie送信条件です。HttpOnlyはJavaScriptからのCookie読取りを制限します。どちらもサーバーの操作権限表ではありません。
from sd04 import CSRF,SimpleCookie,urlsplit
lab = CSRF(mode="origin")
cookie = SimpleCookie()
cookie.loadCookie属性を解析する("sid=alice; Path=/; HttpOnly; SameSite=Lax")
print(cookie["sid"]["httponly"],cookie["sid"]["samesite"]SameSite値を読む)HttpOnly=True、SameSite=Laxです。サイトの判定にはschemeと登録可能ドメインなどを使い、オリジンのhost・portとの一致とは異なります。
2他オリジンと同一オリジンを比較する
Originはscheme・host・portの組として、期待する値と完全一致させます。
print(lab.request(origin="cross"他オリジンの要求を送る))
print(lab.request(origin="same"期待するオリジンへそろえる))
print(lab.state())他オリジンは403、同一ならaliceが90です。文字列の末尾一致だけで似た名前を許可しないようにします。
3見た目が似た名前を拒否する
期待するホスト名を接頭辞に含むだけの別名を提示します。
parts = urlsplit(lab.origin)
fake =期待するホスト名を含む別のOriginを作る parts.scheme+"://"+parts.hostname+".other.example.test:"+str(parts.port)
print(lab.request(origin=fake))
print(lab.state())403で残高90を保ちます。部分一致による許可とは違う結果です。
4Origin欠落時だけRefererを補助にする
この例の方針では、Originがない場合に限りRefererからschemeとhost・portを取り出します。両方なければ拒否します。
print(lab.request(origin=None,referer="same"Origin欠落時の補助情報を使う))
print(lab.request(origin=None,referer=None両方欠落した場合を確認する))
print(lab.state())同一Refererの要求だけ受け入れて80です。Refererが欠ける環境もあるため、欠落時の方針を明確にします。
5Origin nullを欠落と同一視しない
nullというOriginが存在する場合です。同一Refererを付けても、この例ではOriginの不一致を優先します。
print(lab.request(origin="null"null Originの扱いを確認する,referer="same"))
print(lab.state())403で80を保ちます。明示的なOriginと欠落を混同して、別のヘッダーで拒否を上書きしていません。
6Fetch Metadataの方針を比較する
検証をmetadataへ切り替え、この例では状態変更にsame-originだけを許します。same-siteは別オリジンを含み得ます。
lab.mode = "metadata"Fetch Metadata検証へ切り替える
print(lab.request(site="cross-site"))
print(lab.request(site="same-site"同一サイトだが別オリジンの場合を比較する))
print(lab.request(site=None情報欠落の扱いを確認する))
print(lab.state())すべて403で80を保ちます。欠落を許す別方針を採る場合は、CSRFトークンなど他の検証と組み合わせる必要があります。
7同一オリジンの信号を受け入れる
same-originの値を送って更新します。手動HTTPクライアントはこれらのヘッダーも設定できるため、信号自体を認証情報として扱いません。
print(lab.request(origin="same",site="same-origin"期待する要求元の分類を提示する))
print(lab.state())
lab.close()aliceは70です。ブラウザからの意図しない要求を制限する仕組みと、認証Cookieの保護・操作権限の検査は併せて必要です。
まとめ
SameSiteと同一オリジンは異なる境界です。
Originの不一致、null、欠落を明示的に扱います。
要求元の信号を認証情報の代わりにせず、トークンや権限検査と組み合わせます。
操作履歴と解説を振り返れます。
Python 3.15 / HTTP API・SQLite / 固定のCookieと要求
from sd04 import CSRF,SimpleCookie,urlsplit
lab = CSRF(mode="origin")
cookie = SimpleCookie()
cookie.load("sid=alice; Path=/; HttpOnly; SameSite=Lax")
print(cookie["sid"]["httponly"],cookie["sid"]["samesite"])True Lax
print(lab.request(origin="cross"))
print(lab.request(origin="same"))
print(lab.state())(403, {'error': 'origin'})
(200, {'subject': 'alice', 'amount': 90})
[('alice', 90), ('bob', 100)]parts = urlsplit(lab.origin)
fake = parts.scheme+"://"+parts.hostname+".other.example.test:"+str(parts.port)
print(lab.request(origin=fake))
print(lab.state())(403, {'error': 'origin'})
[('alice', 90), ('bob', 100)]print(lab.request(origin=None,referer="same"))
print(lab.request(origin=None,referer=None))
print(lab.state())(200, {'subject': 'alice', 'amount': 80})
(403, {'error': 'origin'})
[('alice', 80), ('bob', 100)]print(lab.request(origin="null",referer="same"))
print(lab.state())(403, {'error': 'origin'})
[('alice', 80), ('bob', 100)]lab.mode = "metadata"
print(lab.request(site="cross-site"))
print(lab.request(site="same-site"))
print(lab.request(site=None))
print(lab.state())(403, {'error': 'fetch_metadata'})
(403, {'error': 'fetch_metadata'})
(403, {'error': 'fetch_metadata'})
[('alice', 80), ('bob', 100)]print(lab.request(origin="same",site="same-origin"))
print(lab.state())
lab.close()(200, {'subject': 'alice', 'amount': 70})
[('alice', 70), ('bob', 100)]演習完了