本文將梳理一下OAuth 2.0中客戶端、授權服務器和資源服務器上可能被利用的漏洞以及預防方法。
一、客戶端漏洞及預防方法
對於客戶端來說,最重要的信息就是客戶端密鑰和令牌,所以客戶端上能夠被利用的漏洞就是令牌和密鑰失竊。
1.1 針對客戶端的CSRF攻擊
CSRF:cross-site request foregry,跨站請求偽造,在OAuth 2.0中的體現就是攻擊者偽造一個合法的請求,並讓完成身份認證的正常用戶觸發該請求,以達到竊取用戶令牌的目的。詳細流程可以用下面的圖表示
如上圖所示,攻擊者發起攻擊的前提是被攻擊對象在授權服務器上完成了身份認證和授權,這樣當用戶無意中點擊攻擊者偽造的請求之后,以個人名義向授權服務器請求token(但是用戶無感知),因為令牌是授權服務器針對客戶端頒發的,授權服務器無法知道接收到的授權碼是攻擊者的還是正常用戶的,因為請求來自正常用戶,所以授權服務器只能給正常用戶頒發令牌,但在響應結果中獲取到令牌的是攻擊者而非正常用戶,當攻擊者拿到令牌之后,就會向資源服務器請求正常用戶的資源了!!!
來分析一下這個漏洞出現的原因:上圖中的攻擊者和被攻擊者,對於授權服務器和客戶端來說都是正常的用戶,只要用戶能完成給客戶端授權的操作,他們都能通過客戶端請求受保護的資源,而授權是否成功的結果是能否拿到授權碼,當他們都完成了認證授權后都能拿到授權碼,而且這個授權碼只是用戶和客戶端之間的唯一標識,授權服務器不會區分授權碼是哪個用戶的請求,因為請求都來自客戶端而非用戶,所以就會出現這樣一種情況:任何用戶都能拿着別人的授權碼向授權服務器請求令牌,而這個令牌卻是自己的,反過來也是如此,攻擊者正是利用了這一漏洞,使用自己的授權碼偽造獲取令牌的請求,並且想辦法讓用戶在不經意間觸發請求從而獲取用戶的令牌,想來真是妙啊!!!
既然知道了自己的軟肋,那是否有辦法搞個盔甲預防這種攻擊呢?答案是肯定的!既然任何人都能使用自己的授權碼請求別人的令牌,而且授權服務器無法區分請求令牌的授權碼是不是真正的用戶,那客戶端就要想辦法在請求令牌之前檢查這個授權碼是否是這個用戶完成授權之后授權服務器返回的,這點對於客戶端來說就是小菜一碟了,因為只有它能將用戶和授權碼關聯起來,方法就是前面提到的state參數,這個參數看似不起眼,實則大有可用,因為它就是客戶端的盔甲!OAuth核心規范中對state參數是這樣描述的:
客戶端用來維持請求與回調之間狀態的不透明值。授權服務器將用戶代理重定向回客戶端時包含該值。應該使用這個參數,它可以防止CSRF跨站請求偽造。
為了預防CSRF攻擊,在將用戶重定向至授權服務器之前,客戶端會生成一個請求上下文標識state,並將state一起發送到授權服務器上,授權服務器完成用戶的認證授權后,回傳state和授權碼,客戶端接收到回調請求后,需要校驗是否有state參數,以及state是否是當前用戶的上下文,如果不是則直接報錯,這樣一來就輕松地解決了當前用戶的授權碼被替換的問題,實現起來其實並不復雜,但是容易忽略!!!
1.2 客戶端憑據失竊
客戶端憑據失竊是針對隱式許可類型而言的,因為在有客戶端的后端信道中,客戶端的密鑰是存儲在客戶端服務器的,正常來講還是相對安全,但是在客戶端和用戶為一體的瀏覽器應用中,客戶端密鑰是存儲在瀏覽器中的,這存在很大的密鑰失竊風險,一旦密鑰失竊,就意味着任何人都能盜取自己令牌,類似賬號被盜!
解決方法:使用動態注冊客戶端。動態注冊客戶端是指不提前分配client_id和client_secret給客戶端,當客戶端需要使用client_id和client_secret時,先檢查客戶端是否存在,不存在時向授權服務器請求注冊!
1.3 客戶端重定向URI注冊
客戶端重定向URI注冊,是指利用授權服務器重定向到客戶端注冊時提供的重定向地址進行的攻擊,主要有以下兩種:授權碼失竊和令牌失竊。其實無論何種攻擊,盜取用戶的令牌進行為所欲為是攻擊者的最終目的,盜取到授權碼之后,可以利用CSRF攻擊進一步盜取用戶的令牌,可謂是步步驚心,下面來看看這兩種攻擊都是怎么發生的!
1.3.1 授權碼失竊
授權碼失竊主要是利用HTTP請求的Referer頭。HTTP Referrer(據說當初被錯誤的拼寫成Referer,沿用至今😃 )是瀏覽器或者一般的客戶端,從一個頁面跳轉至另一個頁面時所附加的HTTP頭部字段,通過這種方式,新的Web頁面就能知道請求來自哪里。但是這種攻擊的前提是授權服務器在校驗客戶端重定向地址redirect_uri時,采用了子目錄模式,而不是精確匹配,子目錄模式就意味着只要請求的uri中包含注冊的uri就認為是合法的請求。
舉個例子🌰 :
比如客戶端注冊時提供的redirect_uri是https://client.com/callback,請求授權時傳入的redirect_uri是https://client.com/callback/index.html,授權服務器采用子目錄進行匹配時發現,請求時的值中中包含了注冊時的值,則校驗通過,授權成功后就會返回授權碼並重定向到請求的redirect_uri上去了。畫了一張圖嘗試解釋這種攻擊流程
利用Referer的請求一般有以下規則:
1.3.2 令牌失竊
令牌失竊和授權碼失竊的攻擊原理是相同的,都是利用客戶端不規則的重定向地址和授權服務器的允許子目錄校驗,只不過令牌失竊是針對隱式許可類型的,而且要求客戶端支持開放重定向功能(關於開放重定向的解釋,參考這里:https://www.wangan.com/articles/1132)。攻擊流程一般是這樣的:
以上兩種攻擊最好的預防方式都是:
注冊時客戶端重定向地址redirect_uri盡可能具體,而且授權服務器校驗客戶端時,最好使用精確匹配,而非允許子目錄。
1.3.3 小結
為了預防客戶端遭到上述攻擊,一個相對安全的客戶端需要做到如下幾點:
- 使用state參數,確保授權碼和授權請求是由同一個用戶發起的
- 選擇合適的許可類型
- 隱式許可類型不應用用於原生應用中,它是專門供瀏覽器內客戶端使用的
- 原生應用無法對客戶端密鑰進行保密,除非是動態注冊的情況在在運行時配置client_secret
- 注冊redirect_uri時應該盡可能地具體
- 盡量避免以URI參數形式傳遞訪問令牌
二、資源服務器漏洞及預防方法
資源服務器的漏洞一種是前面講的令牌失竊,另一種是受保護資源如果支持令牌通過參數傳輸,則攻擊者通過URL參數向資源服務器請求時,很容易發起XSS攻擊。
2.1 XSS攻擊
Cross-Site Scripting(跨站腳本攻擊)簡稱 XSS,是一種代碼注入攻擊。攻擊者通過在目標網站上注入惡意腳本,使之在用戶的瀏覽器上運行。利用這些惡意腳本,攻擊者可獲取用戶的敏感信息如 Cookie、SessionID 等,進而危害數據安全。為了和 CSS 區分,這里把攻擊的第一個字母改成了 X,於是叫做 XSS。
上面的解釋引自美團技術團隊的文章,寫得真好,詳情請戳:https://tech.meituan.com/2018/09/27/fe-security.html
為什么說令牌支持參數傳遞的時候就容易造成XSS攻擊呢,因為如果放在Header中,一般情況下在請求到達資源服務器獲取資源的方法之前,一般會先經過攔截器,在攔截器中做統一處理,如果攻擊者使用的是一個非法的令牌,在攔截器中校驗失敗,即便獲取資源接口本身支持參數,也無法利用該漏洞實現XSS攻擊;但是如果令牌支持參數傳遞,則請求會直接懟到獲取資源的方法上,比如像下面這樣發起請求:
https://resourceendpoint.com/getResourceInfo?accessToken=fdeggfdafgggfzd&name=<script>alert("XSS")</script>
只要用戶點擊的連接,就能出現彈出框,這只是一個簡單的例子,真正的攻擊者可不會這么溫柔!
XSS的預防措施有以下幾種:
- 轉義所有不可信的數據,使用URI編碼
- 資源服務器增加響應頭Content-Type,讓受保護資源返回正確的媒體類型,比如application/json,這樣返回的類型就是json,會拒絕執行JavaScript中的代碼
- 資源服務器增加響應頭X-Content-Type-Options: nosniff,它的作用是防止在沒有聲明Content-Type頭的情況下執行MIME嗅探
- 資源服務器增加響應頭X-XSS-Protection:1;mode=block,它的作用是自動啟動大多數瀏覽器內置的XSS過濾器來看看如何為端點添加頭部(火狐不支持)
- 資源服務器增加響應頭Content-Security-Policy,采用內容安全策略(content security policy,CSP),通過使用頭來聲明允許加載什么資源,以此降低XSS風險
- 資源服務器不允許通過查詢參數傳遞access_token
2.2 CORS
在隱式許可類型中,客戶端對資源的訪問是由瀏覽器直接發起的,這就會出現跨域問題。跨域問題指的是當請求方與服務方的協議、主機和端口三者中,任意一個值不同時,請求將會被拒絕,因為這違背了瀏覽器的同源策略。該策略的目的是防止一個頁面中的JavaScript代碼從另外一個域加載惡意代碼,但是在OAuth的這種場景中是允許這樣的訪問,即資源服務器不該拒絕來自不同域的客戶端(瀏覽器)請求。
解決方法:跨域資源共享CORS,資源服務器需要增加跨域支持,Java中的實現方式一般是這樣的,在springboot項目中增加CORS配置:
@Configuration
public class CorsConfig{
private CorsConfiguration buildConfig() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*"); //允許任何域名
corsConfiguration.addAllowedHeader("*"); //允許任何頭
corsConfiguration.addAllowedMethod("*"); //允許任何方法
return corsConfiguration;
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", buildConfig()); //注冊
return new CorsFilter(source);
}
}
CORS還有另外一種解決方案就是帶填充的JSON(JSON With Padding,JSONP)。JSONP是開發人員用來繞過瀏覽器強加的跨域限制的一種技術,它讓瀏覽器可以接受來自當前頁面所在域之外其他域的數據。實際上JSON數據是通過在目標環境中加載並運行JavaScript腳本來傳遞的,通常會指定一個回調函數,由於數據的請求表現為一個script標簽,而不是一個AJAX請求,因此能夠繞過瀏覽器的同源策略檢查。但是因為它存在安全漏洞,已經被棄用。
2.3 令牌重放
OAuth 2.0與之前版本最主要的區別之一在於其核心框架沒有對加密方法做出要求,它在各種連接中都完全依賴傳輸層安全協議(TLS),因此OAuth生態系統中盡可能地強制使用TLS被認為是最佳時間。此外,有一個標准是專門用於此的:HTTP嚴格傳輸安全(HTTP strict transport security,HSTS),它讓Web服務器能夠聲明瀏覽器在於它交互時必須使用安全的HTTPS連接,而不允許使用不安全的HTTP協議。
實現方式很簡單,就是添加響應頭:
Strict-Transport-Security: 'max-age=3153600'
這樣以來,每次在瀏覽器中通過HTTP請求該端點,瀏覽器都會執行一個內部307重定向,這樣就能防止任何意外的未加密通信。
2.4 小結
為了預防資源服務器遭到上述攻擊,一個相對安全的資源服務器需要做到如下幾點:
- 在受保護資源的響應中過濾素有不可信的數據
- 為每個端點選擇合適的Content-Type
- 盡可能利用瀏覽器的防護機制以及安全頭部
- 如果資源端點要使用隱式許可類型,則要使用CORS
- 避免讓受保護資源支持JSONP
- 總是將HTTPS和HSTS結合使用
三、授權服務器漏洞及預防方法
3.1 常規安全
授權服務器需要和用戶、客戶端和資源服務器三者進行交互,它在OAuth中扮演着舉足輕重的角色,所以保證其安全性是非常重要的,一般的常規安全措施有:服務器使用安全日志、有效證書的TLS、安全的宿主環境、正確的操作系統賬戶權限控制等。
3.2 會話劫持
問題:會話劫持是針對授權碼而言的,如果授權碼能夠重復使用,那么攻擊者就能拿着用戶的授權碼向授權服務器發送獲取令牌請求,如果這時授權服務器不進行授權碼的有效性校驗,直接頒發令牌,就會讓攻擊者能夠使用受害者的令牌了。
解決:
- 客戶端不能多次使用同一個授權碼。如果一個客戶端使用了已經被使用過的授權碼,授權服務器必須拒絕該請求,並且盡可能地吹之前通過該授權碼頒發的所有令牌
- 保證授權碼只會頒發給經過身份認證的客戶端:如果客戶端不是保密客戶端,則要確保授權碼只會頒發給請求中client_id對應的客戶端
3.3 重定向URI被篡改
問題:這個是利用客戶端注冊了松散的重定向地址,並且授權服務器未使用精確匹配導致的漏洞,詳情已經在1.3講過,不再贅述
解決:
- 授權服務器要求客戶端必須注冊標准URI
- 開啟精確匹配
3.4 客戶端假冒
攻擊者劫持了授權碼之后,想要進一步獲取令牌,需要保證下面兩點:
- 保證授權碼與請求上下文state一致
- 需要有客戶端密鑰
攻擊者只要想辦法實現上面兩點,就能輕松獲取令牌了。為了實現這一點,攻擊者攔截用戶授權請求,並將客戶端重定向uri指向自己的頁面,用戶完成身份認證和授權,如果授權服務器使用寬松的redirect_uri校驗,則會驗證通過生成一個授權碼,然后帶着授權碼和state信息重定向到攻擊者頁面,攻擊者拿到授權碼code和state后會偽造用戶向客戶端的請求(調用客戶端重定向地址),因為state和code都是受害用戶請求的,所以客戶端驗證通過並向授權服務器請求令牌,授權服務器接收到的請求會被認為是正常請求,校驗通過頒發token,客戶端獲取到token繼續請求受保護資源,資源服務器將返回請求資源,但是接收資源的人不是正常用戶,而是攻擊者!下面看一下這種攻擊的實現流程:
上述漏洞的出現還是因為授權服務器使用了松散的redirect_uri校驗,導致授權碼被截取,同時在頒發令牌的驗證流程中,沒有校驗請求授權時傳入的redirect_uri和請求令牌時的redirect_uri是否一致。
預防方法:在頒發授權碼之前,確保令牌請求中的重定向地址與授權請求中的重定向地址一致,這樣即使授權碼被盜,並且成功偽裝客戶端發起令牌請求,也會在授權服務器被攔截!
3.5 開放重定向器
開放重定向器,就是指在授權過程中,如果授權服務器校驗參數失敗,會將用戶重定向到被篡改的重定向頁面,攻擊者可以利用該漏洞構造截取授權碼或令牌(隱式許可類型),步驟如下:
- 在授權服務器https://authendpoint.com上注冊一個新的客戶端
- 將redirect_uri注冊為https://attacker.com
- 為惡意客戶端構造哪一個無效的授權請求URI。比如可以使用錯誤或不存在的授權范圍:
https://authendpoint.com/authorize?client_id=daffdafd3554543dafdaf&redirect_uri=https://attacker.com&state=dfdgjudbetrdd&response_type=token&scope=WRONG_SCOPE
- 以正當客戶端為目標,構造一個惡意的URI,將其重定向到上一步構造的URI:
https://authendpoint.com/authorize?client_id=daffdafd3554543dafdaf&state=dfdgjudbetrdd&response_type=token&scope=RIGHT_SCOPE&redirect_uri=https://authendpoint.com/authorize?client_id=daffdafd3554543dafdaf&redirect_uri=https://attacker.com&state=dfdgjudbetrdd&response_type=token&scope=WRONG_SCOPE
- 如果受害用戶已經使用過OAuth客戶端並且授權服務器支持TOFU,那么攻擊者就會收到重定向至https://attacker.com的響應,響應中可能會攜帶授權碼或令牌。
至此,OAuth中客戶端、資源服務器和授權服務器中可能存在的漏洞及預防方法梳理的差不多,下面總結一下這些內容。
3.6 小結
為了預防授權服務器遭到上述攻擊,一個相對安全的授權服務器需要做到如下幾點:
- 授權碼使用一次之后將其銷毀
- 授權服務器應該采用精確匹配的重定向URI校驗算法
- 避免讓授權服務器成為開放重定向器,在非法的請求中直接返回錯誤而不是重定向
OAuth的安全需要依靠各個組件之間的相互制約之外,各個組件在實現自己服務的過程中,不能把安全的保證機制交給其他組件去實現,而是盡可能想到其他組件可能存在的不完善,進而完善自己,所謂寬以待人嚴以律己!