Node.js 網站設計 – 安全性
實務 – Node.js
理論 – 參考
- 4 Simple Steps to Secure Your Express Node Application
- XSS – Secure Node Apps Against OWASP Top 10 - Cross Site Scripting
- Secure Node Apps Against OWASP Top 10 - Cross Site Request Forgery
- Secure Node Apps Against OWASP Top 10 - Authentication & Sessions
- Secure Node Apps Against OWASP Top 10 - Injection
XSS – Cross-site scripting (跨網站指令碼)
範例
<html>
<a href="https://site.com/search?q=<script>alert('hacked')</script>">
Click Here
</a>
</html>
解決方式
- always validate, escape, or sanitize user input.
<script>alert('hacked')</script>
escape 之後
>script<alert('hacked')>/script<
- tell the web browser to allow content only from trusted sources (例如使用 helmet)
var express = require('express');
var helmet = require('helmet');
var app = express();
app.use(helmet.csp({
defaultSrc: ["'self'"],
scriptSrc: ['*.google-analytics.com'],
styleSrc: ["'unsafe-inline'"],
imgSrc: ['*.google-analytics.com'],
connectSrc: ["'none'"],
fontSrc: [],
objectSrc: [],
mediaSrc: [],
frameSrc: []
}));
app.listen(80);
CSRF – Cross Site Request Forgery (跨站請求偽造)
Scenario 1: Changes allowed via GET requests
Solution: The solution here is very simple. Never allow changes to occur on GET requests. Only allow changes to be made when the HTTP method is POST, PUT, or DELETE.
Scenario 2: Posting exploited data to sites
Solution : The solution here is to implement the synchronizer token pattern.
var express = require('express');
var csrf = require('csurf');
var app = express();
app.use(csrf());
app.use(function(req, res, next) {
res.locals._csrf = req.csrfToken();
next();
});
//Add _csrf to rendered HTML forms as hidden field (see HTML below)
app.listen(80);
前端
<html>
<form method="post" action="transfer">
<input type="hidden" name="_csrf" value="_csrf">
<input type="text" name="to">
<input type="test" name="dollars">
</form>
</html>
Scenario 3: Bypassing CSRF protections
前端
<form method="post">
<input type="text" name="to" value="">
<input type="text" name="dollars" value="">
<input type="hidden" name="csrf" value="a0d73b12">
</form>
後端
app.post('/transfer', function (req, res) {
if (isValid(req.body.csrfToken)) {
var to = req.params.to || req.body.to;
var dollars = req.params.dollars || req.body.dollars;
//Transfer money
}
});
防禦
var express = require('express');
var helmet = require('helmet');
var app = express();
app.use(helmet.xframe('deny')); //never allow in frames
app.listen(80);
Authentication & Sessions
Broken Authentication and Session Management
Scenario 1: Plain text passwords
解決 : 密碼加鹽
var bcrypt = require('bcrypt-nodejs');
var hashPassword = function(password, callback) {
bcrypt.genSalt(10, function(err, salt) {
if (err) return callback(err);
bcrypt.hash(password, salt, null, function(err, hash) {
if (err) return callback(err);
callback(null, hash);
});
});
};
var verifyPassword = function(password, hash, callback) {
bcrypt.compare(password, hash, function(err, isMatch) {
if (err) return callback(err);
callback(null, isMatch);
});
};
Scenario 2: Session IDs in the URL
解法: Pass session IDs via cookies and not the URL
var express = require('express');
var session = require('express-session');
var app = express();
app.use(session({
secret: 'our super secret session secret',
cookie: { maxAge: 3600000 } // 2 hours in milliseconds
}));
app.listen(80);
Scenario 3: Site accessed over HTTP
Solution 1: 改用 https Solution 2: 不允許 cookies to be sent over HTTP Solution 3: not allow client side scripts access to your cookies Solution 4: tell the browser to never make HTTP requests again (HTTP Strict Transport Security) (HSTS).
Injection
Scenario 1: SQL Injection
Solution 1: Escape user input
Solution #2: Parameterized SQL queries
"SELECT * FROM users WHERE user = $1 AND pass = $2";
Scenario 2: Node Injection
Solution : Never use eval() with user input.
Scenario 3: MongoDB Injection
問題程式:
db.users.find({user: user, pass: pass});
駭客方法:
POST /login HTTP/1.1
Host: example.com
Content-Type: application/json
{
"user": {"$gt": ""},
"pass": {"$gt": ""}
}
解決方法:
db.users.find({user: { $in: [user] }, pass: { $in: [pass] }});