爲你的網站帶上帽子 — 使用 helmet 保護 Express 應用

Express 基於 Node.js,是一款用於構建 Web 服務的優秀框架。它很容易上手,且得益於其中間件的概念,能夠很方便地進行配置與拓展。儘管如今有各類各樣的用於建立 Web 應用的框架,但個人第一選擇始終是 Express。然而,直接使用 Express 不能徹底遵循安全性的最佳實踐。所以咱們須要使用相似 helmet 的模塊來改善應用的安全性。php

部署

在開始以前,請確認你已經安裝好了 Node.js 以及 npm(或 yarn)。你能夠在 Node.js 官網下載以及查看安裝指南css

咱們將以一個新的工程爲例,不過你也能夠將這些功能應用於現有的工程中。html

在命令行中運行如下命令建立一個新的工程:前端

mkdir secure-express-demo
cd secure-express-demo
npm init -y
複製代碼

運行如下命令安裝 Express 模塊:node

npm install express --save
複製代碼

secure-express-demo 目錄下建立一個名爲 index.js 的文件,加入如下代碼:android

const express = require('express');
const PORT = process.env.PORT || 3000;
const app = express();

app.get('/', (req, res) => {
  res.send(`<h1>Hello World</h1>`);
});

app.listen(PORT, () => {
  console.log(`Listening on http://localhost:${PORT}`);
});
複製代碼

保存文件,試運行看看它是否能正常工做。運行如下命令啓動服務:ios

node index.js
複製代碼

訪問 http://localhost:3000,你應該能夠看到 Hello Worldgit

hello-world.png

檢查 Headers

giphy.gif

如今讓咱們經過增長與刪除一些 HTTP headers 來改善應用安全性。你能夠用一些工具來檢查它的 headers,例如使用 curl 運行如下命令:github

curl http://localhost:3000 --include
複製代碼

--include 標誌可讓其輸出 response 的 HTTP headers。若是你沒有安裝 curl,也能夠用你最經常使用瀏覽器開發者工具的 network 面板代替。web

你能夠看到在收到的 response 中包含的如下 HTTP headers:

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 20
ETag: W/"14-SsoazAISF4H46953FT6rSL7/tvU"
Date: Wed, 01 Nov 2017 13:36:10 GMT
Connection: keep-alive
複製代碼

通常來講,由 X- 開頭的 header 是非標準頭部。請注意那個 X-Powered-By 的 header,它會暴露你使用的框架。對於攻擊者來講,這能夠下降攻擊成本,由於他們只專一攻擊此框架的已知漏洞便可。

戴上頭盔(helmet)

giphy.gif

來看看若是咱們使用 helmet 會發生什麼。運行如下命令安裝 helmet

npm install helmet --save
複製代碼

helmet 中間件加入你的應用中。對 index.js 進行以下修改:

const express = require('express');
const helmet = require('helmet');
const PORT = process.env.PORT || 3000;
const app = express();

app.use(helmet());

app.get('/', (req, res) => {
  res.send(`<h1>Hello World</h1>`);
});

app.listen(PORT, () => {
  console.log(`Listening on http://localhost:${PORT}`);
});
複製代碼

這樣就使用了 helmet 的默認配置。接下來看看它作了什麼事情。重啓服務,再次經過如下命令檢查 HTTP headers:

curl http://localhost:3000 --inspect
複製代碼

新的 headers 會相似於下面這樣:

HTTP/1.1 200 OK
X-DNS-Prefetch-Control: off
X-Frame-Options: SAMEORIGIN
Strict-Transport-Security: max-age=15552000; includeSubDomains
X-Download-Options: noopen
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Content-Type: text/html; charset=utf-8
Content-Length: 20
ETag: W/"14-SsoazAISF4H46953FT6rSL7/tvU"
Date: Wed, 01 Nov 2017 13:50:42 GMT
Connection: keep-alive
複製代碼

首先值得慶祝的是 X-Powered-By header 不見了。但如今又多了好些新的 header,它們是作什麼的呢?

X-DNS-Prefetch-Control

這個 header 對增長安全性並無太大做用。它的值爲 off 時,將關閉瀏覽器對頁面中 URL 的 DNS 預讀取。DNS 預讀取能夠提升你的網站的性能,根據 MDN 描述,它能夠增長 5% 或更高的圖片加載速度。不過開啓這項功能也可能會使用戶在屢次訪問同一個網頁時緩存出現問題。

