Tuesday, January 19, 2021

Security Checklist for Express (NodeJS)

 

Image for post

ความปลอดภัย ภัยคุกคาม เป็นเรื่องที่ยากจะเข้าใจ เพราะในทุกๆวัน การโจมตี มันมีวิธีการมากมาย ในฐานะนักพัฒนาซอฟท์แวร์ ไม่ควรมองข้ามวิธีการที่จะปกป้องซอฟท์แวร์ของเราให้มีความปลอดภัยอยู่เสมอ จากผู้โจมตีระบบ

ผมได้รับโปรเจคช่วงนี้เป็น NodeJS Engineer / API Developer โปรเจคช่วงนี้จะดูแล Backend ทั้งหมด ตั้งแต่

  1. Implement APIs ด้วย Express
  2. สร้าง Dockerfile, docker-compose.yml
  3. Deployment docker image และสร้าง docker container จากข้อ 2 ขึ้น Cloud ผ่าน OpenShift ไปยัง AWS
  4. Configuration Middleware ทั้งหลาย ก่อนจะมาถึง Express ตอนนี้ใช้ Kong
  5. Dashboard, Monitoring Tool, Notification System via Slack
  6. Unit Testing, E2E Testing, Load Testing
  7. Swagger API Contract
  8. และสุดท้าย ณ เวลานี้ คือ Securiry Check

เข้าเรื่องเลยดีกว่า บทความนี้ผมจะข้ามเรื่อง Authentication, Authorization ไปก่อนนะครับ แต่จะโฟกัสที่ middleware express แทน

ในฐานะที่ผมใช้ Express เป็น Core หลักเลยในการให้ partner ทั้งหลาย Consume APIs สิ่งที่ผมต้องทำอยู่เสมอเกี่ยวกับด้าน Security มีดังนี้

# เปิดใช้ TLS/SSL อยู่เสมอ

ก่อนที่จะ expose application ของเราออกสู่ public network ควรใส่ใจอยู่เสมอว่า หาก API ของเรามี POST, PUT, DELETE หรือ method ที่สามารถ mutation server จำไว้เลยว่า hacker สามารถเล่นงานเราได้

Trust proxy ก็เป็นวิธีนึงที่เราสามารถจัดการระดับ application layer ได้

ด้วยความที่เราจัดการแค่ระดับ application layer มันยังไม่เพียงพอ เราควรเพิ่ม ที่ layer อื่นด้วย เช่นหาก เราใช้ NGINX เป็น server ก็อย่าลืมเปิดมันซ่ะ

# Testing HTTPS Certificate

ทดสอบว่าการรับส่ง ข้อมูลผ่านมายัง application ของเราผ่าน HTTPS หรือไม่ ซึ่งมันก็มีเครื่องมือมากมายให้เราเล่น

# Using Snyk, NSP and Retire.JS to Identify and Fix Vulnerable Dependencies in your Node.js Applications

ที่ทีมปกติจะใช้ nsp (Node Security Platform) ในการค้นหา และแก้ไขพวก Vulnerable จุดอ่อนแอที่ง่ายต่อการโจมตีในเวปไซต์

แต่ล้ว Redhat ก็ได้ออกบทความเปรียบเทียบเครื่องมือ ที่น่าสนใจ ลองไปติดตามอ่านได้เลยครับ

# Sanitize หรือ Encoding data ที่ไม่น่าไว้วางใจที่ส่งมายัง Application

HTML Encoding (ESCAPE)

HTML tags นี่แหละตัวดีเลย หากเราเปิดให้สามารถส่ง script tag ไปยัง backend ได้แล้วหล่ะ ก็ script tag นั้นๆจะกลายเป็น permanent attack script ได้

วิธีการแก้ คือ encoding มัน โดยสามารถใช้ library เหล่านี้

" is replaced with " 
& is replaced with &
< is replaced with &lt;
> is replaced with &gt;

Scanner & Testing Tool

Middleware

# แนวทางการป้องกัน XSS Injection จากทาง OWASP

var express    = require('express');
var bodyParser = require('body-parser');
var validator = require('express-validator');

var app = express();

app.use(bodyParser.urlencoded());

