前言 安全性,總是一個不可忽視的問題。許多人都承認這點,但是卻很少有人真的認真地對待它。所以我們列出了這個清單,讓你在將你的應用部署到生產環境來給千萬用戶使用之前,做一個安全檢查。 以下列出的安全項,大多都具有普適性,適用於除了Node.js外的各種語言和框架。但是,其中也包含一些用Node.js寫
前言
安全性,總是一個不可忽視的問題。許多人都承認這點,但是卻很少有人真的認真地對待它。所以我們列出了這個清單,讓你在將你的應用部署到生產環境來給千萬用戶使用之前,做一個安全檢查。
以下列出的安全項,大多都具有普適性,適用於除了Node.js
外的各種語言和框架。但是,其中也包含一些用Node.js
寫的小工具。
配置管理
安全性相關的HTTP頭
以下是一些安全性相關的HTTP頭,你的站點應該設置它們:
-
Strict-Transport-Security
:強制使用安全連接(SSL/TLS之上的HTTPS)來連接到伺服器。 -
X-Frame-Options
:提供對於“點擊劫持”的保護。 -
X-XSS-Protection
:開啟大多現代瀏覽器內建的對於跨站腳本攻擊(XSS)的過濾功能。 -
X-Content-Type-Options
: 防止瀏覽器使用MIME-sniffing
來確定響應的類型,轉而使用明確的content-type
來確定。 -
Content-Security-Policy
:防止受到跨站腳本攻擊以及其他跨站註入攻擊。
在Node.js
中,這些都可以通過使用Helmet模塊輕鬆設置完畢:
var express = require('express');
var helmet = require('helmet');
var app = express();
app.use(helmet());
Helmet
在Koa中也能使用:koa-helmet。
當然,在許多的架構中,這些頭會在Web伺服器(Apache,nginx)的配置中設置,而不是在應用的代碼中。如果是通過nginx配置,配置文件會類似於如下例子:
# nginx.conf
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
add_header Content-Security-Policy "default-src 'self'";
完整的例子可以參考這個nginx配置。
如果你想快速確認你的網站是否都設置這些HTTP頭,你可以通過這個網站線上檢查:http://cyh.herokuapp.com/cyh 。
客戶端的敏感數據
當部署前端應用時,確保不要在代碼中暴露如密鑰這樣的敏感數據,這將可以被所有人看到。
現今並沒有什麼自動化檢測它們的辦法,但是還是有一些手段可以用來減少不小心將敏感數據暴露在客戶端的概率:
-
使用
pull request
更新代碼 -
建立起
code review
機制
身份認證
對於暴力破解的保護
暴力破解即系統地列舉所有可能的結果,並逐一嘗試,來找到正確答案。在web應用中,用戶登陸就特別適合它發揮。
你可以通過限制用戶的連接頻率來防止這類的攻擊。在Node.js
中,你可以使用ratelimiter包。
var email = req.body.email;
var limit = new Limiter({ id: email, db: db });
limit.get(function(err, limit) {
});
當然,你可以將它封裝成一個中間件以供你的應用使用。Express
和Koa
都已經有現成的不錯的中間件:
var ratelimit = require('koa-ratelimit');
var redis = require('redis');
var koa = require('koa');
var app = koa();
var emailBasedRatelimit = ratelimit({
db: redis.createClient(),
duration: 60000,
max: 10,
id: function (context) {
return context.body.email;
}
});
var ipBasedRatelimit = ratelimit({
db: redis.createClient(),
duration: 60000,
max: 10,
id: function (context) {
return context.ip;
}
});
app.post('/login', ipBasedRatelimit, emailBasedRatelimit, handleLogin);
這裡我們所做的,就是限制了在一段給定時間內,用戶可以嘗試登陸的次數 -- 這減少用戶密碼被暴力破解的風險。以上例子中的選項都是可以根據你的實際情景所改變的,所以不要簡單的複製粘貼它們。。
如果你想要測試你的服務在這些場景下的表現,你可以使用hydra。
Session管理
對於cookie的安全使用,其重要性是不言而喻的。特別是對於動態的web應用,在如HTTP這樣的無狀態協議的之上,它們需要使用cookie來維持狀態。
Cookie標示
以下是一個每個cookie可以設置的屬性的列表,以及它們的含義:
-
secure - 這個屬性告訴瀏覽器,僅在請求是通過HTTPS傳輸時,才傳遞cookie。
-
HttpOnly - 設置這個屬性將禁止
javascript
腳本獲取到這個cookie,這可以用來幫助防止跨站腳本攻擊。
Cookie域
-
domain - 這個屬性用來比較請求URL中服務端的功能變數名稱。如果功能變數名稱匹配成功,或這是其子功能變數名稱,則繼續檢查
path
屬性。 -
path - 除了功能變數名稱,cookie可用的URL路徑也可以被指定。當功能變數名稱和路徑都匹配時,cookie才會隨請求發送。
-
expires - 這個屬性用來設置持久化的cookie,當設置了它之後,cookie在指定的時間到達之前都不會過期。
在Node.js
中,你可以使用cookies包來輕鬆創建cookie。但是,它是較底層的。在創建應用時,你可能更像使用它的一些封裝,如cookie-session 。
var cookieSession = require('cookie-session');
var express = require('express');
var app = express();
app.use(cookieSession({
name: 'session',
keys: [
process.env.COOKIE_KEY1,
process.env.COOKIE_KEY2
]
}));
app.use(function (req, res, next) {
var n = req.session.views || 0;
req.session.views = n++;
res.end(n + ' views');
});
app.listen(3000);
(以上例子取自cookie-session模塊的文檔)
CSRF
跨站請求偽造(CSRF)是一種迫使用戶在他們已登錄的web應用中,執行一個並非他們原意的操作的攻擊手段。這種攻擊常常用於那些會改變用戶的狀態的請求,通常它們並不竊取數據,因為攻擊者並不能看到響應的內容。
在Node.js
中,你可以使用csrf模塊來緩和這種攻擊。它同樣是非常底層的,你可能更喜歡使用如csurf這樣的Express
中間件。
在路由層,可以會有如下代碼:
var cookieParser = require('cookie-parser');
var csrf = require('csurf');
var bodyParser = require('body-parser');
var express = require('express');
// setup route middlewares
var csrfProtection = csrf({ cookie: true });
var parseForm = bodyParser.urlencoded({ extended: false });
// create express app
var app = express();
// we need this because "cookie" is true in csrfProtection
app.use(cookieParser());
app.get('/form', csrfProtection, function(req, res) {
// pass the csrfToken to the view
res.render('send', { csrfToken: req.csrfToken() });
});
app.post('/process', parseForm, csrfProtection, function(req, res) {
res.send('data is being processed');
});
在展示層,你需要使用CSRF token
:
<form action="/process" method="POST">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
Favorite color: <input type="text" name="favoriteColor">
<button type="submit">Submit</button>
</form>
(以上例子取自csurf模塊的文檔)
數據合法性
XSS
以下是兩種類似的,但是略有不同的攻擊方式,一種關於跨站腳本,而另一種則關於存儲。
-
非持久化的XSS攻擊 在攻擊者向指定的URL的響應HTML中註入可執行的
JavaScript
代碼時發生。 -
持久化的XSS攻擊 在應用存儲未經過濾的用戶輸入時發生。用戶輸入的代碼會在你的應用環境下執行。
為了防禦這類攻擊,請確保你總是檢查並過濾了用戶的輸入內容。
SQL註入
在用戶的輸入中包含部分或完整的SQL查詢語句時,SQL註入就有可能發生。它可能會讀取敏感數據,或是直接刪除數據。
例如:
select title, author from books where id=$id
以上這個例子中,$id
來自於用戶輸入。用戶輸入2 or 1=1
也可以。這個查詢可能會變成:
select title, author from books where id=2 or 1=1
最簡單的預防方法則是使用參數化查詢(parameterized queries)或預處理語句(prepared statements)。
如果你正在通過Node.js
使用PostgreSQL
。那麼你可以使用node-postgres模塊,來創建參數化查詢:
var q = 'SELECT name FROM books WHERE id = $1';
client.query(q, ['3'], function(err, result) {});
命令註入
攻擊者使用命令註入來在遠程web伺服器中運行系統命令。通過命令註入,攻擊者甚至可以取得系統的密碼。
實踐中,如果你有一個URL:
https://example.com/downloads?file=user1.txt
它可以變成:
https://example.com/downloads?file=%3Bcat%20/etc/passwd
在這個例子中,%3B
會變成一個分號。所以將會運行多條系統命令。
為了預防這類攻擊,請確保總是檢查過濾了用戶的輸入內容。
我們也可以以Node.js
的角度來說:
child_process.exec('ls', function (err, data) {
console.log(data);
});
在child_process.exec
的底層,它調用了/bin/sh
,所以它是一個bash
解釋器,而不僅僅是只能執行用戶程式。
當用戶的輸入是一個反引號或$()
時,將它們傳入這個方法就很危險了。
可以通過使用child_process.execFile
來解決上面這個問題。
安全傳輸
SSL版本,演算法,鍵長度
由於HTTP是明文傳輸的,所以我們需要通過一個SSL/TLS通道來加密,即HTTPS。如今高級別的加密方式已被普遍使用,但是,如果在服務端缺乏配置,也可能會導致服務端使用低級別的加密,或不加密。
你需要測試:
-
密碼,密鑰和重協商(renegotiation)都已經合法妥善得配置完畢。
-
證書的合法性。
使用如nmap
和sslyze
這樣的工具可以使這項工作非常簡單。
檢查證書信息
nmap --script ssl-cert,ssl-enum-ciphers -p 443,465,993,995 www.example.com
使用sslyze
來檢查SSL/TSL:
./sslyze.py --regular example.com:443
HSTS
在上文的配置管理章節我們已經對其有了接觸 - Strict-Transport-Security
頭會強制使用HTTPS來連接伺服器。以下是一個Twitter的例子:
strict-transport-security:max-age=631138519
這裡的max-age
定義了瀏覽器需要自動將所有HTTP請求轉換成HTTPS的秒數。
對於它的測試是非常簡單的:
curl -s -D- https://twitter.com/ | grep -i Strict
拒絕服務
賬號鎖定
賬號鎖定用於緩和暴力破解帶來的拒絕服務方面的影響。實踐中,它意味著,當用戶嘗試了幾次登陸並失敗後,將在其後的一段內,禁止他的登陸操作。
可以使用之前提到的rate-limiter
來阻止這類攻擊。
正則表達式
這類攻擊主要是由於一些正則表達式,在極端情況下,會變得性能及其糟糕。這些正則被稱為惡魔正則(Evil Regexes):
-
對於重覆文本進行分組
-
在重覆的分組內又有重覆內容
([a-zA-Z]+)*
,(a+)+
或(a|a?)+
在如aaaaaaaaaaaaaaaaaaaaaaaa!
這樣的輸入面前,都是脆弱的。這會引起大量的計算。更多詳情可以參考ReDos。
可以使用Node.js
工具safe-regex這檢測你的正則:
$ node safe.js '(beep|boop)*'
true
$ node safe.js '(a+){10}'
false
錯誤處理
錯誤碼,堆棧信息
一些錯誤場景可能會導致應用泄露底層的應用架構信息,如:like: X-Powered-By:Express
。
堆棧信息可能自己本身並沒有什麼用,但它經常能泄露一些攻擊者非常感興趣的信息。將堆棧信息返回出來是非常不好的實踐。你需要將它們記錄在日誌中,而不是展示給用戶。
NPM
更強的能力意味著更大的責任 - NPM有這許多可以現成使用的包,但是代價是:你需要檢查這些包本身是否存在安全問題。
幸運的是Node Security project
(nsp)是一個非常棒的工具,來檢查你使用的模塊是否是易被一些已知的手段攻擊的。
npm i nsp -g
# either audit the shrinkwrap
nsp audit-shrinkwrap
# or the package.json
nsp audit-package
最後
這個清單主要根據OWASP
維護的Web Application Security Testing Cheat Sheet所列。