固定したOAuth2 state値の使用

固定したOAuth2 state値の使用

説明

OAuth2の state は認証リクエストとコールバックを関連付け、CSRFやセッションの混同を防ぐための値です。固定文字列や再利用する定数を使うと、攻撃者が予測可能な値でコールバックを作成できます。

想定される影響

  • 認証応答の偽造やログインCSRFが可能になるおそれがあります。
  • ユーザーが攻撃者の意図したアカウントやセッションへ関連付けられる可能性があります。

対処方法

  • リクエストごとに予測できない state を生成してください。
  • サーバーセッションや署名付きストレージに関連付け、コールバックで検証してください。有効期限を設定し、検証と使用済みへの変更を原子的に行って再利用を防いでください。

例

セッションへの関連付けと空の保存値の拒否を示す抜粋です。newRandomState は暗号学的に安全な乱数で空でない値を生成するかエラーを返し、Session.Get は文字列を返す前提です。有効期限と一度だけの使用を保証する処理は省略しています。実際のログインを受け入れる前に実装してください。

変更前

go
const oauthState = "fixed-state"

func login(cfg *oauth2.Config) string {
    return cfg.AuthCodeURL(oauthState)
}

func callback(r *http.Request) bool {
    return r.URL.Query().Get("state") == oauthState
}

変更後

go
func login(cfg *oauth2.Config, session Session) (string, error) {
    state, err := newRandomState()
    if err != nil {
        return "", err
    }
    session.Set("oauth_state", state)
    return cfg.AuthCodeURL(state), nil
}

func callback(r *http.Request, session Session) bool {
    expected := session.Get("oauth_state")
    return expected != "" && r.URL.Query().Get("state") == expected
}

解説:

  • 変更前: 認証リクエストとコールバックの検証に同じ固定値を使うため、予測可能なコールバックを作成されるおそれがあります。
  • 変更後: リクエストごとに予測できない値を生成してユーザーのセッションに保存し、空でないことを確認してからリクエストの値と比較します。この比較だけでは有効期限や一度だけの使用は保証されません。

参考資料