譯註:緩存問題未查到資料,若是您瞭解這塊請留言

它的默認值是 off,若是你但願經過它提高性能,能夠在調用 helmet() 時傳入 { dnsPrefetchControl: { allow: true }} 開啓 DNS 預讀取。

X-Frame-Options

X-Frame-Options 可讓你控制頁面是否能在 <frame/><iframe/> 或者 <object/> 之類的頁框內加載。除非你的確須要經過這些方式來打開頁面,不然請經過下面的配置徹底禁用它:

app.use(helmet({
  frameguard: {
    action: 'deny'
  }
}));
複製代碼

全部的現代瀏覽器都支持 X-Frame-Options。你也能夠經過稍後將介紹的內容安全策略來控制它。

Strict-Transport-Security

它也被稱爲 HSTS(嚴格安全 HTTP 傳輸),用於確保在訪問 HTTPS 網站時不出現協議降級(回到 HTTP)的狀況。若是用戶一旦訪問了帶有此 header 的 HTTPS 網站,瀏覽器就會確保未來再次訪問次網站時不容許使用 HTTP 進行通訊。此功能有助於防範中間人攻擊。

有時,當你使用公共 WiFi 時嘗試訪問 https://google.com 之類的門戶網頁時就能看到此功能運做。WiFi 嘗試將你重定向到他們的門戶網站去,但你曾經經過 HTTPS 訪問過 google.com,且它帶有 Strict-Transport-Security 的 header,所以瀏覽器將阻止重定向。

你能夠訪問 MDN 或者 OWASP wiki 查看更多相關信息。

X-Download-Options

這個 header 僅用於保護你的應用免受老版 IE 漏洞的困擾。通常來講,若是你部署了不能被信任的 HTTP 文件用於下載,用戶能夠直接打開這些文件(而不須要先保存到硬盤去)而且能夠直接在你 app 的上下文中執行。這個 header 能夠確保用戶在訪問這種文件前必須將其下載到本地,這樣就能防止這些文件在你 app 的上下文中執行了。

你能夠訪問 helmet 文檔MSDN 博文查看更多相關信息。

X-Content-Type-Options

一些瀏覽器不使用服務器發送的 Content-Type 來判斷文件類型,而使用「MIME 嗅探」,根據文件內容來判斷內容類型並基於此執行文件。

假設你在網頁中提供了一個上傳圖片的途徑,但攻擊者上傳了一些內容爲 HTML 代碼的圖片文件,若是瀏覽器使用 MIME 嗅探則會將其做爲 HTML 代碼執行,攻擊者就能執行成功的 XSS 攻擊了。

經過設置 header 爲 nosniff 能夠禁用這種 MIME 嗅探。

X-XSS-Protection

此 header 能在用戶瀏覽器中開啓基本的 XSS 防護。它不能避免一切 XSS 攻擊,但它能夠防範基本的 XSS。例如,若是瀏覽器檢測到查詢字符串中包含相似 <script> 標籤之類的內容,則會阻止這種疑似 XSS 攻擊代碼的執行。這個 header 能夠設置三種不一樣的值:011; mode=block。若是你想了解更多關於如何選擇模式的知識,請查看 X-XSS-Protection 及其潛在危害 一文。

升級你的 helmet

以上只是 helmet 提供的默認設置。除此以外,它還可讓你設置 Expect-CTPublic-Key-PinsCache-ControlReferrer-Policy 之類的 header。你能夠在 helmet 文檔 中查找更多相關配置。

保護你的網頁免受非預期內容的侵害

giphy.gif

跨站腳本執行對於 web 應用來講是沒法根絕的威脅。若是攻擊者能夠在你的應用中注入並運行代碼,其後果對於你和你的用戶來講多是一場噩夢。有一種能試圖阻止在你網頁中運行非預期代碼的方案:CSP(內容安全策略)。

CSP 容許你設定一組規則,以定義你的頁面可以加載資源的來源。任何違反規則的資源都會被瀏覽器自動阻止。

你能夠經過修改 Content-Security-Policy HTTP header 來指定規則,或者你不能改 header 時也可使用 meta 標籤來設定。

這個 header 相似於這樣:

