配置管理
安全HTTP頭
- Strict-Transport-Security 強制實施與服務器的安全(HTTP over SSL / TLS)連接
- X-Frame-Options 提供點擊劫持保護
- X-XSS-Protection 支持在最新的Web瀏覽器中內置的跨站點腳本(XSS)過濾器
- X-Content-Type-Options 可防止瀏覽器從聲明的內容類型中嗅探響應
- Content-Security-Policy 可防止各種攻擊,包括跨站點腳本和其他跨站點注入
可使用helmet模塊快捷設置
const express = require('express')
const helmet = require('helmet')
const app = express()
app.use(helmet())
// ...
使用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'";
客戶端的敏感數據
注意不要在客戶端代碼中存放敏感數據
認證
暴力(破解)保護
可以使用ratelimiter模塊限制登錄頻率
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);
會話管理
安全使用cookie的重要性不容低估:特別是在動態Web應用程序中,需要在HTTP等無狀態協議中維護狀態。
Cookie標志
- secure - 此屬性告訴瀏覽器僅在通過HTTPS發送請求時才發送cookie。
- HttpOnly - 此屬性用於幫助防止跨站點腳本等攻擊,因為它不允許通過JavaScript訪問cookie。
Cookie范圍
- domain - 此屬性用於與請求URL的服務器的域進行比較。如果域匹配或者它是子域,則接下來將檢查路徑屬性。
- path - 除了域之外,還可以指定cookie有效的URL路徑。如果域和路徑匹配,則cookie將在請求中發送。
- expires - 此屬性用於設置持久性cookie,因為cookie在超過設置日期之前不會過期
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);
CSRF
跨站點請求偽造是一種攻擊,迫使用戶在他們當前登錄的Web應用程序上執行不需要的操作。這些攻擊專門針對狀態更改請求,而不是數據被盜,因為攻擊者無法訪問看到對偽造請求的回應。
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');
});
<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>
數據驗證
XSS
一種是跨站點腳本的反射版本,另一種是存儲。
當攻擊者使用特制鏈接將可執行JavaScript代碼注入HTML響應時,會發生反射的跨站點腳本(Reflected Cross site scripting)。
當應用程序存儲未正確過濾的用戶輸入時,將發生存儲的跨站點腳本(Stored Cross site scripting)。它在Web應用程序的特權下在用戶的瀏覽器中運行。
要防御這種攻擊,請確保始終過濾/清理用戶輸入。
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
防御這類攻擊的最簡單方法是使用參數化查詢或預處理語句。
如果用的是PostgreSQL,那么可以使用node-postgres模塊。
創建參數化查詢:
var q = 'SELECT name FROM books WHERE id = $1';
client.query(q, ['3'], function(err, result) {});
sqlmap是一個開源的滲透測試工具,可以自動檢測和利用SQL注入漏洞並接管數據庫服務器。使用此工具測試應用程序的SQL注入漏洞。
命令注入
命令注入是攻擊者用於在遠程Web服務器上運行OS命令的技術。使用這種方法,攻擊者甚至可能會獲得系統密碼。
在實踐中,如果你有一個像這樣的URL:
https://example.com/downloads?file=user1.txt
它可以變成:
https://example.com/downloads?file=%3Bcat%20/etc/passwd
在此示例中,%3B
將成為分號,因此可以運行多個OS命令。
要防御這種攻擊,請確保始終過濾/清理用戶輸入。
另外在nodejs中
child_process.exec('ls', function (err, data) {
console.log(data);
});
在hood下child_process.exec
調用執行/bin/sh
,因此它是一個bash解釋器而不是程序啟動器。
當用戶輸入傳遞給此方法時,這是有問題的 - 可以是反引號$()
,也可以是攻擊者注入的新命令。
要克服這個問題,只需使用child_process.execFile
。
安全傳輸
SSL版本,算法,密鑰長度
由於HTTP是明文協議,因此必須通過SSL/TLS
隧道(稱為HTTPS)進行保護。如今通常使用高級密碼,服務器中的錯誤配置可用於強制使用弱密碼 - 或者最壞的情況下不加密。
使用nmap檢查證書信息
nmap --script ssl-cert,ssl-enum-ciphers -p 443,465,993,995 www.example.com
使用sslyze測試SSL/TLS
漏洞
./sslyze.py --regular example.com:443
HSTS
在配置管理部分中,我們簡要地觸及了這一點 - Strict-Transport-Security
標頭強制實施與服務器的安全(HTTP over SSL / TLS)連接。從Twitter獲取以下示例:
strict-transport-security:max-age=631138519
這里max-age定義了瀏覽器應自動將所有HTTP請求轉換為HTTPS的秒數。
測試非常簡單:
curl -s -D- https://twitter.com/ | grep -i Strict
拒絕服務
帳戶鎖定
帳戶鎖定是一種減輕暴力猜測攻擊的技術。在實踐中,這意味着在少量不成功的登錄嘗試之后,系統禁止在給定時間段內進行登錄嘗試(最初可能是幾分鍾,然后它可以指數增加)。
可以使用之前觸及的速率限制器模式來保護的應用程序免受這類攻擊。
正則表達式
這種攻擊利用了這樣一個事實,即大多數正則表達式實現可能會達到極端情況,導致它們工作得非常慢。這些正則表達式被稱為邪惡的正則表達式:
- 重復分組
- 在重復組內
- 重復
- 交替重疊
([a-zA-Z]+)*
,(a+)+
或者(a|a?)+
都是易受攻擊的正則表達式作為簡單的輸入,aaaaaaaaaaaaaaaaaaaaaaaa!
可能導致繁重的計算。
要檢查Regex對這些,可以使用名為safe-regex的Node.js工具。它可能會產生誤報,因此請謹慎使用。
$ node safe.js '(beep|boop)*'
true
$ node safe.js '(a+){10}'
false
錯誤處理
錯誤代碼,堆棧跟蹤
在不同的錯誤情況的應用程序可能會泄漏對底層基礎架構的敏感細節,如:X-Powered-By:Express
。
堆棧跟蹤本身不被視為漏洞,但它們通常會泄露攻擊者可能感興趣的信息。由於產生錯誤的操作而提供調試信息被認為是不好的做法。應該始終記錄它們,但不要向用戶顯示它們。
NPM
強大的功能帶來了很大的責任 - NPM有很多可以立即使用的軟件包,但需要付出代價:應該檢查應用程序需要什么。它們可能包含至關重要的安全問題。
node安全項目
幸運的是,Node Security project有一個很棒的工具nsp,可以檢查使用過的模塊是否存在已知漏洞。
npm i nsp -g
# either audit the shrinkwrap
nsp audit-shrinkwrap
# or the package.json
nsp audit-package
Snyk
Snyk類似於Node Security Project,但其目的是提供一種工具,不僅可以檢測,還可以修復代碼庫中與安全相關的問題。
HTTP Basic authentication
HTTP基本身份驗證是客戶端在發出請求時提供用戶名和密碼的方法。
這是實施訪問控制的最簡單方法,因為它不需要cookie,會話或其他任何東西。要使用它,客戶端必須發送Authorization標頭以及它所做的每個請求。用戶名和密碼未加密,但以這種方式構造:
- 用戶名和密碼連接成一個字符串:
username:password
- 此字符串使用Base64編碼
- 該
Basic
關鍵字此編碼值前放
john:secret
base64編碼成am9objpzZWNyZXQ=
curl --header "Authorization: Basic am9objpzZWNyZXQ=" my-website.com
import basicAuth from 'basic-auth';
function unauthorized(res) {
res.set('WWW-Authenticate', 'Basic realm=Authorization Required');
return res.send(401);
};
export default function auth(req, res, next) {
const {name, pass} = basicAuth(req) || {};
if (!name || !pass) {
return unauthorized(res);
};
if (name === 'john' && pass === 'secret') {
return next();
}
return unauthorized(res);
};
也可以通過nginx配置
缺點:
- 用戶名和密碼隨每個請求一起發送,可能會暴露它們 - 即使是通過安全連接發送的
- 連接到SSL / TLS,如果網站使用弱加密,或者攻擊者可以破解它,則會立即暴露用戶名和密碼
- 沒有辦法使用Basic auth注銷用戶
- 憑證到期並非易事 - 您必須要求用戶更改密碼才能這樣做
Cookies
當服務器在響應中收到HTTP請求時,它可以發送Set-Cookie
標頭。瀏覽器將其放入cookie jar中,並且cookie將與每個對CookieHTTP頭中相同源的請求一起發送。
要使用cookie進行身份驗證,必須遵循一些關鍵原則。
始終使用HttpOnly cookie
為了減輕XSS攻擊的可能性,HttpOnly在設置cookie時始終使用該標志。這樣他們就不會出現了document.cookies。
始終使用簽名的cookie
使用簽名cookie,服務器可以判斷客戶端是否修改了cookie。
這也可以在Chrome中觀察到 - 首先讓我們看一下服務器如何設置cookie:
稍后,所有請求都使用為給定域設置的cookie:
缺點:
- 需要付出額外的努力來緩解CSRF攻擊
- 與REST不兼容 - 因為它將狀態引入無狀態協議
令牌(Tokens)
如今JWT(JSON Web Token)無處不在 - 仍值得研究潛在的安全問題。
JWT由三部分組成:
- 標頭(Header),包含令牌類型和散列算法
- 有效負載(Payload),包含 claims
- 簽名(Signature),如果選擇HMAC SHA256,可以按如下方式計算:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
var koa = require('koa');
var jwt = require('koa-jwt');
var app = koa();
app.use(jwt({
secret: 'very-secret'
}));
// Protected middleware
app.use(function *(){
// content of the token will be available on this.state.user
this.body = {
secret: '42'
};
});
curl --header "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ" my-website.com
與之前的一樣,也可以在Chrome中觀察到令牌:
在瀏覽器中使用JWT,必須將其存儲在LocalStorage或SessionStorage中,這可能導致XSS攻擊。
缺點: 需要額外努力來緩解XSS攻擊
簽名(Signatures)
使用cookies或tokens,如果傳輸層因任何原因被暴露,你的憑據(credentials)很容易訪問 - 並且使用tokens或cookie,攻擊者可以像真實用戶一樣行事。
解決這個問題的一種可能方法 - 在討論API時而不是瀏覽器時,請簽署每個請求。
當API的消費者發出請求時,它必須對其進行簽名,這意味着它必須使用私鑰從整個請求創建散列。
對於該哈希計算,可以使用:
- HTTP方法
- 請求的路徑
- HTTP標頭
- HTTP有效負載的校驗和(Checksum of the HTTP payload)
- 和一個私鑰來創建哈希(and a private key to create the hash)
為了使其工作,API的使用者和提供者都必須具有相同的私鑰。獲得簽名后,您必須將其添加到請求中,無論是查詢字符串還是HTTP標頭。此外,還應添加日期,以便可以定義到期日期。
為什么要經歷所有這些步驟?因為即使傳輸層受到攻擊,攻擊者也只能讀取你的流量,無法充當用戶,因為攻擊者無法簽署請求 - 因為私鑰不在ta的擁有中。大多數AWS服務都使用這種身份驗證。
可以嘗試使用node-http-signature處理HTTP請求簽名。
缺點:不能在瀏覽器/客戶端中使用,只能在API之間使用
一次性密碼
一次性密碼算法生成一次性密碼,其中包含共享密鑰以及當前時間或計數器:
- 基於時間的一次性密碼算法,基於當前時間。
- 基於HMAC的一次性密碼算法,基於計數器。
這些方法用於利用雙因素身份驗證的應用程序:用戶輸入用戶名和密碼,然后服務器和客戶端都生成一次性密碼。
在Node.js中,使用notp實現它相對容易。
缺點:
- 使用共享密鑰(如果被盜)可以模擬用戶令牌
- 因為客戶端可能被竊取/出錯每個實時應用程序都有方法繞過這個,比如電子郵件重置會為應用程序添加額外的攻擊向量
該在何時選擇何種驗證方法?
如果只需支持一個web應用,那么cookies和tokens的實現都是可以的(cookies對XSRF的防護較好,而JWT則更易於防護XSS)。
如果需要同時支持web應用和移動客戶端,那么請使用基於token的驗證。
如果正在構建僅與其他API通信的API,那么就使用signatures。
SSL
Secure Sockets Layer,這是其全名,他的作用是協議,定義了用來對網絡發出的數據進行加密的格式和規則。
+------+ +------+
服務器 | data | -- SSL 加密 --> 發送 --> 接收 -- SSL 解密 -- | data | 客戶端
+------+ +------+
+------+ +------+
服務器 | data | -- SSL 解密 --> 接收 <-- 發送 -- SSL 加密 -- | data | 客戶端
+------+ +------+
協議
所謂協議,是程序員在編程時,大家對發送的數據格式采用的一種共同結構,也就是標准數據結構。
比如, HTTP 請求頭的協議采用
GET /demo HTTP/1.1\n
Host: www.google.com\n
Connection: keep-alive\n
這樣的格式,每一個廠商在開發的網絡軟件都遵循這一標准。這樣在另一個接受端,可以采用相同的解析代碼來編程。
OpenSSL
OpenSSL 是在程序上對 SSL 標准的一個實現,提供了:
- libcrypto 通用加密庫
- libssl TLS/SSL 的實現
- openssl 命令行工具
程序員可以通過免費開源的 OpenSSL 庫來對自己的應用程序提供 SSL 加密。OpenSSL 由 Eric A. Young 和 Tim J. Hudson 在 1995 年提出。1998年,OpenSSL 項目組接管,並制定標准規范。
SSH
telnet是一個不安全的通過命令行進行服務器客戶端通信的工具。
SSH 是使用 OpenSSL 加密的這樣的通信工具,提供了更多安全功能。
服務器安裝 $ sudo aptitude install openssh-server
服務器啟動 $ sudo service ssh start
客戶端連接 $ ssh qm@192.168.1.101
Node.js 是完全采用 OpenSSL 進行加密的,其相關的 TLS HTTPS 服務器模塊和 Crypto 加密模塊都是通過 C++ 在底層調用 OpenSSL 。
OpenSSL --> crypto --> tls --> https
OpenSSL 實現了對稱加密:
AES(128) | DES(64) | Blowfish(64) | CAST(64) | IDEA(64) | RC2(64) | RC5(64)
非對稱加密:
DH | RSA | DSA | EC
以及一些信息摘要:
MD2 | MD5 | MDC2 | SHA | RIPEMD | DSS
其中信息摘要是一些采用哈希算法的加密方式,也意味着這種加密是單向的,不能反向解密。這種方式的加密大多是用於保護安全口令,比如登錄密碼。這里面最長用的是 MD5 和 SHA (建議采用更穩定的 SHA1)。