本系列文之後也會置於個人網站
+-------------------+
| 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是基於Code flow的安全強化版。在整個過程前後添加了兩個動作--產生code_verifier
和code_challenge
,並在最後透過code_challenge
驗證code_verifier
。其目的有很大程度是為了建立前端通訊與後端通訊的關聯。
那麼先來看看原本發生了什麼問題。
首先,已經知道Code Flow的整個流程是:
code
。code
轉交給授權的客戶端。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.
那麼如何做才能夠在前端通訊與後端通訊建立更爲明確的關聯了?客戶端必須有某種方式能攻證明授權的是自己。
code
。並與授權伺服器約定好,在未來又一個人會帶着這個code
,並使用一個能證明自己就是「那個人」的方法來找你。code
交給客戶端。code
訪問授權伺服器。這邊我不打算詳細說明客戶端如何證明身份。從概念上來說,就是有一個難題,這個難題的答案只有自己知道。要從難題推出答案很難,但是從難題證明答案正確很容易。
換個比喻就是:
而這裏使用到的難題就是單向雜湊函數(One-Way Hash Function)。如同其名,這個函數功能要透過一個方向運算非常簡單,但是反過來卻非常困難。
實際上產生過程在OAuth 2.0 Playground已經看過了:
產生出的code_challenge
和已知的演算法(單向雜湊函數,這裏是SHA256)組成難題。只要能夠提出正確的code_verifier
就能夠證明客戶端(出題人)。
這次同樣已Code模式爲基礎,透過PKCE來強化安全性。
同樣登入。但在登入時,將code_challege
與演算法組成的難題告訴授權伺服器。
最後在後端通訊時,將答案告訴授權伺服器:
如果驗證失敗就不會通過授權,並且code
可能也已經被泄漏,也不再可用。如果都通過,就能夠正常取得存取權杖。
code_verifity
和code_challenge
要產生並沒有那麼困難,code_verifity
只是亂數字串而已。然後透過一定演算法就可以得到code_challenge
code_verifity
和code_challenge
同樣的驗證也就沒有那麼困難。授權伺服器先後得到:
code_challenge
和驗證使用的方法code_verifity
現在姑且叫第一個叫做c1
,第二個叫做v
,驗證使用的方叫做m
。透過m
和v
可以得到c2
,只要c1
和c2
一致就是通過驗證。下面可以用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