Bezpieczeństwo i walidacja danych w Node.js: Kluczowe praktyki

W dzisiejszym świecie aplikacji webowych, gdzie Node.js jest jednym z najpopularniejszych środowisk do budowania serwerów backendowych, bezpieczeństwo i walidacja danych odgrywają kluczową rolę. Nieprawidłowo zwalidowane dane mogą prowadzić do ataków takich jak SQL Injection, Cross-Site Scripting (XSS) czy Denial of Service (DoS). W tym artykule omówimy kluczowe praktyki, biblioteki i przykłady kodu, które pomogą Ci zabezpieczyć aplikację Node.js. Skupimy się na walidacji wejściowych danych oraz ogólnych mechanizmach bezpieczeństwa, opierając się na rekomendacjach ekspertów i organizacji takich jak OWASP.

Walidacja danych: Dlaczego i jak?

Walidacja danych to proces sprawdzania, czy dane wejściowe (np. z formularzy, zapytań API) spełniają oczekiwane kryteria, takie jak typ, długość czy format. Brak walidacji może umożliwić wstrzyknięcie złośliwego kodu. Według OWASP, walidacja powinna opierać się na liście akceptowanych wejść lub schemacie, a niebezpieczne dane powinny być escapowane.

Popularne biblioteki do walidacji

  • express-validator: Integruje się z frameworkiem Express, opierając się na validator.js. Pozwala na walidację łańcuchową (chain validation).
  • Joi: Biblioteka do definiowania schematów walidacji, idealna do API REST.
  • Yup: Podobna do Joi, często używana z Express do walidacji schematów.

Przykłady walidacji z express-validator

W aplikacji Express możesz walidować parametry zapytania, parametry URL czy ciało żądania. Na przykład, walidacja parametru userId jako liczby całkowitej w endpointcie GET:

JavaScript
const { param, validationResult } = require('express-validator');

app.get('/api/v1/users/:userId', param('userId').isInt(), (req, res) => {
  const result = validationResult(req);
  if (result.isEmpty()) {
    // Przetwarzaj żądanie
    res.send({ user: /* pobierz użytkownika */ });
  } else {
    res.status(400).send({ errors: result.array() });
  }
});

Ten kod sprawdza, czy userId jest liczbą całkowitą, i zwraca błąd 400, jeśli nie.

Dla walidacji ciała żądania w POST, np. podczas tworzenia użytkownika:

JavaScript
const { body } = require('express-validator');

app.post('/api/v1/users', 
  body('fullName').trim().notEmpty(),
  body('email').isEmail().withMessage('Nieprawidłowy adres e-mail'),
  body('age').isInt({ min: 18 }),
  (req, res) => {
    const result = validationResult(req);
    if (result.isEmpty()) {
      // Zapisz użytkownika
      res.status(201).send();
    } else {
      res.status(400).send({ errors: result.array() });
    }
  }
);

To zapewnia, że fullName nie jest pusty, email jest poprawny, a age to liczba >= 18.

Walidacja z Joi

Joi pozwala na definiowanie schematu:

JavaScript
const Joi = require('joi');
const schema = Joi.object({
  username: Joi.string().alphanum().min(3).max(30).required(),
  password: Joi.string().pattern(new RegExp('^[a-zA-Z0-9]{6,30}$')).required()
});

const { error } = schema.validate(req.body);
if (error) {
  return res.status(400).send(error.details[0].message);
}

Ta praktyka blokuje nieprawidłowe wejścia i zapobiega atakom wstrzykiwania.

Bezpieczeństwo w Node.js: Kluczowe mechanizmy

Bezpieczeństwo obejmuje nie tylko walidację, ale też ochronę przed atakami na poziomie aplikacji i serwera. Oficjalna dokumentacja Node.js podkreśla unikanie ataków takich jak Prototype Pollution czy Timing Attacks.

Sanitizacja danych i ochrona przed atakami

Sanitizuj dane, aby usunąć niebezpieczne znaki. Użyj bibliotek jak express-mongo-sanitize do ochrony przed NoSQL Injection czy xss-clean przed XSS:

JavaScript
const mongoSanitize = require('express-mongo-sanitize');
const xss = require('xss-clean');
app.use(mongoSanitize());
app.use(xss());

To middleware automatycznie czyści żądania.

HTTPS i zabezpieczenia nagłówków

Zawsze używaj HTTPS do szyfrowania transmisji. Wymuś HTTPS middlewarem:

JavaScript
app.use((req, res, next) => {
  if (req.headers['x-forwarded-proto'] !== 'https') {
    return res.redirect('https://' + req.headers.host + req.url);
  }
  next();
});

Użyj helmet do ustawiania bezpiecznych nagłówków HTTP:

JavaScript
const helmet = require('helmet');
app.use(helmet());

To chroni przed XSS, clickjackingiem i innymi atakami.

Rate limiting i ochrona przed DoS

Ogranicz liczbę żądań, aby zapobiec atakom brute-force. Użyj express-rate-limit:

JavaScript
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minut
  max: 100 // maks. 100 żądań na IP
});
app.use('/api/', limiter);

Monitoruj obciążenie serwera z toobusy-js, aby unikać DoS.

Autentykacja i CSRF

Do autentykacji używaj JWT lub sesji. Ochrona przed CSRF: Użyj middleware jak csurf (choć jest przestarzałe, szukaj alternatyw). Dla brute-force, implementuj blokady kont i CAPTCHA.

Inne praktyki z OWASP i Node.js

  • Logowanie aktywności: Używaj Winston lub Pino do rejestrowania zdarzeń dla debugowania i bezpieczeństwa.
  • Ograniczanie rozmiaru żądań: Ustaw limity w Express: app.use(express.json({ limit: '1kb' }));.
  • Bezpieczne moduły: Pinuj wersje zależności i używaj npm audit do sprawdzania luk.

Wniosek

Implementacja tych praktyk znacząco zwiększa bezpieczeństwo aplikacji Node.js. Zawsze aktualizuj zależności, testuj aplikację i śledź nowe zagrożenia. Pamiętaj, że bezpieczeństwo to proces ciągły – regularne audyty są kluczowe.

Citations: