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

CSRFトークンによる検証

認証セッションとトークンの対応、失敗時の更新拒否、再送に対する別の対策を説明します。

説明

CSRFトークンは、要求が正しいセッションのページから作られたことを確かめるための情報です。値があるかだけではなく、そのセッションに結び付く値かを検証する必要があります。

固定のセッションIDへHMACで結び付けたトークンを使います。欠落、不一致、他セッションの値、改変を比較し、正しい要求だけ残高が変わることを確認します。

認証セッションとトークンの対応、失敗時の更新拒否、再送に対する別の対策を説明します。

1二つのセッションのトークンを作る

検証用のHMAC鍵で、セッションごとに異なるトークンを作ります。

from sd04 import CSRF lab = CSRF(mode="token")トークン検証を有効にする a = lab.token("alice")aliceのセッションへ結び付ける b = lab.token("bob")bobの値と比較する print(a) print(b) print(a==b)

aliceとbobでは値が異なります。署名付きトークンを作るときも、入力にセッションを識別する情報を含めます。

2トークンなしのCookie付き要求

認証Cookieが正しくても、トークンを付けない場合です。

print(lab.request()トークンを付けずにPOSTする) print(lab.state())

403のcsrfで、両者の残高100を保ちます。認証処理の成功だけで更新へ進んでいません。

3別セッションのトークンを提示する

aliceのCookieへbobのトークンを付けます。

print(lab.request(body="amount=10&csrf="+b別セッションの値を組み合わせる)) print(lab.state())

bob向けに正しく作った値でも、aliceには一致しません。単に既知のトークン集合に含まれるかを調べる実装では不十分です。

4同じセッションのトークンを提示する

aliceのCookieとaliceに結び付くトークンを使います。

print(lab.request(body="amount=10&csrf="+a認証セッションと対応する値を提示する)) print(lab.state())

aliceだけ90になります。トークン検証後のセッションを更新対象へ使っています。

5トークンの1文字を変更する

トークンの先頭を別の16進数字へ変えます。

changed =トークンの一部を変更する ("0" if a[0]!="0" else "1")+a[1:] print(lab.request(body="amount=10&csrf="+changed変更した値を提示する)) print(lab.state())

403でaliceの残高90を保ちます。比較処理には一定時間のcompare_digestを使っています。

6bobの正しい組で要求する

Cookieとトークンを両方bobへ切り替えます。

print(lab.request(session="bob"認証主体をbobへ変える,body="amount=10&csrf="+bbobに対応するトークンを付ける)) print(lab.state())

bobだけ90になります。セッション間で更新先が混ざっていません。

7同じ正当な要求を再送する

この方式のトークンはセッションに結び付く値であり、一回限りの注文IDではありません。

print(lab.request(body="amount=10&csrf="+a)正当な要求をもう一度送る) print(lab.state()) lab.close()

aliceは80になり、再送はもう一度実行されます。CSRF対策とは別に、二重送信を防ぐ要求IDや業務の冪等性を設計します。

まとめ

トークンを認証セッションへ結び付けます。

欠落・不一致を拒否し、更新前の状態を保ちます。

CSRFトークンの照合と業務の重複実行防止は別の役割です。

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

Python 3.15 / HTTP API・SQLite / 固定のCookieと要求

1
from sd04 import CSRF lab = CSRF(mode="token") a = lab.token("alice") b = lab.token("bob") print(a) print(b) print(a==b)
7c74d243e61455ee2eb19475d3a5f3063a03cb48f1d88a11409c41a081b95bd6
d9ba1a211ac674f651554bbb34569c90aa5f847afaa925e6047bc9c6cae95394
False
2
print(lab.request()) print(lab.state())
(403, {'error': 'csrf'})
[('alice', 100), ('bob', 100)]
3
print(lab.request(body="amount=10&csrf="+b)) print(lab.state())
(403, {'error': 'csrf'})
[('alice', 100), ('bob', 100)]
4
print(lab.request(body="amount=10&csrf="+a)) print(lab.state())
(200, {'subject': 'alice', 'amount': 90})
[('alice', 90), ('bob', 100)]
5
changed = ("0" if a[0]!="0" else "1")+a[1:] print(lab.request(body="amount=10&csrf="+changed)) print(lab.state())
(403, {'error': 'csrf'})
[('alice', 90), ('bob', 100)]
6
print(lab.request(session="bob",body="amount=10&csrf="+b)) print(lab.state())
(200, {'subject': 'bob', 'amount': 90})
[('alice', 90), ('bob', 90)]
7
print(lab.request(body="amount=10&csrf="+a)) print(lab.state()) lab.close()
(200, {'subject': 'alice', 'amount': 80})
[('alice', 80), ('bob', 90)]

演習完了