# Node.js Security Overview
# Security in the NPM ecosystem
- Moving the whole traffic of the registry to HTTPS (opens new window)
- Protect your npm account with two-factor authentication and read-only tokens (opens new window)
- Using Two-Factor Authentication (opens new window)
# Securing your Node.js applications
# Using the Helmet module (opens new window)
Helmet (opens new window)์ ์ด์ฉํ๋ฉด ์น ์ทจ์ฝ์ฑ์ผ๋ก๋ถํฐ ์ฑ์ ๋ณดํธํ ์ ์๋ค.
- csp (opens new window)๋ Content-Security-Policy ํค๋๋ฅผ ์ค์ ํ์ฌ XSS(Cross-site scripting) ๊ณต๊ฒฉ ๋ฐ ๊ธฐํ ๊ต์ฐจ ์ฌ์ดํธ ์ธ์ ์ ์ ์๋ฐฉํ๋ค.
- hidePoweredBy (opens new window)๋ X-Powered-By ํค๋๋ฅผ ์ ๊ฑฐํ๋ค.
- hpkp (opens new window)๋ Public Key Pinning ํค๋๋ฅผ ์ถ๊ฐํ์ฌ, ์์กฐ๋ ์ธ์ฆ์๋ฅผ ์ด์ฉํ ์ค๊ฐ์ ๊ณต๊ฒฉ์ ๋ฐฉ์งํ๋ค.
- hsts (opens new window)๋ ์๋ฒ์ ๋ํ ์์ ํ(SSL/TLS๋ฅผ ํตํ HTTP) ์ฐ๊ฒฐ์ ์ ์ฉํ๋ Strict-Transport-Security ํค๋๋ฅผ ์ค์ ํ๋ค.
- ieNoOpen (opens new window)์ IE8 ์ด์์ ๋ํด X-Download-Options๋ฅผ ์ค์ ํ๋ค.
- noCache๋ Cache-Control ๋ฐ Pragma ํค๋๋ฅผ ์ค์ ํ์ฌ ํด๋ผ์ด์ธํธ ์ธก์์ ์บ์ฑ์ ์ฌ์ฉํ์ง ์๋๋ก ํ๋ค.
- noSniff (opens new window)๋ X-Content-Type-Options ๋ฅผ ์ค์ ํ์ฌ, ์ ์ธ๋ ์ฝํ ์ธ ์ ํ์ผ๋ก๋ถํฐ ๋ฒ์ด๋ ์๋ต์ ๋ํ ๋ธ๋ผ์ฐ์ ์ MIME ๊ฐ๋ก์ฑ๊ธฐ๋ฅผ ๋ฐฉ์งํ๋ค.
- frameguard (opens new window)๋ X-Frame-Options ํค๋๋ฅผ ์ค์ ํ์ฌ clickjacking์ ๋ํ ๋ณดํธ๋ฅผ ์ ๊ณตํ๋ค.
- xssFilter (opens new window)๋ X-XSS-Protection์ ์ค์ ํ์ฌ ๋๋ถ๋ถ์ ์ต์ ์น ๋ธ๋ผ์ฐ์ ์์ XSS(Cross-site scripting) ํํฐ๋ฅผ ์ฌ์ฉํ๋๋ก ํ๋ค.
$ npm i helmet -S
์ดํ ์ฝ๋์์ Helmet์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ๋ค.
const express = require('express');
const helmet = require('helmet');
const app = express();
app.use(helmet());
2
3
4
5
X-Powered-Byํค๋๋ฅผ ์ฌ์ฉํ์ง ์๊ธฐ
app.disabled('x-powered-by');
๋ฌผ๋ก Helmet๋ฅผ ์ด์ฉํ๋ฉด ์๋์ผ๋ก ์์ ์์ ์ ํ๋ค.
# Validating user input
- command injection
- SQL injection
- stored cross-site scripting
์ ํญ๋ชฉ์ ํผํ๊ธฐ ์ํด์ ์ฌ์ฉ์ ์ ๋ ฅ์ ์ ํจ์ฑ๊ฒ์ฌ๋ฅผ ํด์ผํ๋ค. ์ฌ์ฉ์ ์ ํจ์ฑ ๊ฒ์ฌํ๊ธฐ์ ์ข์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ Joi (opens new window)์ด๋ค.
์ฌ์ฉ๋ฐฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ๋ค.
const Joi = require('joi');
const schema = Joi.object().keys({
username: Joi.string().alphanum().min(3).max(30).required(),
password: Joi.string().regex(/^[a-zA-Z0-9]{3,30}$/),
access_token: [Joi.string(), Joi.number()],
birthyear: Joi.number().integer().min(1900).max(2013),
email: Joi.string().email()
}).with('username', 'birthyear').without('password', 'access_token');
// Return result.
const result = Joi.validate({
username: 'abc',
birthyear: 1994
}, schema);
// result.error === null -> valid
// You can also pass a callback which will be called synchronously with the validation result.
Joi.validate({
username: 'abc',
birthyear: 1994
}, schema, function (err, value) { }); // err === null -> valid
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Secure Coding Style
# Do not use eval()
Eval์ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฝ๋ ์ธ์ ์ ๊ณต๊ฒฉ์ ๊ฐ๋ฅํ๊ฒ ํ๋ค. ์ด๋ฅผ ์ฌ์ฉํ๋ ค๊ณ ํ์ง๋ง๋ผ.
eval()
๋ฟ๋ง ์๋๋ผ ์๋์ ํํ๋ ํผํด์ผํ๋ค. ์๋์ ํํ์ ๋ฐฑ๊ทธ๋ผ์ด๋๋ก eval()
๋ฅผ ์ฌ์ฉํ๋ค.
setInterval(String, 2)
setTimeout(String, 2)
new Function(String)
# Always use use strict;
use strict;
๋ฅผ ์ฌ์ฉํ๋ฉด ์๋ฐ์คํฌ๋ฆฝํธ์ "๋ณ์ข
"์ ํ์ฉํ์ง ์๊ฒ ํ๋ค. ๋ํ ๋ช๋ช ์๋ฌต์ ์ธ ์๋ฌ๋ค์ ์ ๊ฑฐํด์ฃผ๊ณ ์๋ฌ๋ก๊ทธ๋ฅผ throwํ๋ค.
'use strict';
const sung = {a: 1, b: 2};
2
3
# Set cookie scope
์ฟ ํค๋ก ์ธํด ์ฑ์ด ์ ์ฉ์ ๋ ธ์ถ๋์ง ์๋๋ก ํ๊ธฐ ์ํด ๊ธฐ๋ณธ ์ธ์ ์ฟ ํค ์ด๋ฆ์ ์ฌ์ฉํ์ง ๋ง๊ณ ์ฟ ํค ๋ณด์ ์ต์ ์ ์ ์ ํ ์ค์ ํ๋ค.
# ๊ธฐ๋ณธ ์ธ์ ์ฟ ํค ์ด๋ฆ์ ์ฌ์ฉํ์ง ์์
๊ธฐ๋ณธ ์ธ์ ์ฟ ํค ์ด๋ฆ์ ์ฌ์ฉํ๋ฉด ์ฑ์ ๊ณต๊ฒฉ์ ๋ ธ์ถ์ํฌ ์ ์๋ค.
์ด๋ก ์ธํด ์ ๊ธฐ๋๋ ๋ณด์ ๋ฌธ์ ๋ X-Powered-By์ ์ ์ฌํ๋ฉฐ, ์ ์ฌ์ ์ธ ๊ณต๊ฒฉ์๋ ์ด๋ฅผ ์ด์ฉํด ์๋ฒ์ ์ง๋ฌธ์ ์ฑ์ทจํ ํ ์ด์ ๋ฐ๋ผ ๊ณต๊ฒฉ ๋์์ ์ค์ ํ ์ ์๋ค.
const session = require('express-session');
app.set('trust proxy', 1); // trust first proxy
app.use(session({
secret: 's3Cur3',
name: 'sessionId'
}));
2
3
4
5
6
7
8
# ์ฟ ํค ๋ณด์ ์ต์ ์ค์
๋ค์๊ณผ ๊ฐ์ ์ฟ ํค ์ต์ ์ ์ค์ ํ์ฌ ๋ณด์์ ๊ฐํํ ํ์๊ฐ ์๋ค.
- secure - ๋ธ๋ผ์ฐ์ ๊ฐ HTTPS๋ฅผ ํตํด์๋ง ์ฟ ํค๋ฅผ ์ ์กํ๋๋ก ํ๋ค.
- httpOnly - ์ฟ ํค๊ฐ ํด๋ผ์ด์ธํธ JavaScript๊ฐ ์๋ HTTP(S)๋ฅผ ํตํด์๋ง ์ ์ก๋๋๋ก ํ๋ฉฐ, ์ด๋ฅผ ํตํด XSS(Cross-site scripting) ๊ณต๊ฒฉ์ผ๋ก๋ถํฐ ๋ณดํธํ ์ ์๋ค.
- domain - ์ฟ ํค์ ๋๋ฉ์ธ์ ํ์ํ๊ณ URL์ด ์์ฒญ๋๊ณ ์๋ ์๋ฒ์ ๋๋ฉ์ธ์ ๋ํด ๋น๊ตํ ๋ ์ฌ์ฉํ๋ค. ๋ ๋๋ฉ์ธ์ด ์ผ์นํ๋ ๊ฒฝ์ฐ์๋ ๊ทธ ๋ค์์ผ๋ก ๊ฒฝ๋ก ์์ฑ์ ํ์ธํด๋ผ.
- path - ์ฟ ํค์ ๊ฒฝ๋ก๋ฅผ ํ์ํ๋ค. ์์ฒญ ๊ฒฝ๋ก์ ๋ํด ๋น๊ตํ ๋ ์ฌ์ฉํ๋ฉฐ ์ด ๊ฒฝ๋ก์ ๋๋ฉ์ธ์ด ์ผ์นํ๋ ๊ฒฝ์ฐ์๋ ์์ฒญ๋๊ณ ์๋ ์ฟ ํค๋ฅผ ์ ์กํ๋ค.
- expires - ์ง์์ ์ฟ ํค์ ๋ํ ๋ง๊ธฐ ๋ ์ง๋ฅผ ์ค์ ํ๋ ๋ฐ ์ฌ์ฉ๋๋ค.
const session = require('cookie-session');
const express = require('express');
const app = express();
const expiryDate = new Date( Date.now() + 60 * 60 * 1000 ); // 1 hour
app.use(session({
name: 'session',
keys: ['key1', 'key2'],
cookie: {
secure: true,
httpOnly: true,
domain: 'example.com',
path: 'foo/bar',
expires: expiryDate
}
})
);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Audit your modules with the Node Security Platform
nsp (opens new window)๋ Node Security Platform์ ์ฃผ์ ์ปค๋งจ๋๋ผ์ธ ์ธํฐํ์ด์ค์ด๋ค. nsp๋ ์ทจ์ฝํ ๋ชจ๋๋ค์ ์ ๊ฒํ๊ธฐ ์ํด package.json ํ์ผ ํน์ npm-shirnkwrap.json ํ์ผ์ NSP API๋ฅผ ํตํด ๊ฐ์ํ๋ค.
$ npm i nsp -g
# From inside your project directory
$ nsp check
2
3
4
5
์ ํจ์ฑ ๊ฒ์ฆ์ ์ํ npm-shrinkwrap.json ํ์ผ์ nodesecurity.io (opens new window)์ ์ ์ถํ๋ ค๋ฉด ๋ค์๊ณผ ๊ฐ์ ๋ช ๋ น์ ์ฌ์ฉํ๋ค.
$ nsp audit-shrinkwrap
์ ํจ์ฑ ๊ฒ์ฆ์ ์ํ package.json ํ์ผ์ nodesecurity.io (opens new window)์ ์ ์ถํ๋ ค๋ฉด ๋ค์๊ณผ ๊ฐ์ ๋ช ๋ น์ ์ฌ์ฉํ๋ค.
$ nsp audit-package
# Look for vulnerabilities with Retire.js (opens new window)
Retire.js (opens new window)๋ ์ฌ์ฉํ๊ณ ์๋ ๋ชจ๋ ๋ฒ์ ๋ค์ ๋ํด ์๋ ค์ง ์ทจ์ฝ์ ์ ํ์งํด์ฃผ๊ธฐ ์ํ ๋ชจ๋์ด๋ค.
$ npm i retire -g
retire ๋ช ๋ น์ผ๋ก ์คํํ๋ฉด node_modules ๋๋ ํ ๋ฆฌ๋ด์ ๋ชจ๋๋ค์ ์ทจ์ฝ์ ์ ์ฐพ์ ์ ์๋ค.
(๋ํ ์ฐธ๊ณ ๋ก retire.js๋ node_modules ์ธ์ ํ๋ก ํธ์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์๋ ์ ์๋ํ๋ค.)