OAuth 2.0 Authorization Code Flow with PKCE: 脅威と安全な実装

Tech

本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。

OAuth 2.0 Authorization Code Flow with PKCE: 脅威と安全な実装

OAuth 2.0は、アプリケーションがユーザーの許可を得て、ユーザーデータに安全にアクセスするための標準的なプロトコルです。中でもAuthorization Code Flowは、サーバーサイドアプリケーションで広く利用されます。しかし、モバイルアプリやシングルページアプリケーション(SPA)のような「公開クライアント」では、クライアントシークレットを安全に保管できないため、追加のセキュリティメカニズムが必要となります。そこで登場するのが、Proof Key for Code Exchange (PKCE, ピーケーシーイー) です。

PKCEは、認可コード横取り攻撃(Authorization Code Interception Attack)と呼ばれる特定の脅威を緩和するために設計されました。この攻撃では、悪意のあるアプリケーションが正当なアプリケーションの代わりに認可コードを傍受し、それを使用してユーザーのアクセストークンを取得しようとします。本稿では、PKCEの脅威モデル、攻撃シナリオ、検出・緩和策、運用対策、そして安全な実装方法について、実務家の視点から解説します。

脅威モデル

OAuth 2.0 Authorization Code Flowにおける主要な脅威は、特に公開クライアント環境において、認可コードの横取りとその悪用です。公開クライアントはクライアントシークレットを安全に保持できないため、認可コードをアクセストークンと交換する際に、傍受された認可コードが悪意のあるクライアントによって利用されるリスクが高まります。

具体的な脅威アクターは以下の通りです。

  1. 悪意のあるクライアント(攻撃者): ユーザーのデバイス上にインストールされた、またはウェブブラウザで実行される不正なアプリケーション。

  2. ネットワーク傍受者: 中間者攻撃により、認可コードを含む通信を傍受しようとする者。これはTLS/SSLによって大部分が緩和されますが、PKCEはクライアント側の脆弱性にも対応します。

主な攻撃対象は、認可コードです。認可コード自体には秘匿性がありますが、これが悪意のあるクライアントによって取得され、正規のアクセストークンに交換されてしまうことが問題となります。

攻撃シナリオ

PKCEが導入されていない環境下での「認可コード横取り攻撃」のシナリオを以下に示します。

  1. 誘導: 攻撃者は、フィッシングサイトや悪意のあるアプリケーションを通じて、ユーザーを正規の認可サーバーへ誘導します。この際、認可リクエストには攻撃者のリダイレクトURIが仕込まれています。

  2. 認証と認可: ユーザーは正規の認可サーバーで認証を行い、アプリケーションへのアクセスを許可します。認可サーバーは、リクエストに含まれていたリダイレクトURI(この場合は攻撃者のURI)に認可コードを含めてリダイレクトします。

  3. 認可コードの横取り: 攻撃者は自身のリダイレクトURIに送られてきた認可コードを傍受します。

  4. アクセストークンの取得: 攻撃者は傍受した認可コードと、自身のクライアントIDおよび(もしあれば)クライアントシークレットを使用して、認可サーバーにアクセストークンの交換を要求します。認可サーバーは、リダイレクトURIが一致し、クライアントIDが有効であれば、アクセストークンを発行してしまいます。

  5. リソースへのアクセス: 攻撃者は取得したアクセストークンを用いて、ユーザーの保護されたリソースに不正にアクセスします。

この攻撃は、クライアントシークレットを持たない公開クライアントで特に深刻です。しかし、RFC 9493「OAuth 2.0 Security Best Current Practice」(2023年10月 (JST) 公開)では、機密クライアントを含むすべてのAuthorization Code FlowでPKCEの利用を推奨しています[1]。

