iT邦幫忙

2021 iThome 鐵人賽

DAY 19
2
Software Development

用Keycloak學習身份驗證與授權系列 第 19

Day18 - 【概念篇】OAuth flows: PKCE

本系列文之後也會置於個人網站


                                                 +-------------------+
                                                 |   Authz Server    |
       +--------+                                | +---------------+ |
       |        |--(A)- Authorization Request ---->|               | |
       |        |       + t(code_verifier), t_m  | | Authorization | |
       |        |                                | |    Endpoint   | |
       |        |<-(B)---- Authorization Code -----|               | |
       |        |                                | +---------------+ |
       | Client |                                |                   |
       |        |                                | +---------------+ |
       |        |--(C)-- Access Token Request ---->|               | |
       |        |          + code_verifier       | |    Token      | |
       |        |                                | |   Endpoint    | |
       |        |<-(D)------ Access Token ---------|               | |
       +--------+                                | +---------------+ |
                                                 +-------------------+
 
                     Figure 2: Abstract Protocol Flow

PKCE模式

說穿了PKCE是基於Code flow的安全強化版。在整個過程前後添加了兩個動作--產生code_verifiercode_challenge,並在最後透過code_challenge驗證code_verifier。其目的有很大程度是為了建立前端通訊與後端通訊的關聯。

原先風險

那麼先來看看原本發生了什麼問題。

首先,已經知道Code Flow的整個流程是:

  1. 資源擁有者登入驗證身份並授權。
  2. 資源擁有者代理(通常爲瀏覽器)從授權伺服器取得一個臨時特殊密碼--code
  3. 資源擁有者代理將特殊密碼code轉交給授權的客戶端。
  4. 客戶端使用code,像授權伺服器換取access_token

可以看到code可能透過網路傳遞了多次,資源擁有者代理與授權伺服器之間、資源擁有者代理與客戶端之間、客戶端與授權伺服器之間。傳遞多次同時意味這泄漏的風險提高,也就有可能有惡意中間層取得存取權杖。

就算不是截取到code,了解攻擊手法的,同樣有可能不小心就猜測到code,進行攻擊。說真的這種情況還真的很難防範,防不勝防。爲了降低被攻擊的機會,最好在添加一些祕密,使攻擊難度提升。當然配合使用 Client Credentials Flow 或許是一個辦法,畢竟按照設計client_secret只會由客戶端擁有,並只在客戶端與授權伺服器之間流通。但這只證明了客戶端是已經被認可的客戶端,尚未證明資源擁有者授權的客戶端與使用code換取存取權杖的爲同一個。

這就像是任何人都可以聲稱自己是被授權的「那個人」。

難道授權伺服器要等個400多年嗎?還是隨便一個人說自己是「那個人」也就認可了呢?怎麼想都不太對吧!

    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+
    | End Device (e.g., Smartphone)  |
    |                                |
    | +-------------+   +----------+ | (6) Access Token  +----------+
    | |Legitimate   |   | Malicious|<--------------------|          |
    | |OAuth 2.0 App|   | App      |-------------------->|          |
    | +-------------+   +----------+ | (5) Authorization |          |
    |        |    ^          ^       |        Grant      |          |
    |        |     \         |       |                   |          |
    |        |      \   (4)  |       |                   |          |
    |    (1) |       \  Authz|       |                   |          |
    |   Authz|        \ Code |       |                   |  Authz   |
    | Request|         \     |       |                   |  Server  |
    |        |          \    |       |                   |          |
    |        |           \   |       |                   |          |
    |        v            \  |       |                   |          |
    | +----------------------------+ |                   |          |
    | |                            | | (3) Authz Code    |          |
    | |     Operating System/      |<--------------------|          |
    | |         Browser            |-------------------->|          |
    | |                            | | (2) Authz Request |          |
    | +----------------------------+ |                   +----------+
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+

             Figure 1: Authorization Code Interception Attack

   A number of pre-conditions need to hold for this attack to work:

   1. The attacker manages to register a malicious application on the
      client device and registers a custom URI scheme that is also used
      by another application.  The operating systems must allow a custom
      URI scheme to be registered by multiple applications.

   2. The OAuth 2.0 authorization code grant is used.

   3. The attacker has access to the OAuth 2.0 [RFC6749] "client_id" and
      "client_secret" (if provisioned).  All OAuth 2.0 native app
      client-instances use the same "client_id".  Secrets provisioned in
      client binary applications cannot be considered confidential.

   4. Either one of the following condition is met:

      4a. The attacker (via the installed application) is able to
          observe only the responses from the authorization endpoint.
          When "code_challenge_method" value is "plain", only this
          attack is mitigated.

