Express框架(15) CSURF - 阻擋跨站攻擊

跨站請求偽造(CSRF)

跨站請求偽造(英語:Cross-site request forgery),是攻擊者通過一些技術手段欺騙使用者的瀏覽器去訪問一個自己曾經認證過的網站並執行一些操作(如發郵件,發訊息,甚至財產操作如轉帳和購買商品)。由於瀏覽器曾經認證過,所以被訪問的網站會認為是真正的使用者操作而去執行。這利用了web中使用者身分驗證的一個漏洞:簡單的身分驗證只能保證請求是發自某個使用者的瀏覽器,卻不能保證請求本身是使用者自願發出的。

  • 例子

假如一家銀行用以執行轉帳操作的URL位址如下: https://bank.example.com/withdraw?account=AccoutName&amount=1000&for=PayeeName

那麼,一個惡意攻擊者可以在另一個網站上放置如下代碼: <img src="https://bank.example.com/withdraw?account=Alice&amount=1000&for=Badman" />

如果有賬戶名為Alice的使用者訪問了惡意站點,而她之前剛訪問過銀行不久,登錄資訊尚未過期,那麼她就會損失1000資金。

這種惡意的網址可以有很多種形式,藏身於網頁中的許多地方。此外,攻擊者也不需要控制放置惡意網址的網站。例如他可以將這種位址藏在論壇,部落格等任何使用者供應內容的網站中。這意味著如果伺服器端沒有合適的防禦措施的話,使用者即使訪問熟悉的可信網站也有受攻擊的危險。

防禦措施

在nodejs專案,可以下載 secure-csrf 這個套件。

1
npm install csrf-tokens --save
  1. 引入 csrf ,等待promise回傳的 secure uuid
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import express, { Request, Response, NextFunction } from 'express';
import CSRF from 'csrf-tokens';

const app = express();

app.use(async(req: Request, res: Response, next: NextFunction) => {
try {
const csrf = new CSRF();
const secret = await csrf.secret();
next();
} catch(error) {
return next(error);
}
});
  1. 產生 csrf token
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import express, { Request, Response, NextFunction } from 'express';
import CSRF from 'csrf-tokens';

const app = express();

// 產生 csrf 的 token 跟 secret
const csrfProtection = async(req: Request, res: Response, next: NextFunction) => {
try {
const csrf = new CSRF();
const secret = await csrf.secret();
const token = csrf.create(secret);
req.body.secret = secret;
req.body.token = token;
next();
} catch(error) {
return next(error);
}
}

// 將token ,secret 帶入需要保護的頁面,如表單頁面
app.get('/form', csrfProtection, function(req, res) {
res.render('/form', {
secret: req.body.secret,
token: req.body.token,
})
})
  1. 驗證 token
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import express, { Request, Response, NextFunction } from 'express';
import CSRF from 'csrf-tokens';

const app = express();

app.use(async(req: Request, res: Response, next: NextFunction) => {
try {
const csrf = new CSRF();
const secret = await csrf.secret();
csrf.verify(req.session.secret, req.body.token);
next();
} catch(error) {
return next(error);
}
});