Flask のセッション Cookie に機密情報を入れてはいけない

Flask のデフォルトセッションはクライアント側の Cookie に保存される。署名により改ざんは検知できるが、内容は暗号化されていないため、機密情報を入れてはいけない。

Flask セッションの仕組み

Flask のセッションは itsdangerous ライブラリで署名されている。

from flask import Flask, session

app = Flask(__name__)
app.secret_key = 'your-secret-key'

@app.route('/login')
def login():
    session['user_id'] = 123
    session['role'] = 'admin'  # これは読める
    return 'Logged in'

ブラウザの開発者ツールで Cookie を確認すると、session という名前の値が見える。

セッション Cookie の中身を見る

セッション Cookie は Base64 エンコードされた JSON であり、簡単にデコードできる。

import base64

cookie = 'eyJ1c2VyX2lkIjoxMjMsInJvbGUiOiJhZG1pbiJ9...'
# ドットより前がペイロード
payload = cookie.split('.')[0]
# Base64 デコード
decoded = base64.urlsafe_b64decode(payload + '==')
print(decoded)  # b'{"user_id":123,"role":"admin"}'

このように、セッションに保存した内容は誰でも読める。

入れてはいけないもの

パスワードやパスワードハッシュ
クレジットカード情報
個人を特定できる機密情報(マイナンバーなど)
API キーやトークン

署名の役割

署名は改ざんを検知するためのものであり、秘匿のためではない。

# 攻撃者が role を admin に書き換えようとしても
# 署名が一致しないので Flask が拒否する

ただし、secret_key が漏洩すると攻撃者は有効な署名を生成できてしまう。secret_key は十分に複雑な値を設定し、絶対に外部に漏らさないこと。

安全な代替手段

機密情報はサーバーサイドに保存し、セッションには ID だけを入れる。

@app.route('/login')
def login():
    session['session_id'] = generate_session_id()
    # 機密情報は Redis やデータベースに保存
    redis.hset(f'session:{session_id}', 'credit_card', '****')
    return 'Logged in'

Flask-Session を使えば、セッション自体をサーバーサイドに保存できる。

from flask_session import Session

app.config['SESSION_TYPE'] = 'redis'
Session(app)

この設定では Cookie にはセッション ID のみが保存され、実際のデータは Redis に格納される。