解決辦法

那麼如何做才能夠在前端通訊與後端通訊建立更爲明確的關聯了?客戶端必須有某種方式能攻證明授權的是自己。

  1. 在概念上客戶端告訴瀏覽器證明自己的方式,並讓瀏覽器將這個訊息告訴授權伺服器。
  2. 接著瀏覽器取得特殊密碼code。並與授權伺服器約定好,在未來又一個人會帶着這個code,並使用一個能證明自己就是「那個人」的方法來找你。
  3. 然後瀏覽器將code交給客戶端。
  4. 客戶端帶著能夠證明自己的方式與code訪問授權伺服器。
  5. 授權伺服器驗證了客戶端爲資源擁有者代理告訴的是同一個,並將存取權杖交給授權伺服器。

這邊我不打算詳細說明客戶端如何證明身份。從概念上來說,就是有一個難題,這個難題的答案只有自己知道。要從難題推出答案很難,但是從難題證明答案正確很容易。
換個比喻就是:

  1. 客戶端將一把鎖交給了瀏覽器代理
  2. 瀏覽器代理又將這把鎖交給了授權伺服器
  3. 接著客戶端將鎖的鑰匙交給授權伺服器
  4. 授權伺服器使用鑰匙嘗試打開鎖,如果成功就證明客戶端是「那個人」

而這裏使用到的難題就是單向雜湊函數(One-Way Hash Function)。如同其名,這個函數功能要透過一個方向運算非常簡單,但是反過來卻非常困難。

實際上產生過程在OAuth 2.0 Playground已經看過了:

產生出的code_challenge和已知的演算法(單向雜湊函數,這裏是SHA256)組成難題。只要能夠提出正確的code_verifier就能夠證明客戶端(出題人)。

透過Keycloak和OAuth.Tools實戰

這次同樣已Code模式爲基礎,透過PKCE來強化安全性。

同樣登入。但在登入時,將code_challege與演算法組成的難題告訴授權伺服器。

最後在後端通訊時,將答案告訴授權伺服器:

如果驗證失敗就不會通過授權,並且code可能也已經被泄漏,也不再可用。如果都通過,就能夠正常取得存取權杖。

產生code_verifitycode_challenge

要產生並沒有那麼困難,code_verifity只是亂數字串而已。然後透過一定演算法就可以得到code_challenge

驗證code_verifitycode_challenge

同樣的驗證也就沒有那麼困難。授權伺服器先後得到:

  1. code_challenge和驗證使用的方法
  2. code_verifity

現在姑且叫第一個叫做c1,第二個叫做v,驗證使用的方叫做m。透過mv可以得到c2,只要c1c2一致就是通過驗證。下面可以用Python程式碼簡單驗證一下:

from base64 import urlsafe_b64encode
from hashlib import sha256

def gen_challege(vertify_str):
    return urlsafe_b64encode(sha256(vertify_str).digest())


####################

c1 = b'24OdicZTLu8T9kV3Pf1ZaPr8iJAGwaQJ0dvTQy5SSf0'
m = gen_challege

####################

v = b'xgoALuaqJPR4bK2wgNUSEBwKrxy6ljufTU7k4DRw7SA7NcqjLLqJXX4bI0091bbK'

####################

c2 = m(v)
c2 = c2.decode().rstrip('=').encode()
c1 == c2  # True

參考資料


上一篇
Day17 - 【概念篇】OAuth flows: Client Credentials
下一篇
Day19 - 【概念篇】OAuth flows: Device Code(1)
系列文
用Keycloak學習身份驗證與授權40

尚未有邦友留言

立即登入留言