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と要求
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
print(lab.request())
print(lab.state())(403, {'error': 'csrf'})
[('alice', 100), ('bob', 100)]print(lab.request(body="amount=10&csrf="+b))
print(lab.state())(403, {'error': 'csrf'})
[('alice', 100), ('bob', 100)]print(lab.request(body="amount=10&csrf="+a))
print(lab.state())(200, {'subject': 'alice', 'amount': 90})
[('alice', 90), ('bob', 100)]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)]print(lab.request(session="bob",body="amount=10&csrf="+b))
print(lab.state())(200, {'subject': 'bob', 'amount': 90})
[('alice', 90), ('bob', 90)]print(lab.request(body="amount=10&csrf="+a))
print(lab.state())
lab.close()(200, {'subject': 'alice', 'amount': 80})
[('alice', 80), ('bob', 90)]演習完了