# Node.js Security Overview

# Security in the NPM ecosystem

# 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
1

์ดํ›„ ์ฝ”๋“œ์—์„œ Helmet์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

const express = require('express');
const helmet = require('helmet');
const app = express();

app.use(helmet());
1
2
3
4
5

X-Powered-Byํ—ค๋”๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ธฐ

app.disabled('x-powered-by');
1

๋ฌผ๋ก  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
1
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};
1
2
3

์ฟ ํ‚ค๋กœ ์ธํ•ด ์•ฑ์ด ์•…์šฉ์— ๋…ธ์ถœ๋˜์ง€ ์•Š๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด ๊ธฐ๋ณธ ์„ธ์…˜ ์ฟ ํ‚ค ์ด๋ฆ„์„ ์‚ฌ์šฉํ•˜์ง€ ๋ง๊ณ  ์ฟ ํ‚ค ๋ณด์•ˆ ์˜ต์…˜์„ ์ ์ ˆํžˆ ์„ค์ •ํ•œ๋‹ค.

# ๊ธฐ๋ณธ ์„ธ์…˜ ์ฟ ํ‚ค ์ด๋ฆ„์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ

๊ธฐ๋ณธ ์„ธ์…˜ ์ฟ ํ‚ค ์ด๋ฆ„์„ ์‚ฌ์šฉํ•˜๋ฉด ์•ฑ์„ ๊ณต๊ฒฉ์— ๋…ธ์ถœ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.

์ด๋กœ ์ธํ•ด ์ œ๊ธฐ๋˜๋Š” ๋ณด์•ˆ ๋ฌธ์ œ๋Š” X-Powered-By์™€ ์œ ์‚ฌํ•˜๋ฉฐ, ์ž ์žฌ์ ์ธ ๊ณต๊ฒฉ์ž๋Š” ์ด๋ฅผ ์ด์šฉํ•ด ์„œ๋ฒ„์˜ ์ง€๋ฌธ์„ ์ฑ„์ทจํ•œ ํ›„ ์ด์— ๋”ฐ๋ผ ๊ณต๊ฒฉ ๋Œ€์ƒ์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

const session = require('express-session');

app.set('trust proxy', 1); // trust first proxy
app.use(session({
    secret: 's3Cur3',
    name: 'sessionId'
}));

1
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
          }
    })
);

1
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
1
2
3
4
5

์œ ํšจ์„ฑ ๊ฒ€์ฆ์„ ์œ„ํ•œ npm-shrinkwrap.json ํŒŒ์ผ์„ nodesecurity.io (opens new window)์— ์ œ์ถœํ•˜๋ ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ช…๋ น์„ ์‚ฌ์šฉํ•œ๋‹ค.

$ nsp audit-shrinkwrap
1

์œ ํšจ์„ฑ ๊ฒ€์ฆ์„ ์œ„ํ•œ package.json ํŒŒ์ผ์„ nodesecurity.io (opens new window)์— ์ œ์ถœํ•˜๋ ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ช…๋ น์„ ์‚ฌ์šฉํ•œ๋‹ค.

$ nsp audit-package
1

# Look for vulnerabilities with Retire.js (opens new window)

Retire.js (opens new window)๋Š” ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š” ๋ชจ๋“ˆ ๋ฒ„์ „๋“ค์— ๋Œ€ํ•ด ์•Œ๋ ค์ง„ ์ทจ์•ฝ์ ์„ ํƒ์ง€ํ•ด์ฃผ๊ธฐ ์œ„ํ•œ ๋ชจ๋“ˆ์ด๋‹ค.

$ npm i retire -g
1

retire ๋ช…๋ น์œผ๋กœ ์‹คํ–‰ํ•˜๋ฉด node_modules ๋””๋ ‰ํ† ๋ฆฌ๋‚ด์˜ ๋ชจ๋“ˆ๋“ค์˜ ์ทจ์•ฝ์ ์„ ์ฐพ์„ ์ˆ˜ ์žˆ๋‹ค.

(๋˜ํ•œ ์ฐธ๊ณ ๋กœ retire.js๋Š” node_modules ์™ธ์— ํ”„๋ก ํŠธ์—”๋“œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ๋„ ์ž˜ ์ž‘๋™ํ•œ๋‹ค.)

# ์ถœ์ฒ˜