在進行安全性評估時,我們注意到了標記語言 Edge Side Includes (ESI)中的一個意外行為,這種語言用于許多流行的 HTTP 代理(反向代理、負載平衡器、緩存服務器、代理服務器)。我們發現成功的 ESI 攻擊可以導致服務器端請求偽造(SSRF)、各種繞過 HTTPOnly cookie 緩解標志的跨站腳本向量(XSS)和服務器端分布式拒絕服務攻擊。我們稱這種技術為 ESI 注入。
通過我們的測試,我們發現了十幾種流行的可以處理 ESI 的產品: Varnish、 Squid Proxy、 IBM WebSphere、 Oracle fusion / weblogic、 Akamai、 Fastly、 F5、 Node.js、 LiteSpeed 和一些特定語言的插件。并非所有這些都默認啟用了 ESI,但下面將進一步討論這個問題。
什么是Edge Side Includes(ESI) ?
ESI 語言基于了一小部分 XML 標簽集合,在許多流行的 HTTP 代理解決方案中使用,通過啟用 Web 內容的高速緩存來解決性能問題。ESI 標簽用于指示反向代理(或緩存服務器)獲取已緩存模板的網頁的更多信息。這些信息在提供給客戶端之前可能來自另一臺服務器。這允許完全緩存的頁面包含動態內容。
ESI ?的一個常見用例是為那些基本上是靜態的頁面提供動態數據片段。ESI ?允許開發人員用 ESI 標記替換頁面的動態部分,從而增加了緩存靈活性。因此,在請求頁面時,代理將處理并獲取 ESI 標記,從而確保后端應用程序服務器的性能。
下面的圖片可用于演示 ESI 的一個典型用例,即天氣網站將緩存城市天氣頁面的內容。然后,動態數據將被它們各自指向 API 端點 URL 的 ESI 標記替換。
通過 ESI 構建網頁的演示
ESI ?的語法相當簡單。上一個例子的 HTML 文件看起來是這樣的:
? The Weather Website
? Weather for
? Monday:
? Tuesday:
[…]
最初的 ESI 規范可以追溯到2001年,每個供應商的實現差異很大。每個產品的特性集是不同的; 一些產品將缺少一些特性,但在其他產品中會出現。你可以在這里閱讀關于原始 ESI 規范的更多信息: http://www.w3.org/tr/ESI-lang。它描述了標記語言的用法和常用特性。各種供應商,包括 Akamai 和 Oracle,也在規范之外增加了額外的功能。
問題所在
HTTP 代理不能區分由上游服務器提供的合法 ESI 標記和在 HTTP 響應中注入的惡意標記。換句話說,如果攻擊者能夠成功地在 HTTP 響應中反射 ESI 標記,那么代理將盲目地解析和計算它們,相信它們是來自上游服務器的合法標記。
為了讓 ESI 解析器處理 ESI 標記,解析器不能對小于號和大于號的字符進行編碼或轉義。如今,web 應用服務器通常會避開用戶可控的特殊字符,以緩解 XSS 攻擊。雖然這將有效地阻止被代理程序解析的 ESI 標記,但 ESI 標記有時可以被注入到非 HTML 的 HTTP 響應中。實際上,ESI 的一個現代特性是允許開發人員向緩存和靜態數據源添加動態內容,比如 JSON 對象和 CSV。在 Fastly 的博客上可以找到關于 ESI + JSON 的詳盡教程,教程的內容顯示可以配置 ESI 解析器來處理 JSON 對象中的 ESI 標記。由于現代框架會嘗試將它們的轉義工作置于上下文環境中,所以 API 端點允許 JSON 屬性中類似 HTML 的字符串并不罕見,因為它們不應該被瀏覽器解釋為 HTML。但是,這允許攻擊者破壞反射在帶有 ESI 標記的 JSON 響應中的輸入,代理將在傳輸過程中對其進行解釋。
前面的場景非常罕見,因為它不代表任何已分析的支持 ESI 的產品的默認行為。大多數常見的攻擊向量將由后端服務器反射 ESI 標記,然后由啟用 ESI 的負載均衡器或代理進行處理。顯然,如果對用戶輸入進行了適當的清理(為了減輕 XSS 攻擊,應該這樣做) ,則代理將對 ESI 標記進行編碼,并且永遠不會對其進行處理。
ESI 注入的副作用
讓我們來看看一些注入的常見場景,以及它們可以用來做什么:
服務器端請求偽造(SSRF)
可以說,ESI 規范最常見和最有用的特性是 include 的使用。ESI 的 include 是一個標簽,當代理或負載均衡器處理這些標記時,執行另一個 HTTP 請求來獲取動態內容。如果攻擊者可以向 HTTP 響應添加 ESI include 標記,則可以在代理服務器(而不是應用程序服務器)的上下文中有效地執行 SSRF 攻擊。
例如,這個有效載荷可以用于在 HTTP 代理上執行 SSRF:
如果收到 HTTP 回調,那么代理服務器容易受到 ESI 注入的攻擊。如下面所討論的,ESI 實現有所不同。有些支持 ESI ?的服務器不允許來自沒有白名單的主機的 include 標簽,這意味著你只能對一臺服務器執行 SSRF。這在下面的 “變種實現” 一節中進行了討論。下面的圖表詳細說明了攻擊者如何利用 ESI 來執行 SSRF:
典型的 ESI 注入可以導致 SSRF
1、攻擊者通過帶有 ESI 有效載荷的代理服務器執行請求,試圖讓后端服務器在響應中反射該請求
2、代理服務器接收請求并將其轉發到適當的后端服務器
3、應用程序服務器在響應中反射 ESI 有效載荷,并將該響應發送到代理服務器
4、代理服務器接收到響應并進行解析,檢查是否存在任何 ESI 標記。代理服務器解析反射的 ESI 標記,并發起對evil.com 的請求。
5、代理服務器接收來自evil.com 并將其添加到后端服務器的初始響應中。
6、代理服務器將完整響應發送回客戶端
繞過客戶端 XSS 過濾器
客戶端 XSS 過濾器通常通過比較請求的輸入和響應來工作。當部分 GET 參數在 HTTP 響應中回顯時,瀏覽器將啟動一系列安全措施,以確定是否反射了潛在的 XSS 有效載荷。如果瀏覽器執行的啟發式算法將有效載荷識別為 HTML 或 Javascript,那么它就失效了,攻擊也就失敗了。
然而,Chrome 的 XSS 保護并不知道 ESI 標記,因為它們從未打算在客戶端進行處理。通過執行一些 ESI 魔法,可以將 XSS 有效載荷的部分分配給 ESI 引擎中的變量,然后將它們打印回來。ESI ?引擎將在服務器端構建惡意 Javascript 有效載荷,然后將其全部發送到瀏覽器。這將繞過 XSS 過濾器,因為發送到服務器的輸入不會按原樣返回到瀏覽器。讓我們分析一個簡單的有效載荷:
x=>alert(/Chrome%20XSS%20filter%20bypass/);>
操作符在服務器端 ESI 變量中存儲任意值。然后可以使用 $(variable_name) 操作符訪問這個變量。在前面的示例中,var1 變量存儲值 cript。然后將該值打印回來,以完成有效的腳本 HTML 標記。然后返回的有效載荷將以如下方式返回:
有些 ESI 實現不支持 ESI 變量,因此會使該技術失效。當 include 可用時,并且可以將它們指向外部域時,可以簡單地包含一個包含 XSS 有效載荷的外部頁面。下面的示例描述了使用 ESI 包括的典型 SSRF 到 XSS 攻擊。?
poc.html:
然后,注入 ESI 標簽來包含頁面:
GET /index.php?msg=
SSRF ?將獲取 poc. html 頁面并在網頁中顯示它,然后將有效載荷添加到 DOM 中。
繞過 HttpOnly Cookie 標志
通過設計,代理和負載均衡等 HTTP 代理可以訪問完整的 HTTP 請求和響應。這包括瀏覽器或服務器發送的所有 cookie。ESI ?規范的一個有用的特性是能夠在 ESI 標記內的傳輸過程中訪問 cookie。這允許開發人員在 ESI 引擎中引用 cookie,通過利用 cookie 的狀態性使它們具有更大的靈活性。
這個特性增加了一個重要的攻擊向量: cookie exfilling。通過 Javascript 引擎竊取 cookie 的一個眾所周知的對策是使用 HTTPOnly 標志。當在創建 cookie 時指定這個標志時,將拒絕 Javascript 引擎訪問 cookie 及其值的能力,從而防止 XSS 攻擊竊取 cookie。由于 ESI 是在服務器端處理的,因此當這些 cookie 從上游服務器傳輸到代理程序時,可以引用它們。一個攻擊向量是使用 ESI 的 include 在 URL 中將 cookie 提取出來。假設 ESI 引擎正在處理以下有效載荷:
在服務器 evil.com 的 HTTP 日志中,攻擊者會看到:
127.0.0.1 evil.com - [08/Mar/2018:15:20:44 - 0500] "GET /?cookie=bf2fa962b7889ed8869cadaba282 HTTP/1.1" 200 2 "-" "-"
通過這種方式,設置了 HTTPOnly 標志的 cookie 可以在沒有 Javascript 的情況下被提取出來。
變種實現
如前所述,不同廠商的 ESI 實現差異很大。不同產品的特性集不同,有些特性的實現方式也不同。我們測試了一些產品,以確定可能針對支持 ESI ?的軟件進行的攻擊,并生成了下面的表格。
該表格的列如下所示:
Includes
這一列表示是否在 ESI 引擎中實現了 ??運算對象。
Vars(變量)
這一列表示是否在 ESI 引擎中實現了?運算對象。
Cookie
這一列表示 ESI 引擎是否可以訪問 cookie。
需要上游服務器的 HTTP 頭
這一列表示 ESI 是否需要上游服務器的標頭才能運行。除非上游應用程序服務器提供了 HTTP 頭,否則代理項不會處理 ESI 語句。
主機白名單
這一列表示的 ESI 的 include 運算對象只針對白名單列出的服務器主機起作用。如果啟用了主機白名單的功能,那么攻擊者就不能使用 ESI include 對除白名單主機以外的主機執行 SSRF 攻擊。
以下章節將會更詳細地介紹 ESI 的實現及特定于供應商的特性。
Squid3
Squid 的 ESI 文檔幾乎不存在,因此我們必須使用源代碼來查找 ESI 特性。在測試各種 ESI 有效載荷時,我們發現了與 Squid 最新版本的 ESI 解析相關的兩個分布式拒絕服務攻擊錯誤。這兩個問題是空指針引用導致的錯誤,這將導致 Squid 服務崩潰。這兩個漏洞的編號分別為 CVE-2018-1000024和 CVE-2018-1000027。以下兩個公告詳細說明了易受攻擊的版本:
·?http://www.squid-cache.org/Advisories/SQUID-2018_1.txt
·?http://www.squid-cache.org/Advisories/SQUID-2018_2.txt
披露時間線:
·?2017年12月13日報告
·?2017年12月14日告知收到
·?2018年1月18日修復漏洞
·?2018年1月21日發布公告
下面是一個 ESI 的 include 運算對象可用于提取 cookies 的有效載荷:
ESI ?的一些實現允許你指定要提取哪個 cookie; Squid 不提供這一功能,你必須一次提取出所有 cookie。
Varnish Cache
Varnish 實現的 ESI 在安全性方面相當可靠。ESI 的 include 指令只能對 VCL (Varnish Configuration Language)定義的上游服務器執行。這意味著 ESI 的 include 指令不能導致對任意主機的 SSRF。所有 SSRF 將被重定向到上游服務器,緩解了大多數 SSRF 攻擊通常引起的問題。在本文發布時,在 Varnish Cache 中還沒有實現 ESI vars。規范文檔中有 vars 和 cookie 訪問的實現計劃。
在通過 Varnish 執行 ESI 時需要注意的一點是,默認情況下,如果 HTTP 響應的第一個非 nil 字符是小于號
在 Varnish Cache 中處理二進制數據的 ESI 標記
禁用此功能后,用戶可以使用文件上傳功能(例如配置文件圖片功能)并向上傳的數據中添加 ESI 標記。然后,它們可以請求服務器返回內容,從而導致 ESI 注入。
另外,在查看 Varnish Cache 中的 ESI 實現時,我們發現在 src 屬性中 ESI 的 include 指令沒有轉義回車符和換行符(CRLF)字符。這允許攻擊者向 ESI 的 include 指令中注入 HTTP 標頭,從而導致產生完整性良好的 HTTP 響應分割漏洞的奇怪變種。攻擊者可以注入以下 ESI 有效載荷來生成一個帶有兩個額外 HTTP 頭的 SSRF,X-Forwarded-For 和 JunkHeader:
GET / HTTP/1.1 Get / http / 1.1
User-Agent: curl/7.57.0 User-agent: curl / 7.57.0
Accept: */* * / *
Host: anything.com
X-Forwarded-For: 127.0.0.1 X-forded-for: 127.0.0.0.1
JunkHeader: JunkValue 1.1.1
X-Forwarded-For: 209.44.103.130 X-forded-for: 209.44.103.130
X-Varnish: 120
披露時間線:ESI? 的 include 指令的請求看起來像這樣:
·?2018年1月25日報告
·?2018年10月26日告知已收到
·?2018年2月13日修復漏洞
Fastly
Fastly 使用了大量自定義的 Varnish 后端,這意味著前面的大部分章節也適用于這一個供應商。僅有的兩個區別是 ESI 的include 指令中的上游服務器不需要 Fastly 的 surrogate-control HTTP 頭來解析 ESI 內容。此外,CRLF 注入似乎沒有影響到 Fastly。
Akamai ESI 測試服務器(ETS)
Akamai 在 ESI 規范的開發中扮演了重要角色(作為作者和編輯)。這可以從它們的 ESI 實現中提供的大量特性以及它們提供的關于 ESI 的極其詳細的文檔中看到。顯然,我們希望測試它們的 ESI 實現。2017年底,我們聯系了 Akamai 的安全負責人,親自提到了我們正在進行的 ESI 注入研究。由于 Akamai 是一個付費的服務提供商,我們要求獲得一個生產環境級的測試鏡像,在那里我們可以執行各種與 ESI ?相關的測試,但我們的好心被拒絕了。由于我們沒有得到 ESI 實例用于研究目的,所以我們嘗試獲得預售試用,但從未收到他們銷售團隊的回復。
我們最終決定對他們公開的 Docker 鏡像進行測試。這個 Docker 鏡像包含一個帶有自定義模塊—— mod_ESI.so ?的 Apache HTTP 服務器。所以,此模塊是其 ESI 實現的20mb ELF 32位編譯版本。值得慶幸的是,由于前面提到的文檔非常詳細,所以沒有必要使用逆向工程對這個 so 庫進行逆向。由于這只是一個測試鏡像,我們的發現可能不代表 Akamai 在生產環境中的實例。我們被告知,在生產環境中,ESI 在默認情況下是禁用的,并且已經設置了一些緩解控制,包括 SSRF 保護(主機白名單和其他緩解措施)和可選的 WAF。
也就是說,Akamai ETS (ESI Test Server)似乎容易受到上述所有場景的攻擊(SSRF、 HTTPOnly 繞過、 XSS 過濾器繞過)。
要使用 ESI 的 include 指令提取 cookie,可以使用下面的 HTTP cookie 字典按名稱引用特定的 cookie:
Akamai ETS 還提供了一系列有趣的特性,例如 ESI 調試模式。該模式通過 ?運算對象啟用,當激活該模式時,它將向 HTTP 響應中添加大量調試信息,例如原始文件(在代理服務器上可以看到而不是應用服務器)和所有環境變量。
還可以通過向 dca 參數指定 XSLT 值來添加基于 XSLT 的 ESI include 指令。下面的 include 指令將導致 HTTP 代理項請求 XML 和 XSLT 文件。然后使用 XSLT 文件過濾 XML 文件。此 XML 文件可用于執行 XML 外部實體(XXE)攻擊。這允許攻擊者執行 SSRF 攻擊,但這并不十分有用,因為這必須通過 ESI include 執行,而 ESI include 本身就是 SSRF 向量。由于基礎庫(Xalan)不支持外部 DTD,因此不解析外部 DTD。這意味著我們不能提取本地文件。
XSLT 文件:
]>
&xxe;
然而,由于我們可以使用 XML 實體,所以,出現在十年前的 Billon-Laugh 攻擊是有可能的。這種攻擊將遞歸地引用實體,導致掛起和內存耗盡,從而導致分布式拒絕服務攻擊。我們在使用 Akamai ETS Docker 鏡像在本地執行了攻擊,在一臺有32gb 內存的機器上服務停止了幾秒鐘。下面的 XSLT 文件可用于執行內存耗盡攻擊:
]>
&lol9;
NodeJS 的 ESI
為了支持 ESI 標簽,開發了一些 node.js 模塊。它們可以用作中間件、模仿代理項或在源代碼中內聯。這個庫對 ESI 規范的實現相當多,支持 include指令、變量 和 cookie。
要使用 ESI 的 include 指令提取 cookie,可以使用 HTTP_COOKIE 變量提取每個 cookie:
NodeJS 的 nodESI?
此模塊只支持 ESI 的 include 指令,不支持 ESI 變量。當執行 ESI 注入時,該模塊的維護人員實現了一個主機白名單機制,并在模塊的文檔中添加了一個安全章節。
其他供應商
我們沒有對上述之外的任何其他供應商進行測試; 這不是對任何其他供應商關于其實現 ESI 安全性的產品的認可或批評。
如何檢測 ESI 注入攻擊
有些代理將要求 ESI 處理在 Surrogate-Control ?HTTP 報頭中發出的信號,從而允許簡單的檢測。此標頭用于向上游服務器指示 ESI 標記可以出現在響應中,并且應該按照這種方式對它們進行解析。如果你觀察到如下 HTTP 頭響應: Surrogate-Control: content="ESI/1.0” ,那么你可能正在處理支持 ESI ?的基礎結構。
但是,大多數代理和負載均衡器會在將該報頭發送到客戶機之前從上游服務器中進行移除。有些代理也不需要任何 Surrogate-Control ?頭。因此,這不是確定 ESI 使用的確切方法。由于 ESI 實現中的特性選擇種類繁多,因此不能執行任何唯一的測試方法來測試 ESI 注入。人們必須測試各種有效載荷并觀察副作用,以正確識別 ESI 可注入的端點。例如,可以使用 ESI 的 include 指令對攻擊者控制的服務器執行 SSRF,但是有些實現會要求主機必須在白名單內。
注:本文參考自:gosecure.net