Tối ưu bảo mật app NodeJS tốt hơn

Việc website bị tin tặc tấn công thực sự là thảm họa với các quản trị viên và chủ doanh nghiệp. Nó có thể phá hoại hoàn toàn hoặc làm ảnh hưởng lớn đến công việc kinh doanh của bạn. Vậy, cái giá phải trả khi website bị hack là bao nhiêu? Vì vậy hãy bảo mật một cách cẩn thận để tránh những rủi ro7 min


1043
1.4k shares, 1043 points

Chống DOS, DDOS hay brute-force mật khẩu

Chúng ta sẽ giới hạn số lượng request (trên 1 địa chỉ IP trong một khoảng thời gian nhất định). Để đề phòng server NodeJS của chúng ta lăn ra chết khi phải hứng chịu các cuộc tấn công từ chối dịch vụ (DOS, DDOS). Việc implement cũng không hề khó nhọc một chút nào với việc dùng cách package npm có sẵn.

const express = require('express');
const rateLimit = require("express-rate-limit");
const app = express();
const limiter = rateLimit({
// 15 minutes
  windowMs: 15 * 60 * 1000,
// limit each IP to 100 requests per windowMs
  max: 100
});
app.use(limiter);

Ở ví dụ trên chúng ta chỉ cần cài đặt package express-rate-limit và config cấu hình cho middleware để lọc các request. Ở trên chúng ta chỉ cho phép 1 địa chỉ IP request tối đa 100 lần trong vòng 15 phút. Nếu vượt quá giới hạn nói trên, server ngay lập tức sẽ trả về status code 429 TOO MANY REQUESTS.

Chúng ta có thể cài đặt số lượng request tôi đa với mỗi routes là khác nhau.

const express = require('express');
const rateLimit = require("express-rate-limit");
const app = express();
const apiLimiter = rateLimit({
// 15 minutes
  windowMs: 15 * 60 * 1000,
  max: 100
});
app.use("/api/", apiLimiter);
const createAccountLimiter = rateLimit({
// 1 hour window
  windowMs: 60 * 60 * 1000,
// start blocking after 5 requests
  max: 5,
  message: "Too many accounts created from this IP, please try again after an hour"
});
app.post("/create-account", createAccountLimiter, function(req, res) {
});

Ngoài lề: Trong giai đoạn development sản phẩm của công ty, đang còn viết unit test (hơn 200 test case). Khi chạy unit test, server cũng nhận hàng trăm request trong vài giây, dẫn đến server return status 429. Chúng ta nên để code như dưới đây khi đang còn phát triển sản phẩm.

if (proccess.env.NODE_ENV === 'production') {
    app.use(limiter);
}

Lọc dữ liệu người dùng gửi lên server

Đừng bao giờ tin dữ liệu người dùng nhập vào, lọc dữ liệu ở phía client. Chúng sẽ chẳng là gì với những hacker lão luyện, hay chỉ đơn là là một thằng script Script kiddie 

Một trang web nào nó ngày xưa đã không lọc dữ liệu ở phía server NodeJS. Để người dùng có thể thay đổi email tùy ý (thậm chí là một email không hợp lệ) 

Các lỗ hổng nổi tiếng rất nguy hiểm khai thác sự hớ hênh trên phải kể đến XSS, SQL Injection, …

Sử dụng helmet 

Helmet là package npm: gồm 14 middleware nhỏ ở trong giúp xử lý, lọc các HTTP header độc hại (nhằm khai thác lỗ hổng XSS hay clickjacking, …).

const express = require('express')
const helmet = require('helmet')
const app = express()
app.use(helmet())

Các tùy chọn mặc định của Helmet khi sử dụng câu lệnh app.use(helmet()). Nếu muốn bạn có thể lựa thêm nhiều tùy chọn, chi tiết bạn có thể tham khảo ở Github.

Sử dụng express-validator để data gửi lên server

Giá trị dữ liệu người dùng submit form lên server hoàn toàn có thể là chuỗi rỗng. Hoặc không đủ độ dài, hay chứa các đoạn mã nguy hiểm (ký tự đặc biệt, ..).

router.post(
  '/login',
  [
    body('username')
      .not()
      .isEmpty()
      .trim()
      .escape(),
    body('password')
      .not()
      .isEmpty()
      .trim()
      .escape()
      .isLength({ min: 6 })
  ],
  async (req, res, next) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(422).json({ errors: errors.array() });
    }
    // ....
  }
);

Ở ví dụ trên, req.body.username và req.body.password sẽ được kiểm tra xem có rỗng hay không (password thì thêm điều kiện ít nhất 6 ký tự) cũng với đọc lại lọc, chuyển đổi các ký tự đặc biệt có thể ẩn tàng là 1 đoạn mã khai thác. Server NodeJS sẽ trả về status code 422 nếu dữ liệu không thỏa mãn điều kiện.

Chúng ta cũng có thể dùng check thay vì body, hàm check sẽ kiểm tra không chỉ req.body mà còn tất cả các giá trị req.cookies, req.headers, req.params hay req.query.

Ngoài các hàm có sẵn, express-validator còn cho phép chúng ta có thể custom middleware để lọc dữ liệu.

const { check } = require('express-validator');
app.post('/user', [
  check('email').custom(value => {
    return User.findByEmail(value).then(user => {
      if (user) {
        return Promise.reject('E-mail already in use');
      }
    });
  }),
  check('password').custom((value, { req }) => {
    if (value !== req.body.passwordConfirmation) {
      throw new Error('Password confirmation is incorrect');
    }
  })
], (req, res) => {
});

