Redis セッションとセッション固定への防御
目標
Redisをセッションストアとして接続したログインサーバーを立て、ログインの瞬間にセッションIDが再発行されるか、絶対期限がidleと無関係に動作するか、ログアウトが即座に無効化するかを、実際のHTTPリクエストで確認します。
なぜ重要なのか
サーバーセッションは、状態をサーバーが握り、ブラウザーには鍵だけを渡す仕組みです。そのためログアウト1回で即座に無効化できますが、その鍵がログインの前後で変わらないと、他人があらかじめ仕込んだ鍵で自分のログインに便乗するセッション固定が成立します。OWASPは、権限レベルが匿名から認証に変わる瞬間にセッションIDを再発行するよう明確に求めています。また有効期限は、idle(活動がなければ期限切れ、リクエストごとに更新)とabsolute(作成から時間が経ったら無条件に期限切れ)の2つを併用する必要がありますが、Redis TTLだけではabsoluteを表せないため、値の中に作成時刻を別に持たせます。このラボは、それらのルールが実際のリクエストで守られているかを自分で証明させます。
ステップ
/root/st/session/app.pyを127.0.0.1:8302で起動してください。GET /healthは200と{"ok":true}を返します。- セッションCookieが
__Host-sidという名前で、Secure、HttpOnly、SameSite、Path=/を持つかどうかを、/root/st/session/cookie.txtにSet-Cookieヘッダー1行で保存してください。 - 匿名セッションを受け取り、そのIDが
sess:<id>キーでRedisにあり、TTLとuserを持つかどうかを/root/st/session/store.txtに書いてください。 - 匿名セッションIDをそのまま持ってログインすると、レスポンスが別のセッションIDを返し、古いIDが消えるかどうかを
/root/st/session/fixation.txtに書いてください。 - ログインセッションのTTLがidle上限以下で、リクエストによって再び満たされるかどうかを
/root/st/session/idle_ttl.txtに書いてください。 createdを過去にしたセッションが、idle TTLが残っていても認証として認められないかどうかを/root/st/session/absolute.txtに書いてください。- ログアウト後に同じCookieが匿名扱いになり、ストアのキーが消えるかどうかを
/root/st/session/logout.txtに書いてください。 /root/st/session/e2e.shで全体の流れを試し、/root/st/session/e2e.outに4行を残してください。
参考
- セッションIDは
secrets.token_urlsafe(32)で作ります(256ビット、OWASPの最小64ビットを上回ります)。 - idleはリクエストごとに
EXPIREでTTLを延ばしてslidingにし、absoluteは値の中のcreatedで判定します。 - よくある失敗1: ログイン時にセッションIDを再発行しないケースです。セッション固定がそのまま成立します。
- よくある失敗2: absoluteをTTLだけで表そうとするケースです。リクエストごとに延びて永遠に死にません。
Redisセッションサーバーを起動する
/root/st/session/app.pyを127.0.0.1:8302で起動してください。GET /healthは200と{"ok":true}を返します。
Redisはすでに6379で動いています。サーバーはバックグラウンドで起動し、準備ができるまでポーリングしてください。
セッションCookieの属性を点検する
セッションCookieが__Host-sidという名前で、Secure、HttpOnly、SameSite、Path=/を持つかどうかを、/root/st/session/cookie.txtにSet-Cookieヘッダー1行で保存してください。
__Host-接頭辞はSecureとPath=/を要求し、Domainを付けてはいけません。curl -D - でレスポンスヘッダーを見ます。
セッションがRedisに保存されているか確認する
匿名セッションを受け取り、そのIDがsess:<id>キーでRedisにあり、TTLとuserを持つかどうかを/root/st/session/store.txtにsid= redis_key= exists=1 ttl=<양수> user=(anon)の形で書いてください(プレースホルダーは正の数です)。
Cookie jar(-c)でセッションIDを取得し、redis-cli EXISTS/TTL/GETで確認します。
ログイン時にセッションIDを再発行する
匿名セッションIDをそのまま持ってログインすると、レスポンスが別のセッションIDを返し、古いIDがストアから消えるかどうかを/root/st/session/fixation.txtにbefore= after= changed=yes old_gone=yesの形で書いてください。
セッション固定対策の核心です。ログイン成功時に古いセッションをdeleteして新しいIDを発行すると、changed=yesになります。
idleタイムアウトはslidingのTTL
ログインセッションのRedis TTLがidle上限以下で、リクエストをもう1回送るとTTLが再び満たされるかどうかを/root/st/session/idle_ttl.txtにttl_before= ttl_after_request=の形で書いてください。どちらも正の数で、afterは上限の近くである必要があります。
リクエストごとにEXPIREでTTLを延ばし直すと、sliding idleになります。
absoluteの期限切れはTTLと無関係
createdをabsolute上限より過去にし、idle TTLは十分に残したセッションが、GET /whoamiで認証として認められないかどうかを/root/st/session/absolute.txtにcode=200 auth=False gone=yesの形で書いてください。
load_sessionでnow - created > ABSOLUTE_SECなら、idle TTLが残っていても拒否する必要があります。
ログアウトで即座に無効化する
ログインしてからログアウトし、同じCookieでGET /whoamiすると匿名扱いになり、ストアのキーが消えるかどうかを/root/st/session/logout.txtにrevoked_auth=False key_gone=yesの形で書いてください。
サーバーが状態を握っているので、キーを1つ消せば即座に無効化されます。JWTとの違いです。
全体の流れを一度に証明する
/root/st/session/e2e.shで、匿名→ログインでの再発行→認証→ログアウトでの無効化を順に試し、/root/st/session/e2e.outにreissue=yes、old_gone=yes、logged_in=True、after_logout=Falseを残してください。
前のステップを1つのスクリプトにつなげます。4行がすべて出る必要があります。