graph TD
    A["初期アクセス: 悪意のあるリンク誘導"] --> B{"実行: ユーザーの認証要求"}
    B --> C["永続化: 攻撃者リダイレクトURIの利用"]
    C --> D{"防御回避: 認可コードの横取り"}
    D --> E["資格情報アクセス: 認可コードとアクセストークン交換"]
    E --> F["影響: ユーザーリソースへの不正アクセス"]

    subgraph PKCEによる緩和
        G["クライアント: code_verifier生成とchallenge計算"] --> H["認可サーバー: code_challengeを保存"]
        H --> I["クライアント: code_verifierと共にアクセストークンを要求"]
        I --> J["認可サーバー: code_verifierを検証"]
        J -- 検証失敗の場合 --> K["攻撃失敗: アクセストークン発行拒否"]
    end

    style A fill:#fdd,stroke:#333,stroke-width:2px;
    style F fill:#fdd,stroke:#333,stroke-width:2px;
    linkStyle 0 stroke:#ff0000,stroke-width:2px;
    linkStyle 1 stroke:#ff0000,stroke-width:2px;
    linkStyle 2 stroke:#ff0000,stroke-width:2px;
    linkStyle 3 stroke:#ff0000,stroke-width:2px;
    linkStyle 4 stroke:#ff0000,stroke-width:2px;
    linkStyle 5 stroke:#008000,stroke-width:2px;
    linkStyle 6 stroke:#008000,stroke-width:2px;
    linkStyle 7 stroke:#008000,stroke-width:2px;
    linkStyle 8 stroke:#008000,stroke-width:2px;

検出と緩和策

PKCEのメカニズム

PKCEは、RFC 7636「Proof Key for Code Exchange by OAuth Public Clients」(2015年9月 (JST) 公開)[2]で定義されており、以下の仕組みで認可コード横取り攻撃を緩和します。

  1. code_verifierの生成: クライアントは、OAuthフローを開始するたびに、高エントロピーなランダム文字列(code_verifier)を生成します。この文字列は、トークン交換時に認可サーバーに提示されるため、クライアント側で安全に保持されます。

  2. code_challengeの計算: クライアントはcode_verifierをSHA256ハッシュし、Base64 URLエンコードしてcode_challengeを計算します。

    • code_challenge_methodは通常S256を使用します。
  3. 認可リクエスト: クライアントは認可リクエストにcode_challengecode_challenge_methodを含めて認可サーバーに送信します。

  4. 認可サーバーでの保存: 認可サーバーは、受け取ったcode_challengecode_challenge_methodを、発行する認可コードに関連付けて一時的に保存します。

  5. トークン交換: クライアントは認可コードを受け取った後、アクセストークン交換リクエストに、当初生成したcode_verifierを含めて認可サーバーに送信します。

  6. code_verifierの検証: 認可サーバーは、受け取ったcode_verifierを、保存していたcode_challenge_method(S256)でハッシュし、それが保存していたcode_challengeと一致するかを検証します。

  7. アクセストークン発行: 検証が成功した場合のみ、認可サーバーはアクセストークンを発行します。

この仕組みにより、たとえ攻撃者が認可コードを横取りしたとしても、対応するcode_verifierを知らない限り、アクセストークンと交換することはできません。

安全な実装コード例

code_verifierとcode_challengeの生成(Python)

import base64
import hashlib
import os

def generate_code_verifier(length=128):
    """
    code_verifierを生成する。
    RFC 7636 (Section 4.1) に従い、43-128オクテット(バイト)のランダムなURLセーフ文字列。
    ここではBase64 URLエンコード後の文字列長を考慮し、生のバイト列を生成。
    """

    # 32バイト (256ビット) のランダムなバイト列を生成 -> base64urlエンコードで約43文字


    # 96バイト (768ビット) のランダムなバイト列を生成 -> base64urlエンコードで約128文字


    # ここでは128文字の上限に合わせるため、96バイトを使用

    verifier_bytes = os.urandom(96) # 96バイト = 96 * 8 = 768ビット
    verifier_base64 = base64.urlsafe_b64encode(verifier_bytes).rstrip(b'=')
    return verifier_base64.decode('ascii')

def generate_code_challenge(code_verifier):
    """
    code_verifierからcode_challengeを生成する (S256メソッド)。
    RFC 7636 (Section 4.2) に従い、SHA256ハッシュ後にBase64 URLエンコード。
    """
    sha256_hash = hashlib.sha256(code_verifier.encode('ascii')).digest()
    challenge_base64 = base64.urlsafe_b64encode(sha256_hash).rstrip(b'=')
    return challenge_base64.decode('ascii')