Sử dụng thư viện ORD/ODM để chống SQL/NoSQL Injection

Ví dụ như Sequelize, Knex, mongoose, … Tuyệt đối đừng dùng kiểu câu truy vấn bằng bằng chuỗi Javascript vì phải nối chuỗi không đảm bảo. Các thư viện họ có hàm gọi rất tiện lợi mà lại đảm bảo an toàn hơn.

const tableOne = 'table_one';
const tableTwo = 'table_two';
let query = `
  SELECT a.year, a.season, b.display_name
  FROM ${tableOne} a, ${tableTwo} b
  WHERE a.id = b.transmitter AND season != 'other'
  GROUP BY a.year, a.season, b.display_name
  ORDER BY b.display_name, year;
  `;

Sử dụng https

Với http, dữ liệu từ các client gửi lên server NodeJS hoàn toàn ‘trong sáng’ và có thể bị hacker bắt và đọc được toàn bộ. Các trình duyệt như Chrome, Firefox, … cũng sẽ hiển cảnh báo khi truy cập vào các website sử dụng http. Với Https, dữ liệu truyền lên server sẽ được mã hóa và tránh khỏi sự dòm ngó từ kẻ xấu.

Để sử dụng https cho website một cách đơn giản, nhanh chóng. Chúng ta có thể sử dụng 2 dịch vụ rất phổ biến sau đây.

  • Cloudflare: Là 1 dịch vụ bên thứ 3, người dùng sẽ truy cập qua Cloudflare về sau đó được điều hướng tới website của bạn. Chứng chỉ, mã hóa, vân vân, mây mây Cloudflare sẽ lo hết, bạn chỉ đơn giản là đăng ký 1 acccount Cloudflare miễn phí và config 1 số thông tin như địa chỉ IP, tên domain vào là xong. Chi tiết cách setup https bằng Cloudflare cho server của bạn có tại đây. Vì là qua bên thứ 3 nên tốc độ truy cập có thể chậm hơn 1 chút.
  • Let’s encrypt: Là dịch vụ cung cấp chứng chỉ TSL/SSL (HTTP + SSL/TSL = HTTPS) của tổ chức Internet Security Research Group (ISRG). Chúng ta có thể dùng tools cerbot để tạo chứng chỉ TSL/SSL cho website của mình. Điểm hạn chế là chứng chỉ được cấp chỉ có giá trị trong 90 ngày, sau 90 ngày thì chúng ta phải thực hiện việc gia hạn chứng chỉ.

Sử dụng biến môi trường

Tuyệt đối không đưa những biến quan trọng, bí mật như secret key, mật khẩu database hiển thị ở trong mã nguồn. Hãy đưa các biến đó vào trong file .env (đưa file .env vào .gitignore). Sau đó lấy biến ra bằng lệnh process.env.[tên_biến]

# .env
AZURE_STORAGE_KEY='1qazxsw23edc`
 const azure = require('azure');
 const apiKey = process.env.AZURE_STORAGE_KEY;
 const blobService = azure.createBlobService(apiKey);

Dùng brcrypt hoặc pbkdf2 để băm mật khẩu

Không nên sử dụng các hàm băm đã lỗi thời như SHA1, MD5, thư viên Crypto mặc định của NodeJS hay cả những hàm băm khá ‘hiện đại’ như SHA-256 để băm mật khẩu. Vì các hàm băm nêu trên một là rất yếu, 2 là không phù hợp để dùng cho việc băm mật khẩu (Số Hash/giây cao). Brcrypt và Pbkdf2 có chỉ số Hash/giây cao hơn đồng nghĩa với việc hacker muốn tấn công bằng phương pháp từ điển cầu vồng thì mất nhiều công sức, thời gian hơn so với việc sử dụng các hàm băm khác.

Giới hạn kích thước payload request gửi lên server

Giới hạn kích thích payload từ phía client gửi lên server. Cũng là một cách nhằm ngăn chặn các cuộc tấn công DOS/DDOS (bắt server sử lý 1 lượng dữ liệu lớn thay vì gửi thật nhiều request).

const express = require('express');
const app = express();
// body-parser
app.use(express.json({ limit: '300kb' })); defaults to a body size limit of 100kb
// Request with json body
app.post('/json', (req, res) => {
 // Check if request payload content-type matches json, because body-parser does not check for content types
 if (!req.is('json')) {
// -> Unsupported media type if request doesn't have JSON body
     return res.sendStatus(415);
 }
 res.send('Hooray, it worked!');
});
app.listen(3000, () => console.log('Example app listening on port 3000!'));

Kết luận

Qua bài viết, hãy bảo mật app, project của bạn cẩn thận hơn để tránh bị tấn công. Tối ưu bảo mật một cách cẩn thận và chi tiết hơn.

Tham khảo thêm về NodeJS : Tối ưu tốc độ ứng dụng NodeJS khi lập trình


Like it? Share with your friends!

1043
1.4k shares, 1043 points

What's Your Reaction?

hate hate
0
hate
confused confused
0
confused
fail fail
0
fail
fun fun
1
fun
geeky geeky
1
geeky
love love
6
love
lol lol
5
lol
omg omg
5
omg
win win
2
win