app.use(validator());
app.use(function(req, res, next) {
for (var item in req.body) {
req.sanitize(item).escape();
}
next();
});

app.listen(80);

Focus แค่สองจุดครับ คือ

  • body-parser = ทำหน้าที่แปลง incoming data ทำตัวเป็น middleware ก่อนไปยัง handler, controller ของเรา ซึ่งเราสามารถ urlencoded ได้ในระดับหนึ่ง
  • express-validator = middleware อีกตัวที่สามารถ sanitize และ encoding data ได้

# CSS Encoding

# ป้องกัน parameter ที่สามารถหยุดการทำงานของ Express

เคยอ่าน WTF JavaScript ไหมครับ ปกติเราส่ง query string เช่น

?name=Golf

JavaScript จะมองในฐานะ String type

แล้วถ้าหากเราทำแบบนี้หล่ะ จะเกิดไรขึ้น

curl http://example.com:8080/endpoint?name=Itchy&name=Scratchy

หากเรามี handler ใน express แบบนี้

app.get('/endpoint', function(req, res) {
if (req.query.name) {
res.status(200).send('Hi ' + req.query.name.toUpperCase())
} else {
res.status(200).send('Hi');
}
});

ผลลัพธ์ที่ได้ คือ

Express returns the results as an Array['Itchy', 'Scratchy']. This will throw an Error that will crash an Express application.

Crash สิครับงานนี้ โดนยิงง่ายๆเลยครับ ดังนั้น OWAPS แนะนำเอกสารตัวนี้ ที่เป็นเครื่องมือทดสอบครับผม

# ปกป้อง HTTP HEADER ด้วย Helmet

และแล้วก็มาถึง Middleware ที่ทาง Express แนะนำครับผม

Helmet เป็นแนวทางการปกป้อง HTTP headers ตามที่ Express ได้แนะนำไว้ข้างต้น โดยทางเอกสารของ Express กล่าวไว้ว่า Helmet สามารถครอบคลุมความปลอดภัย ได้ดังนี้

  • csp sets the Content-Security-Policy header to help prevent cross-site scripting attacks and other cross-site injections.
  • hidePoweredBy removes the X-Powered-By header.
  • hpkp Adds Public Key Pinning headers to prevent man-in-the-middle attacks with forged certificates.
  • hsts sets Strict-Transport-Security header that enforces secure (HTTP over SSL/TLS) connections to the server.
  • ieNoOpen sets X-Download-Options for IE8+.
  • noCache sets Cache-Control and Pragma headers to disable client-side caching.
  • noSniff sets X-Content-Type-Options to prevent browsers from MIME-sniffing a response away from the declared content-type.
  • frameguard sets the X-Frame-Options header to provide clickjacking protection.
  • xssFilter sets X-XSS-Protection to enable the Cross-site scripting (XSS) filter in most recent web browsers.

ซึ่งการใช้งานค่อนข้างง่ายมากครับ เพียงแค่

var helmet = require('helmet')
app.use(helmet())

# ป้องกันการโจมตี CSRF (Cross-site Request Forgery)

CSRF คือ เป็นเทคนิคการปลอมตัวเป็นผู้ใช้ในระบบ ที่อาศัยหลอกระบบว่า เป็นบุคคลที่ถูกต้อง ผ่านการตรวจสอบในระบบนั้นๆ มาแล้ว

โดยแฮกเกอร์จะขโมย cookie หรือ session แล้วนำมาใช้สเมือนว่าเป็นผู้ใช้ในระบบนั้นจริงๆ เพื่อทำธุรกรรมต่างๆ ตามที่ต้องการ

พบเจอได้บ่อยในวิธี Phishing, E-mail, Social Engineering, Form Input ต่างๆ ที่หลอกให้ผู้ใช้กด กรอก โดยผู้ใช้อาจไม่รู้ตัวเลยว่ากำลังยินยอมให้แฮกเกอร์ได้ข้อมูลได้ผลประโยชน์อะไรบางอย่าง เช่น session ค้างอยู่ที่เวปธนาคาร แฮกเกอร์จัดการโอนเงินจากบัญชีผู้ใช้ ไปยังปลายทาง ซึ่งทางธนาคารก็คิดว่าเป็นผู้ใช้คนนั้นจริงๆ เพราะ session ถูกต้อง

วิธีแก้ไขโดยส่วนมากจะยอมใช้ CSRF Token มาช่วยในการยืนยันการ Request

ในส่วนของ Express เองเราสามารถใช้ https://github.com/expressjs/csurf

ดังนั้น ในทุกๆครั้งที่มีการ Request เราจะ Generate CSRF Token ใหม่เสมอ และตรวจสอบ CSRF Token ทุกครั้ง จาก Cookie ของผู้ใช้ก่อน Action หรือ Response การร้องขอกลับไป

# อย่าใช้ Evil Regular Expression

คำนิยามของมัน คือ https://en.wikipedia.org/wiki/ReDoS

The regular expression denial of service (ReDoS)[1] is an algorithmic complexity attack that produces a denial-of-service by providing a regular expression that takes a very long time to evaluate.

คือ การเขียน Regex ที่ไม่มีประสิทธิภาพ เปิดโอกาสให้ใส่ข้อมูลที่ทำให้ ประมวลผล Regular Expression Match / No Match ที่นานเกินไป จนทำให้ Server Down ไป และถ้าใน JavaScript อาจเกิด Blocking I/O ได้

และตามเอกสาร ของ OWAPS ฉบับล่าสุด

The Regular expression Denial of Service (ReDoS) is a Denial of Service attack, that exploits the fact that most Regular Expression implementations may reach extreme situations that cause them to work very slowly (exponentially related to input size). An attacker can then cause a program using a Regular Expression to enter these extreme situations and then hang for a very long time.

Regex ที่ออกแบบมาไม่ดี ทำให้เวลาในการประมวลผลข้า การขยายตัวเวลาในกานคำนวนตามขนาดของ input

ได้ยกตัวอย่าง Evil Pateern ไว้ ดังนี้

Examples of Evil Patterns:

(a+)+

([a-zA-Z]+)*

(a|aa)+

(a|a?)+

(.*a){x} | for x > 10

All the above are susceptible to the input aaaaaaaaaaaaaaaaaaaaaaaa! (The minimum input length might change slightly, when using faster or slower machines)

วิธีการแก้ไข

หา Possibility ของ Input format ที่เป็นไปได้ก่อน และออกแบบ Regex Pattern ตามเหมาะสม อย่าเปิดโอกาสให้ Regex กว้างมากเกินไป เพราะจะทำให้เวลาในการประมวลผลนาน จนทำให้ Server รองรับการประมวลผลไม่ไหว ซึ่งเป็นช่องโหว่ในการโจมตีจากแฮกเกอร์ได้

เครื่องมือในการ Scan

  • RXRR — Regular expression denial of service (REDoS) static analysis.
  • SDL RegEx Fuzzer — a tool to assist in testing regex for possible DoS vulnerabilities.

# กำหนด Rate Limit

เราสามารถกำหนดจำนวนการร้องขอ ต่อ IP Address ได้ (หากจำเป็นต้องทำ) ด้วยวิธีการ

const express = require('express');
const redisClient = require('redis').createClient();
const app = express();const limiter = require('express-limiter')(app, redisClient);// Limit requests to 100 per hour per ip address.
limiter({
lookup: ['connection.remoteAddress'],
total: 100,
expire: 1000 * 60 * 60
})

จากตัวอย่างจะเก็บจำนวนการร้องขอของ Client ไว้ที่ In-Memory Database แล้วกำหนดจำนวนที่สามารถร้องขอได้ ภายใต้ระยะเวลา 1 ชั่วโมง

และทั้งหมดที่กล่าวมา คือ วิธีการเบื้องต้นในการป้องกันภัยคุกคามจากผู้ไม่หวังดีบนแอพพลิเคชันของเรา ด้วยการป้องกันผ่านทาง Application layer โดยการ Implement กับ Express JS ครับ นอกจากนี้ การ Expose Log ต่างๆ ก็ไม่ควรเปิด เมื่อเรา Deploy to Production ครับซึ่งเป็นวิธีพื้นฐานที่เรามักมองข้ามไป

References:

Source : https://medium.com/@goangle/security-checklist-for-express-nodejs-fe4569be10ee

No comments:

Post a Comment