一覧SD-04-3 · CSRF対策の実装

リクエストの送信元の検証

要求元の信号を使う防御の範囲と、認証やトークン検証が別に必要な理由を説明します。

説明

ブラウザが付ける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と要求

1
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
2
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)]
3
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)]
4
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)]
5
print(lab.request(origin="null",referer="same")) print(lab.state())
(403, {'error': 'origin'})
[('alice', 80), ('bob', 100)]
6
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)]
7
print(lab.request(origin="same",site="same-origin")) print(lab.state()) lab.close()
(200, {'subject': 'alice', 'amount': 70})
[('alice', 70), ('bob', 100)]

演習完了