Proof Key for Code Exchange
OAuth2 定義兩種 Client 類型:confidential 與 public 兩種。之前不管 OAuth2 或是 OpenID Connect,在說明流程的時候,都是以 confidential client,搭配較安全的 Authorization Code Grant / Authorization Code Flow 作為範例,但終究還是會面對相較不安全的 public client。
Authorization Code Interception Attack
在 RFC 7636 裡有提到 public client 容易受到攻擊的方法之一--authorization code interception attack,中文直譯為「授權碼攔截攻擊」,顧名思義,它的攻擊手法基礎正是把 authorization code 偷走。
之前討論到 OAuth2 的 Authorization Code Grant 或 OpenID Connect 時,有提到 code 用途主要是拿來交換 token;而在簡介 HTTP 協定有提到,HTTP 的特色即是無狀態,也就是只要請求內容一模一樣,則它無法分辨請求是否是惡意程式所發出的,因此只要攻擊者能偽造請求,即可成功使用 code 換取 token。
那回頭看一下 Client 會傳什麼資訊給 token endpoint:
grant_type
code
redirect_uri
除了上面三個參數外,另外還需要傳 Client ID 與 secret 給授權服務器驗證,即可換到 token。理論上憑證資訊與 token 都是不能外流的,但 public client 的特色即是 Client 處於相較不安全的環境,如 Native App 只要匯出後解開,即可找到憑證資訊。
以 Native App 為例,攻擊手法的時序圖如下:
@startuml |
詳細說明如下:
- 合法 App 在發出授權請求時,因為像智慧型手機的
redirect_uri
只能設定成自定義 URI 才能順利引導使用者回到 App 裡 - 因 OAuth2 定義需要 TLS 加密保護,所以這裡是安全的
- 拿到關鍵的 code 了
- 合法 App 取得 code,但如果有另一個惡意 App 也註冊了相同的 URI,則惡意 App 也能拿得到 code
- 只要惡意 App 取得了 code,即可對授權伺服器發出要 token 的請求
- 最後 token 就會被惡意的 App 偷走了
從流程圖上來看,這個攻擊是非常容易實現的,而 RFC 7636 正是定義如何防範此問題。
Proof Key for Code Exchange
首先先來定義專有名詞:
- code verifier: 一個隨機字串,與授權請求與 token 請求互相關聯
- code challenge: 從 code verifier 產生的 challenge,會夾帶到授權請求一起送出,以便後續驗證
- code challenge method: 產生 code challenge 的方法
協定流程
在 Client 發出的授權請求前,先保存一個參數在儲存空間裡,稱之為 code verifier,值為一個密碼學安全的僞隨機數。接著 Client 使用 code_verifier 搭配 code challenge method 產生另一個值稱之為 code challenge。方法 RFC 裡定義有兩種:
- plain: 不做任何處理
- S256: 將 code verifier 做 SHA-256 雜湊後再做 Base64UrlEncode
在發送授權請求的時候,多定義了下面這兩個參數:
名稱 | 必要 |
---|---|
code_challenge | REQUIRED |
code_challenge_method | OPTIONAL |
授權伺服器在收到 code_challenge
時,必須將它與回應的 code
做關聯。
接著在 Client 收到 code
後,發出 token 請求時,多帶 code verifier:
名稱 | 必要 |
---|---|
code_verifier | REQUIRED |
授權伺服器收到 code_verifier
與 code
之後,先把 code
關聯的 code_challenge
與 code_challenge_method
找到後,使用 code_verifier
搭配關聯到的 code_challenge_method
產生 challenge,再跟 code_challenge
比較,即可知道發出 token 請求的 Client 與當初發授權請求的是同一個 Client。
流程圖
@startuml |
這裡的步驟跟前面說明的一樣,主要是第 5 步,因為即便惡意 App 拿到了 code_challenge
,也無法回推第 5 步要傳給授權服務器的 code_verifier
,因此即能有效阻檔「授權碼攔截攻擊」