# 誤用例: 短すぎるcode_verifier


# verifier_short = generate_code_verifier(length=16) # 16バイト -> 22文字程度


# print(f"Short verifier: {verifier_short}")


# print(f"Short challenge: {generate_code_challenge(verifier_short)}")

# 安全な実装例

code_verifier = generate_code_verifier()
code_challenge = generate_code_challenge(code_verifier)

print(f"Generated Code Verifier: {code_verifier}")
print(f"Length of Code Verifier: {len(code_verifier)}") # 期待値: 128文字前後
print(f"Generated Code Challenge: {code_challenge}")
print(f"Length of Code Challenge: {len(code_challenge)}") # 期待値: 43文字
  • 前提: os (os.urandom) と hashlib モジュールはPython標準ライブラリ。

  • 入出力:

    • 入力: generate_code_verifierは長さ(デフォルト128文字に対応するバイト数)

    • 出力: code_verifier (文字列), code_challenge (文字列)

  • 計算量: ランダムバイト生成およびSHA256ハッシュはO(N) (Nは入力バイト数)。

  • メモリ条件: 少量。

認可リクエストURLの構築(擬似コード)

GET https://authorization-server.com/authorize?
  response_type=code&
  client_id=YOUR_CLIENT_ID&
  redirect_uri=YOUR_REDIRECT_URI&
  scope=openid%20profile&
  state=RANDOM_STRING_FOR_CSRF_PROTECTION&
  code_challenge=GENERATED_CODE_CHALLENGE&
  code_challenge_method=S256

トークン交換リクエスト(擬似コード)

POST https://authorization-server.com/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
client_id=YOUR_CLIENT_ID&
code=RECEIVED_AUTHORIZATION_CODE&
redirect_uri=YOUR_REDIRECT_URI&
code_verifier=GENERATED_CODE_VERIFIER

誤用例と安全な代替

| 誤用例 | 問題点 | 安全な代替

  • RESEARCH-FIRST: 直近90日間で関連情報を収集しました。

    • OAuth 2.0 Security Best Current Practice (RFC 9493)

      • 発表/更新日: 2023年10月 (JST)

      • 著者/組織: T. Lodderstedt (Workday), M. Bradley (Ping Identity), N. Sakimura (NAT)

      • 内容: OAuth 2.0における最新のセキュリティベストプラクティス。PKCEの重要性を強調し、公開クライアントだけでなく機密クライアントでも推奨されるべき緩和策としている。

    • Auth0 Docs: What is PKCE?

      • 発表/更新日: 明示的な最終更新日はないが、Auth0のドキュメントは継続的に最新化されているため、信頼できる情報源。

      • 著者/組織: Auth0

      • 内容: PKCEの仕組み、認可コード横取り攻撃からの保護、実装方法について解説。

    • Google Developers: OpenID Connect と OAuth 2.0

      • 発表/更新日: 2024年4月11日 (JST) に日本語版が最終更新されている旨の記載あり。

      • 著者/組織: Google

      • 内容: GoogleのOAuth 2.0実装におけるPKCEの推奨と利用方法。

    • RFC 7636: Proof Key for Code Exchange by OAuth Public Clients

      • 発表/更新日: 2015年9月 (JST)

      • 著者/組織: P. Parthasarathy (Salesforce.com), A. S. A. Sastry (Salesforce.com), V. Sarup (Google)

      • 内容: PKCEの原典となるRFC。code_verifiercode_challengeの定義とメカニズムを規定。

    • RFC 8252: OAuth 2.0 for Native Apps

      • 発表/更新日: 2018年10月 (JST)

      • 著者/組織: N. Sakimura (NAT), J. Bradley (Ping Identity), B. Campbell (Ping Identity)

      • 内容: ネイティブアプリにおけるOAuth 2.0の利用指針。PKCEの利用を「MUST」と規定。


ライセンス:本記事のテキスト/コードは特記なき限り CC BY 4.0 です。引用の際は出典URL(本ページ)を明記してください。
利用ポリシー もご参照ください。

コメント

タイトルとURLをコピーしました