Day13 - 替你的 Express 戴上頭盔吧!

替你的 Express 戴上頭盔吧

前言

前面介紹了 CORS 跟 Env 可以說是基本的基本,接下來要來介紹一個很特別的東西,叫做 Helmet。

Helmet 是什麼?

開始說明 Helmet 之前,我們要先釐清一件事情,也就是關於「網路安全」的事情。

網路資訊安全是一個非常大的議題,當然這一篇不可能全部都介紹到,畢竟網路資訊安全包含了個人註冊帳號密碼時的註冊規則等,因此這一篇只會著重於後端的安全性。

Helmet 其實在官方文件中有特別提到,Helmet 是一個幫助你保護你的 Express 應用程式的中介軟體,它會設定一些 HTTP 標頭,這些標頭可以幫助你的應用程式避免一些已知的網路攻擊。

簡單來講,Helmet 就是在替你的 Express 戴上頭盔(Helmet),讓你的應用程式可以避免一些已知的網路攻擊,就如同一個人戴上頭盔,可以避免一些已知的傷害。

Helmet 做了哪些事情?

預設引入 app.use(helmet()); 的狀況下,它會自動幫你在 HTTP 標頭中加入一些東西,而這些東西是可以幫助你的應用程式避免一些已知的網路攻擊。

隱藏 X-Powered-By 標頭

在預設的情況下,Express 會在 HTTP 標頭中加入 X-Powered-By: Express,這個標頭會讓攻擊者知道你的應用程式是使用 Express 來開發的,因此攻擊者可以針對 Express 來進行攻擊。

Note
實戰開發上會避免讓攻擊者知道你的應用程式是使用什麼來開發的,因為當該應用程式爆出漏洞時,攻擊者就可以針對該漏洞來進行攻擊,因此基本的隱藏是必要的;早期我在接觸 WordPress 時,就有遇到過攻擊者針對 WordPress 的漏洞來進行攻擊,因此這點是非常重要的。

而 Helmet 會幫你隱藏這個標頭,因此攻擊者就無法知道你的應用程式是使用 Express 來開發的。

不信?你可以試著用前面所練習的 Express 用 Postman 試戳一下,你會看到以下的結果

X-Powered-By

減緩點擊劫持

Clickjacking(點擊劫持)是一種攻擊,簡單來講就是誘騙你在網頁上點擊某個元素(通常是按鈕或連結),基本上就是在網頁上做了一個透明的覆蓋層,讓你以為你點擊的是某個元素,但實際上點擊的是另一個元素。

而 Helmet 會幫你在 HTTP 標頭中加入 X-Frame-Options,這個標頭可以讓你的網頁在被嵌入到其他網頁時,可以避免被點擊劫持。

如果不好懂的話,我們可以看一下範例程式碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- 底下是一個惡意 HTML From 結構-->
<!DOCTYPE html>
<html>
<head>
<title>惡意網頁</title>
</head>
<body>
<h1>點擊這裡贏取大獎!</h1>
<iframe
src="駭客的網站"
style="opacity: 0; position: absolute; top: 0; left: 0; width: 100%; height: 100%;"
frameborder="0">
</iframe>
</body>
</html>

因此當你點擊「點擊這裡贏取大獎!」時,實際上你點擊的是駭客的網站,而不是你想要的網站。

而 Helmet 會幫你在 HTTP 標頭中加入 X-Frame-Options,這個標頭可以讓你的網頁在被嵌入到其他網頁時,可以避免被點擊劫持。

Note
這個標頭有三種設定方式,分別是 DENYSAMEORIGINALLOW-FROM,而預設的設定是 SAMEORIGIN,也就是只有同源的網頁才可以嵌入,詳細你可以參考這裡

跨站請求偽造

跨站請求偽造可能比較多人看到,中文若你不熟悉的話,或許 CSRF 會比較熟悉,這是一種攻擊,簡單來講就是攻擊者會在你的網頁中加入一些惡意的東西,當你點擊時,就會觸發攻擊者的惡意程式碼。

而關於這一點我就不多說明了,畢竟我之前有寫文章說明過了

