當專案周期快結束時,開發人員會越來越關注應用的“安全性”問題,一個安全的應用程式并不是一種奢侈,而是必要的,你應該在開發的每個階段都考慮應用程式的安全性,例如系統架構、設計、編碼,包括最后的部署,
在這篇教程中,我們將一步步來學習如何提高Node.js應用程式安全性的方法,
1. 資料驗證 - 永遠不要信任你的用戶
來自用戶輸入或其他系統的資料,你都必須要進行驗證,否則,這會對當前系統造成威脅,并導致不可想象的安全漏洞,現在,讓我們學習如何驗證Node.js中的傳入資料,你可以使用名為validator的模塊來執行資料驗證, 例如:
const validator = require('validator');
validator.isEmail('[email protected]'); //=> true
validator.isEmail('bar.com'); //=> false
另外,你也可以使用joi模塊來進行資料和模型的驗證,例如:
const joi = require('joi');
try {
const schema = joi.object().keys({
name: joi.string().min(3).max(45).required(),
email: joi.string().email().required(),
password: joi.string().min(6).max(20).required()
});
const dataToValidate = {
name: "Shahid",
email: "abc.com",
password: "123456",
}
const result = schema.validate(dataToValidate);
if (result.error) {
throw result.error.details[0].message;
}
} catch (e) {
console.log(e);
}
2. SQL注入攻擊
SQL注入可以讓惡意用戶通過傳遞非法引數,來篡改SQL陳述句,下面是一個例子,假設你寫了這樣一個SQL:
UPDATE users
SET first_name="' + req.body.first_name + '" WHERE id=1332;
在正常情況下,你希望這次查詢應該是這樣的:
UPDATE users
SET first_name = "John" WHERE id = 1332;
但是現在,如果有人將first_name的值按下面這種方式傳遞:
John", last_name="Wick"; --
這時,你的SQL陳述句就會變成這樣:
UPDATE users
SET first_name="John", last_name="Wick"; --" WHERE id=1001;
你會看到,WHERE條件被注釋掉了,這次更新會將整張表所有用戶的first_name改成John,last_name改成Wick,這下,你闖禍了!
如何避免SQL注入
避免SQL注入攻擊最有效的辦法就是將輸入資料進行過濾,你可以對每一個輸入資料逐一進行驗證,也可以用引數系結的方式驗證,開發者們最常用的就是引數系結的方式,因為它高效而且安全,
如果你在使用一些比較流行的ORM框架,例如sequelize、hibernate等等,那么框架中就已經提供了這種資料驗證和SQL注入保護機制,
如果你更喜歡依賴資料庫模塊,例如mysql for Node,那么你可以使用資料庫提供的過濾方法,下面代碼是使用mysql for Node的一個例子:
var mysql = require('mysql');
var connection = mysql.createConnection({
host : 'localhost',
user : 'me',
password : 'secret',
database : 'my_db'
});
connection.connect();
connection.query(
'UPDATE users SET ?? = ? WHERE ?? = ?',
['first_name',req.body.first_name, ,'id',1001],
function(err, result) {
//...
});
??的地方被欄位名稱替換,?的地方被欄位值替換,這樣就保證了輸入值的安全性,
你也可以使用存盤程序來提高安全級別,但是由于缺乏可維護性,開發人員傾向于避免使用存盤程序,
同時,你還應該執行服務器端的資料驗證, 但我不建議你手動驗證每個欄位,可以使用joi等模塊來解決這個問題,
型別轉換
JavaScript是一種動態型別語言,即值可以是任何型別,你可以使用型別轉換方法來驗證資料的型別,這樣就能保證,只有指定型別的資料才可以進入資料庫,比如,用戶ID只能是數字型別,看下面的代碼:
var mysql = require('mysql');
var connection = mysql.createConnection({
host : 'localhost',
user : 'me',
password : 'secret',
database : 'my_db'
});
connection.connect();
connection.query(
'UPDATE users SET ?? = ? WHERE ?? = ?',
['first_name',req.body.first_name, ,'id',Number(req.body.ID)],
function(err, result) {
//...
});
你注意到變化了嗎?這里我們使用了Number(req.body.ID)方法來確保用戶ID必須為數字,
3. 應用程式認證和授權
敏感資料(例如密碼)應該以一種安全的方式存盤在系統中,這樣,惡意用戶就不會濫用敏感資訊,在本節中,我們將學習如何存盤和管理通用的密碼,幾乎每個應用程式在其系統中都有不同的密碼存盤方式,
密碼哈希
哈希是一個將輸入值生成固定大小字串的函式,哈希函式的輸出值是無法解密的,因此可以說是“單向的”,因此,像密碼這樣的資料,存盤在資料庫中的值必須是哈希值,而不是明文,
你也許想知道,既然哈希是一種不可逆的加密方式,那么攻擊者又是如何取得密碼訪問權限的呢?
正如我上面提到的那樣,哈希加密使用輸入字串并生成固定長度的輸出值,因此,攻擊者采取了相反的方法,他們從常規密碼串列中生成哈希,然后將哈希與系統中的哈希進行比較以找到密碼,這種攻擊方式叫做查表法(Lookup Tables)
這就是為什么你作為系統架構師,絕不允許系統中使用簡單通用密碼的原因,為了避免攻擊,你也可以使用一種叫”salt"的東西,我們稱之為“哈希加鹽法”,將salt附加到密碼哈希中,從而使輸入值唯一,salt值必須是隨機的不可預測的,我們建議你使用的哈希演算法是BCrypt,在Node.js中,你可以使用bcyrpt節點模塊執行哈希處理,
請參考下面例子中的代碼:
const bcrypt = require('bcrypt');
const saltRounds = 10;
const password = "Some-Password@2020";
bcrypt.hash(
password,
saltRounds,
(err, passwordHash) => {
//we will just print it to the console for now
//you should store it somewhere and never logs or print it
console.log("Hashed Password:", passwordHash);
});
SaltRounds函式是哈希函式的成本,成本越高,生成的hash密碼越安全,你應該根據服務器的計算能力來確定salt值,密碼的hash值生成后,用戶輸入的密碼將會和存盤在資料庫中的hash值比對,參考代碼如下:
const bcrypt = require('bcrypt');
const incomingPassword = "Some-Password@2020";
const existingHash = "some-hash-previously-generated"
bcrypt.compare(
incomingPassword,
existingHash,
(err, res) => {
if(res && res === true) {
return console.log("Valid Password");
}
//invalid password handling here
else {
console.log("Invalid Password");
}
});
密碼存盤
無論你是用資料庫還是檔案來存盤密碼,都不能使用明文存盤,我們在上一節已經學到,你可以將密碼進行哈希后存盤在資料庫中,我推薦密碼欄位用varchar(255)資料型別,你也可以選擇不限長度的欄位型別,如果你使用的是bcrypt,則可以使用varchar(60)欄位型別,因為bcrypt會生成固定長度為60個字符的哈希,
認證和授權
一個擁有合適的角色權限系統,將會阻止一些惡意用戶在系統中做一些越權的事情,為了實作正確的授權程序,將合適的角色和權限分配給每個用戶,以便他們可以執行權限范圍內的某些任務,在Node.js中,你可以使用著名的ACL模塊,根據系統中的授權來開發訪問控制串列,
const ACL = require('acl2');
const acl = new ACL(new ACL.memoryBackend());
// guest is allowed to view blogs
acl.allow('guest', 'blogs', 'view')
// check if the permission is granted
acl.isAllowed('joed', 'blogs', 'view', (err, res) => {
if(res){
console.log("User joed is allowed to view blogs");
}
});
請查閱acl2檔案以獲取更多資訊和示例代碼,
4. 暴力攻擊防護
黑客經常會使用軟體反復使用不同的密碼嘗試獲得系統權限,直到找到有效密碼為止,這種攻擊方式叫做暴力攻擊,為了避免這種攻擊,一種簡單有效的辦法是“讓他等一會”,也就是說,當某人嘗試登錄系統并嘗試輸入無效密碼3次以上時,請讓他們等待60秒左右,然后再嘗試,這樣,攻擊者將大大提高時間成本,并且將使他們永遠無法破解密碼,
防止這種攻擊的另一種方法是屏蔽無效登錄請求的IP,系統在24小時內允許每個IP進行3次錯誤地登錄嘗試,如果有人嘗試進行暴力破解,則將其IP封鎖24小時, 許多公司已使用這種方法來防止暴力攻擊, 如果使用Express框架,則有一個中間件模塊可在傳入請求中啟用速率限制, 它稱為express = brute,
下面是一個例子,
安裝依賴項
npm install express-brute --save
在路由中啟用它
const ExpressBrute = require('express-brute');
const store = new ExpressBrute.MemoryStore(); // stores state locally, don't use this in production
const bruteforce = new ExpressBrute(store);
app.post('/auth',
bruteforce.prevent, // error 429 if we hit this route too often
function (req, res, next) {
res.send('Success!');
}
);
//...
5. HTTPS安全傳輸
現在已經2021年了,你也應該使用HTTPS來向網路中發送資料了,HTTPS是具有安全通信支持的HTTP協議的擴展,使用HTTPS,可以保證用戶在互聯網中發送的資料是被加密的,是安全的,
在這里我不打算詳細介紹HTTPS協議的作業原理,我們只討論如何使用它,這里我強烈推薦使用LetsEncrypt來為你的所有域名生成安全證書,
你可以在基于Apache和Nginx的Web服務器上使用LetsEncrypt,我強烈建議你在反向代理或網關層上使用HTTPS協議,因為它們有很多繁重的計算操作,
6. 會話劫持保護
會話(session)是任何動態Web應用程式最重要的部分,一個安全的會話對用戶和系統來說真的是非常必要的,會話是使用Cookie實作的,因此必須確保其安全以防止會話劫持,以下是可以為每個cookie設定的屬性串列以及它們的含義:
- secure - 該屬性告訴瀏覽器僅在通過HTTPS發送請求時才發送cookie,
- HttpOnly - 該屬性用于防止跨站點腳本攻擊,因為它不允許通過JavaScript訪問cookie,
- domain - 該屬性用于與請求URL的服務器域名進行比較,如果域名匹配,或者是其子域,那么接下來就會檢查path屬性,
- path - 除了domian之外,還可以指定cookie有效的URL路徑, 如果domain和路徑匹配,則可以在請求中發送cookie,
- expires - 該屬性用于設定持久性的cookie,cookie只會在超過了設定的日期才會過期,
在Express框架中,你可以使用express-session npm模塊來管理會話,
const express = require('express');
const session = require('express-session');
const app = express();
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true,
cookie: { secure: true, path: '/'}
}));
7. 跨站點請求偽造攻擊(CSRF)防護
跨站點請求偽造攻擊利用系統中受信任的用戶,對Web應用程式執行有害的惡意操作,在Node.js中,我們可以使用csurf模塊來緩解CSRF攻擊,該模塊需要首先初始化express-session或cookie-parser,你可以看看下面的示例代碼:
const express = require('express');
const cookieParser = require('cookie-parser');
const csrf = require('csurf');
const bodyParser = require('body-parser');
// setup route middlewares
const csrfProtection = csrf({ cookie: true });
const parseForm = bodyParser.urlencoded({ extended: false });
// create express app
const 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');
});
app.listen(3000);
在網頁上,你需要創建一個隱藏輸入域,將CSRF令牌保存在該輸入域中,例如:
<form action="/process" method="POST">
<input type="hidden" name="_csrf" value="https://www.cnblogs.com/frontworld/archive/2021/01/31/{{csrfToken}}">
Favorite color: <input type="text" name="favoriteColor">
<button type="submit">Submit</button>
</form>
如果使用的是AJAX請求,那么CSRF令牌可以通過請求頭(header)來傳遞,
var token = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
headers: {
'CSRF-Token': token
}
8. 拒絕服務
拒絕服務或DOS攻擊,可以讓攻擊者通過破壞系統,使系統被迫關閉服務或用戶無法訪問服務,攻擊者通常會向系統發送大量的流量和請求,從而增加服務器CPU和記憶體負載,導致系統崩潰,為了緩解Node.js應用程式中的DOS攻擊,首先是要識別此類事件, 我強烈建議將這兩個模塊集成到系統中,
- Account lockout - 在n次嘗試失敗后,將帳戶或IP地址鎖定一段時間(例如24小時?)
- Rate limiting - 限制用戶在特定時間段內只能請求系統n次,例如,單個用戶每分鐘只能請求3次,
正則運算式拒絕服務攻擊(ReDOS)是DOS攻擊的一種,攻擊者利用系統中正則運算式的設計缺陷或計算復雜度來大量消耗服務器的系統資源,造成服務器的服務中斷或停止,
我們可以使用一些工具來檢查有風險的正則運算式,從而避免這些正則運算式的使用,例如這個工具:
https://github.com/davisjam/vuln-regex-detector
9. 依賴關系驗證
我們在專案中都使用了大量的依賴項,我們還需要檢查并驗證這些依賴關系,以確保整個專案的安全性,NPM已經具有這樣的審核功能來查找專案的漏洞,只需在源代碼目錄中運行下面的命令即可:
npm audit
要修復漏洞,可以運行此命令:
npm audit fix
您也可以先進行dry run來檢查修復程式,然后再將其應用到專案中,
npm audit fix --dry-run --json
10. HTTP安全頭資訊
HTTP提供了一些安全頭資訊,可以防止常見的攻擊,如果使用的是Express框架,則可以使用helmet模塊,1行代碼就可以啟用所有安全頭,
npm install helmet --save
下面來看看如何使用:
const express = require("express");
const helmet = require("helmet");
const app = express();
app.use(helmet());
//...
這將啟用以下HTTP頭:
- Strict-Transport-Security
- X-frame-Options
- X-XSS-Protection
- X-Content-Type-Protection
- Content-Security-Policy
- Cache-Control
- Expect-CT
- Disable X-Powered-By
這些HTTP頭可防止惡意用戶的各種攻擊,例如點擊劫持,跨站點腳本攻擊等,
(本文完)
公眾號 - 前端新世界

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/255107.html
標籤:其他
上一篇:原型鏈
下一篇:原型鏈