Content-Security-Policy: default-src 'none';
    script-src 'nonce-XQY ZwBUm/WV9iQ3PwARLw==';
    style-src 'nonce-XQY ZwBUm/WV9iQ3PwARLw==';
    img-src 'self';
    font-src 'nonce-XQY ZwBUm/WV9iQ3PwARLw==' fonts.gstatic.com;
    object-src 'none';
    block-all-mixed-content;
    frame-ancestors 'none';
複製代碼

在這個例子中,你能夠看到咱們只容許從本身的域名或者 Google Fonts 的 fonts.gstatic.com 來獲取字體;只容許加載本域名下的圖片;只容許加載不指定來源,但必須包含指定 nonce 值的腳本及樣式文件。這個 nonce 值須要用下面這樣的方式指定:

<script src="myscript.js" nonce="XQY ZwBUm/WV9iQ3PwARLw=="></script>
<link rel="stylesheet" href="mystyles.css" nonce="XQY ZwBUm/WV9iQ3PwARLw==" />
複製代碼

當瀏覽器收到 HTML 時,爲了安全起見它會清除全部的 nonce 值,其它的腳本沒法獲得這個值,也就沒法添加進網頁中了。

你還能夠禁止全部在 HTTPS 頁面中包含的 HTTP 混合內容和全部 <object /> 元素,以及經過設置 default-srcnone 來禁用一切不爲圖片、樣式表以及腳本的內容。此外,你還能夠經過 frame-ancestors 來禁用 iframe。

你能夠本身手動去編寫這些 header,不過走運的是 Express 中已經有了許多現成的 CSP 解決方案。helmet 支持 CSP,但 nonce 須要你本身去生成。我我的爲此使用了一個名爲 express-csp-header 的模塊。

安裝及運行 express-csp-header

npm install express-csp-header --save
複製代碼

爲你的 index.js 添加並修改如下內容,啓用 CSP:

const express = require('express');
const helmet = require('helmet');
const csp = require('express-csp-header');

const PORT = process.env.PORT || 3000;
const app = express();

const cspMiddleware = csp({
  policies: {
    'default-src': [csp.NONE],
    'script-src': [csp.NONCE],
    'style-src': [csp.NONCE],
    'img-src': [csp.SELF],
    'font-src': [csp.NONCE, 'fonts.gstatic.com'],
    'object-src': [csp.NONE],
    'block-all-mixed-content': true,
    'frame-ancestors': [csp.NONE]
  }
});

app.use(helmet());
app.use(cspMiddleware);

app.get('/', (req, res) => {
  res.send(`
    <h1>Hello World</h1>
    <style nonce=${req.nonce}>
      .blue { background: cornflowerblue; color: white; }
    </style>
    <p class="blue">This should have a blue background because of the loaded styles</p>
    <style>
      .red { background: maroon; color: white; }
    </style>
    <p class="red">This should not have a red background, the styles are not loaded because of the missing nonce.</p>
  `);
});

app.listen(PORT, () => {
  console.log(`Listening on http://localhost:${PORT}`);
});
複製代碼

重啓服務,訪問 http://localhost:3000,能夠看到一個帶有藍色背景的段落,由於相關的樣式成功被加載了。而另外一個段落沒有樣式,由於其樣式缺乏了 nonce 值。

csp-output.png

CSP header 還能夠設定報告違規的 URL,你還能夠在嚴格啓用 CSP 以前僅開啓報告模式,收集相關數據。

你能夠在 MDN CSP 介紹查看更多信息,並瀏覽 「Can I Use」 網站查看 CSP 兼容性。大多數主流瀏覽器都支持這項特性。

總結

giphy.gif

惋惜的是,在安全性方面不存在所謂的萬能方案,新的漏洞層出不窮。可是,你能夠很輕鬆地在你的 web 應用中設置這些 HTTP header,顯著地提高你應用的安全性,何樂而不爲呢?若是你想了解更多有關 HTTP header 提升安全性的最佳實踐,請瀏覽 securityheaders.io

若是你想了解更多 web 安全方面的最佳實踐,請訪問 Open Web Applications Security Project(OWASP),它涵蓋了普遍的主題及有用的資源。

若是你有任何問題,或有其它用於提高 Node.js web 應用的工具,請隨時聯繫我:


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索