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:
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:
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:
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:
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:
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:
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:
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 auditdo 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: