ความปลอดภัย ภัยคุกคาม เป็นเรื่องที่ยากจะเข้าใจ เพราะในทุกๆวัน การโจมตี มันมีวิธีการมากมาย ในฐานะนักพัฒนาซอฟท์แวร์ ไม่ควรมองข้ามวิธีการที่จะปกป้องซอฟท์แวร์ของเราให้มีความปลอดภัยอยู่เสมอ จากผู้โจมตีระบบ
ผมได้รับโปรเจคช่วงนี้เป็น NodeJS Engineer / API Developer โปรเจคช่วงนี้จะดูแล Backend ทั้งหมด ตั้งแต่
- Implement APIs ด้วย Express
- สร้าง Dockerfile, docker-compose.yml
- Deployment docker image และสร้าง docker container จากข้อ 2 ขึ้น Cloud ผ่าน OpenShift ไปยัง AWS
- Configuration Middleware ทั้งหลาย ก่อนจะมาถึง Express ตอนนี้ใช้ Kong
- Dashboard, Monitoring Tool, Notification System via Slack
- Unit Testing, E2E Testing, Load Testing
- Swagger API Contract
- และสุดท้าย ณ เวลานี้ คือ 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 <
> is replaced with >
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:
No comments:
Post a Comment