Поради щодо безпеки Express.js: Як ви можете зберегти та захистити свій додаток

Зробіть 7 кроків, щоб переконатися, що ваш додаток непереможний

Ваш телефон заблоковано? У вас є пін-код, пароль, відбиток пальця або FaceID? Я на 99 відсотків впевнений, що ти це робиш. І зрозуміло, чому - ви дбаєте про свою безпеку. У наш час захистити свій телефон так само важливо, як і чистити зуби вранці.

Для старанних та уважних розробників програмного забезпечення захист своїх програм не менш важливий для захисту своїх телефонів. Якщо ви розробник і вирішите нехтувати цим, перегляньте свій підхід. Якщо ви власник проекту і ваша команда розробників каже, що безпека даних може зачекати, перегляньте свою команду.

У цій статті я хочу поговорити про те, як переконатися, що ваш проект Express.js безпечний і непереможний для зловмисних атак.

Для захисту даних слід вжити 7 простих і не дуже простих заходів:

  1. Використовуйте надійні версії Express.js
  2. Захистіть з'єднання та дані
  3. Захистіть свої файли cookie
  4. Захистіть свої залежності
  5. Перевірте введення даних своїх користувачів
  6. Захистіть свою систему від грубої сили
  7. Контролюйте доступ користувачів

Давайте детальніше розглянемо кожен.

1. Використовуйте надійні версії Express.js

Застарілі або застарілі версії Express.js не можна використовувати. Друга та третя версії Express більше не підтримуються. У цьому питання безпеки чи продуктивності вже не вирішені.

Як розробник, вам абсолютно необхідно перейти на Express 4. Ця версія - революція! Зовсім інакше це стосується системи маршрутизації, проміжного програмного забезпечення та інших незначних аспектів.

2. Захистіть з'єднання та дані

Щоб захистити заголовки HTTP, ви можете використовувати Helmet.js - корисний модуль Node.js. Це сукупність 13 проміжних функцій для встановлення заголовків відповідей HTTP. Зокрема, є функції для встановлення Політики безпеки вмісту, обробки прозорості сертифікатів, запобігання клацанню, відключення кешування на стороні клієнта або додавання деяких невеликих захистів XSS.

npm install helmet --save

Навіть якщо ви не хочете використовувати всі функції Шолома, абсолютним мінімумом, який ви повинні зробити, є вимкнення заголовка X-Powered-By:

app.disable('x-powered-by')

Цей заголовок можна використовувати для виявлення того, що програма працює від Express, що дозволяє хакерам проводити точну атаку. Безумовно, заголовок X-Powered-By - не єдиний спосіб ідентифікувати експрес-запуск програми, але це, мабуть, найпоширеніший і найпростіший.

Щоб захистити свою систему від атак забруднення параметрів HTTP, ви можете використовувати HPP. Це проміжне програмне забезпечення відкладає такі параметри, як req.query та req.body, і замість цього вибирає останнє значення параметра. Команда встановлення виглядає так:

npm install hpp --save 

Щоб зашифрувати дані, які надсилаються від клієнта на сервер, використовуйте захист транспортного рівня (TLS). TLS - це криптографічний протокол для захисту комп’ютерної мережі, нащадок шифрування рівня захищеного сокета (SSL). TLS можна обробляти за допомогою Nginx - безкоштовного, але ефективного сервера HTTP - і Let's Encrypt - безкоштовного сертифікату TLS.

3. Захистіть свої файли cookie

У Express.js 4 є два модулі сеансів cookie:

  • експрес-сесія (у Express.js 3 це була express.session)
  • cookie-сесія (у Express.js 3 це була express.cookieSession)

Модуль експрес-сеансу зберігає ідентифікатор сесії у файлі cookie та дані сеансу на сервері. Сесія cookie зберігає всі дані сеансу в файлі cookie.

Загалом сесія cookie є більш ефективною. Однак, якщо дані сеансу, які потрібно зберігати, є складними і можуть перевищувати 4096 байт на файл cookie, використовуйте експрес-сесію. Інша причина використання експрес-сесії - це коли потрібно зберегти дані cookie невидимими для клієнта.

Крім того, слід встановити параметри безпеки файлів cookie, а саме:

  • безпечний
  • httpOnly
  • домен
  • шлях
  • закінчується

Якщо для параметра «захищений» встановлено значення «істина», браузер надсилатиме файли cookie лише через HTTPS. Якщо для параметра «httpOnly» встановлено значення «true», файл cookie буде надісланий не через JS клієнта, а через HTTP (S). Значення “домен” вказує на домен файлу cookie. Якщо домен cookie відповідає домену сервера, для позначення шляху cookie використовується "шлях". Якщо шлях до файлу cookie відповідає шляху запиту, файл cookie буде надісланий у запиті. Нарешті, як випливає з назви, значення “закінчується” означає час, коли термін дії файлів cookie закінчується.

Ще однією важливою рекомендацією є не використовувати назву файлу cookie сеансу за замовчуванням. Це може дозволити хакерам виявити сервер і здійснити цілеспрямовану атаку. Натомість використовуйте загальні назви файлів cookie.

4. Захистіть свої залежності

Без сумніву, npm - це потужний інструмент веб-розробки. Однак, щоб забезпечити найвищий рівень безпеки, розгляньте можливість використання лише 6-ї його версії - npm @ 6. Старіші можуть містити деякі серйозні уразливості щодо залежності, які можуть поставити під загрозу весь ваш додаток. Також для аналізу дерева залежностей використовуйте таку команду:

npm audit

аудит npm може допомогти вирішити реальні проблеми у вашому проекті. Він перевіряє всі ваші залежності у залежностях, devDependencies, bundledDependencies та optionalDependencies, але не ваші peerDependencies. Тут ви можете прочитати про всі поточні вразливості в будь-яких пакунках npm.

Securing dependencies

Іншим інструментом забезпечення безпеки залежності є Snyk. Snyk запускає перевірку програми, щоб визначити, чи містить вона якусь уразливість, перелічену в базі даних із відкритим кодом Snyk. Щоб провести перевірку, виконайте три простих кроки.

Крок 1. Встановіть Snyk

npm install -g snyk cd your-app

Крок 2. Запустіть тест

snyk test

Step 3. Learn how to fix the issue

snyk wizard

Wizard is a Snyk method, which explains the nature of the dependency vulnerability and offers ways of fixing it.

5. Validate the input of your users

Controlling user input is an extremely important part for server-side development. This is a no less important problem than unauthorized requests, which will be described in the seventh part of this article.

First of all, wrong user input can break your server when some values are undefined and you do not have error handling for a specific endpoint. However, different ORM systems can have unpredictable behavior when you try to set undefined, null, or other data types in the database.

For example, destroyAll method in Loopback.js ORM (Node.js framework) can destroy all data in a table of the database: when it does not match any records it deletes everything as described here. Imagine that you can lose all data in a production table just because you have ignored input validation.

Use body/object validation for intermediate inspections

To start with, you can use body/object validation for intermediate inspections. For example, we use ajv validator which is the fastest JSON Schema validator for Node.js.

const Ajv = require('ajv'); const ajv = new Ajv({allErrors: true}); const speaker = { 'type': 'object', 'required': [ 'id', 'name' ], 'properties': { 'id': { 'type': 'integer', }, 'name': { 'type': 'string', }, }, }; const conversation = { type: 'object', required: [ 'duration', 'monologues' ], properties: { duration: { type: 'integer', }, monologues: { type: 'array', items: monolog, }, }, }; const body = { type: 'object', required: [ 'speakers', 'conversations' ], properties: { speakers: { type: 'array', items: speaker, }, conversations: { type: 'array', items: conversation, }, }, }; const validate = ajv.compile(body); const isValidTranscriptBody = transcriptBody => { const isValid = validate(transcriptBody); if (!isValid) { console.error(validate.errors); } return isValid; };

Handle errors

Now, imagine that you forgot to check a certain object and you do some operations with the undefined property. Or you use a certain library and you get an error. It can break your instance, and the server will crash. Then, the attacker can ping a specific endpoint where there is this vulnerability and can stop your server for a long time.

The simplest way to do an error handling is to use try-catch construction:

try { const data = body; if (data.length === 0) throw new Error('Client Error'); const beacons = await this.beaconLogService.filterBeacon(data); if (beacons.length > 0) { const max = beacons.reduce((prev, current) => (prev.rssi > current.rssi) ? prev : current); await this.beaconLogService.save({ ...max, userId: headers['x-uuid'] }); return { data: { status: 'Saved', position: max }, }; } return { data: { status: 'Not valid object, }, }; } catch(err) { this.logger.error(err.message, err.stack); throw new HttpException('Server Error', HttpStatus.INTERNAL_SERVER_ERROR); }

Feel free to use a new Error(‘message’) constructor for error handling or even extend this class for your own purpose!

Use JOI

The main lesson here is that you should always validate user input so you don't fall victim to man-in-the-middle attacks. Another way to do it is with the help of @hapi/joi – a part of the hapi ecosystem and a powerful JS data validation library.

Pay attention here that the module joi has been deprecated. For this reason, the following command is a no go:

npm install joi

Instead, use this one:

npm install @hapi/joi

Use express-validator

One more way to validate user input is to use express-validator – a set of express.js middlewares, which comprises validator.js and function sanitizer. To install it, run the following command:

npm install --save express-validator 

Sanitize user input

Also, an important measure to take is to sanitize user input to protect the system from a MongoDB operator injection. For this, you should install and use express-mongo-sanitize:

npm install express-mongo-sanitize

Protect your app against CSRF

Besides, you should protect your app against cross-site request forgery (CSRF). CSRF is when unauthorized commands are sent from a trusted user. You can do this with the help of csurf. Prior to that, you need to make sure that session middleware for cookies is configured as described earlier in this article. To install this Node.js module, run the command:

npm install csurf 

6. Protect your system against brute force

A brute force attack is the simplest and most common way to get access to a website or a server. The hacker (in most cases automatically, rarely manually) tries various usernames and passwords repeatedly to break into the system.

These attacks can be prevented with the help of rate-limiter-flexible package. This package is fast, flexible, and suitable for any Node framework.

To install, run the following command:

npm i --save rate-limiter-flexible yarn add rate-limiter-flexible

This method has a simpler but more primitive alternative: express-rate-limit. The only thing it does is limiting repeated requests to public APIs or to password reset.

npm install --save express-rate-limit

7. Control user access

Among the authentication methods, there are tokens, Auth0, and JTW. Let’s focus on the third one! JTW (JSON Web Tokens) are used to transfer authentication data in client-server applications. Tokens are created by the server, signed with a secret key, and transferred to a client. Then, the client uses these tokens to confirm identity.

Express-jwt-permissions is a tool used together with express-jwt to check permissions of a certain token. These permissions are an array of strings inside the token:

"permissions": [   "status",   "user:read",   "user:write" ]

To install the tool, run the following command:

npm install express-jwt-permissions --save

To Wrap Up

Here, I have listed the essential Express.js security best practices and some tools that can be used along the way.

Just to review:

Securing dependencies

I strongly recommend that you make sure that your application is resistant to malicious attacks. Otherwise, your business and your users may suffer significant losses.

Do you have an idea for Express.js project?

My company KeenEthics is experienced in express js development. In case you need a free estimate of a similar project, feel free to get in touch.

If you have enjoyed the article, you should definitely continue with a piece on data safety in outsourcing to Ukraine: KeenEthics Team on Guard: Your Data is Safe in Ukraine. The original article posted on KeenEthics blog can be found here: express js security tips.

P.S.

A huge shout-out to Volodia Andrushchak, Full-Stack Software Developer @KeenEthics for helping me with the article.

The original article posted on KeenEthics blog can be found here: Express.js Security Tips: Save Your App!