早期防禦的方式是在表單中加入 CSRF Token,但現今主流開發模式是前後端分離,因此就會改用所謂的 Client Double Submit Cookie,而這個方式是在 Cookie 中加入 CSRF Token,然後在 HTTP 標頭中加入 CSRF Token,當後端收到請求時,就會比對 Cookie 中的 CSRF Token 跟 HTTP 標頭中的 CSRF Token 是否相同,如果相同就代表是合法的請求,否則就是非法的請求,實作方式可以參考這裡

跨站腳本攻擊

這個其實在一般開發上是滿常遇到的攻擊手法,又稱之為跨網站指令碼攻擊,簡稱 XSS,通常比較常見於 Input 欄位,攻擊者會在 Input 欄位中輸入一些惡意的指令碼,當你點擊送出後,這一段惡意的指令碼就會在某個地方執行,而這個地方可以是你的網頁,也可以是其他網頁。

例如你的圖片 src 屬性來源是使用者輸入的,而使用者輸入的內容是惡意的指令碼,當你的網頁載入時,這段惡意的指令碼就會被執行,概念就像是使用者在 input 輸入以下

1
<script>alert('哈哈,你被 XSS 攻擊囉!');</script>

接著渲染出來的結果就會是

1
<img src="#" onerror="alert('哈哈,你被 XSS 攻擊囉!');">

其他 Helmet

當然不只有以上這些,Helmet 還有其他的東西,例如:

  • Content-Security-Policy
  • Cross-Origin-Opener-Policy
  • Cross-Origin-Resource-Policy
  • Origin-Agent-Cluster
  • Referrer-Policy
  • Strict-Transport-Security
  • X-Content-Type-Options
  • X-DNS-Prefetch-Control
  • X-Download-Options
  • X-Frame-Options
  • X-Permitted-Cross-Domain-Policies
  • X-Powered-By
  • X-XSS-Protection

這些都是可以幫助你的應用程式避免一些已知的網路攻擊,當然這些都是可以自行設定的,如果你想要了解更多的話,可以參考官方文件

該如何在 Express 中使用 Helmet?

Helmet 的使用方式非常簡單,只需要在你的專案中安裝 Helmet 套件:

1
npm install helmet

接著在你的程式碼中引用 Helmet:

1
2
3
4
5
6
7
8
9
10
11
12
const express = require('express');
const helmet = require('helmet');

const app = express();

// ...略過其他程式碼

app.use(helmet());

// ...略過其他程式碼

app.listen(3000);

其引用的方式跟其他的中介軟體一樣,只需要在 app.use() 中加入即可。

Note
如果你覺得從頭建立 Express 很煩的話,官方其實也有出一個叫做 Express Generator 的東西,可以幫你快速建立一個 Express 專案唷。

基本上預設 Helmet 會幫你加入前面所提到的東西,但如果你想要自行設定的話,也是可以的,例如你想要自行設定 X-Frame-Options,你可以這樣子寫:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const express = require('express');
const helmet = require('helmet');

const app = express();

// ...略過其他程式碼

app.use(
helmet({
frameguard: {
xFrameOptions: { action: "deny" }, // 不然就是 sameorigin
},
})
);

// ...略過其他程式碼

app.listen(3000);

如果不想要使用某個功能的話,也是可以的,例如你不想要使用 X-Frame-Options 的話,你可以這樣子寫:

1
2
3
4
5
app.use(
helmet({
xFrameOptions: false,
})
);

那假設我只想要引入 X-Powered-By 來刪除 Express(或其他)的標頭,但其他的都不想要引入,你可以這樣子寫:

1
app.use(helmet.xPoweredBy());

這樣子就只會引入 X-Powered-By 這個功能,其他的都不會引入。

Note
其實 Express 官方的 Issues 曾經爭論過要不要把 X-Powered-By 這個功能拿掉,詳情可見 Security improvement: don’t reveal powered-by

結論

雖然 Helmet 已經盡可能幫你的 Express 戴上頭盔,但也千萬不要認為 Helmet 是一個萬靈藥,畢竟資安事件層出不窮,而且攻擊者也會不斷的找尋漏洞,因此我們還是要多加注意,不要因為 Helmet 的存在,而忽略了資安的重要性唷。

中箭