Skip to content

Commit 2ce17c0

Browse files
author
Automatajm
committed
�️ Fix CORS misconfiguration security vulnerability
- Replace insecure wildcard CORS with strict origin validation - Only allow credentials for whitelisted origins - Add security logging for unauthorized CORS attempts - Prevent origin null attacks - Add preflight request validation - Enhanced CORS security middleware - Resolves: CORS misconfiguration for credentials transfer CodeQL alert
1 parent 5aadb6d commit 2ce17c0

1 file changed

Lines changed: 87 additions & 11 deletions

File tree

backend/server.js

Lines changed: 87 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const cors = require('cors');
33
const bcrypt = require('bcrypt');
44
const https = require('https');
55
const fs = require('fs');
6-
const rateLimit = require('express-rate-limit'); // 🔒 NUEVO: Rate limiting
6+
const rateLimit = require('express-rate-limit');
77
const config = require('./config');
88
const db = require('./db');
99
const path = require('path');
@@ -154,7 +154,6 @@ const Logger = {
154154
}
155155
},
156156

157-
// 🔒 NUEVO: Logger para rate limiting
158157
rateLimitExceeded: (req, limitType) => {
159158
const clientIP = req.headers['x-forwarded-for'] ||
160159
req.headers['x-real-ip'] ||
@@ -351,21 +350,99 @@ app.use(cors({
351350
// 🔒 Rate limiter general ANTES de otros middlewares
352351
app.use(generalLimiter);
353352

354-
// ===== MIDDLEWARE ADICIONAL =====
353+
// ===== MIDDLEWARE ADICIONAL SEGURO =====
355354
app.use((req, res, next) => {
356-
res.header('Access-Control-Allow-Origin', req.headers.origin || '*');
357-
res.header('Access-Control-Allow-Credentials', 'true');
355+
// 🔒 CORS SEGURO: Solo orígenes whitelisteados con credenciales
356+
const requestOrigin = req.headers.origin;
357+
358+
// Verificar si el origen está en la whitelist
359+
const isOriginAllowed = requestOrigin && allowedOrigins.some(allowedOrigin => {
360+
if (typeof allowedOrigin === 'string') {
361+
return requestOrigin === allowedOrigin;
362+
} else if (allowedOrigin instanceof RegExp) {
363+
return allowedOrigin.test(requestOrigin);
364+
}
365+
return false;
366+
});
367+
368+
// Solo establecer headers CORS para orígenes permitidos
369+
if (isOriginAllowed) {
370+
// ✅ SEGURO: Origin específico validado
371+
res.header('Access-Control-Allow-Origin', requestOrigin);
372+
res.header('Access-Control-Allow-Credentials', 'true');
373+
} else if (isDevelopment && !requestOrigin) {
374+
// Solo en desarrollo: permitir requests sin origin (Postman, curl, etc.)
375+
res.header('Access-Control-Allow-Origin', '*');
376+
// ✅ NO permitir credenciales con origen wildcard
377+
res.header('Access-Control-Allow-Credentials', 'false');
378+
} else {
379+
// ❌ Origen no permitido: no establecer headers CORS
380+
if (requestOrigin) {
381+
Logger.security('CORS: Origen no permitido intentando acceso con credenciales', {
382+
origin: requestOrigin,
383+
ip: req.headers['x-forwarded-for'] || req.connection.remoteAddress,
384+
userAgent: req.headers['user-agent']
385+
});
386+
}
387+
388+
// No establecer headers CORS para orígenes no permitidos
389+
// El navegador bloqueará la request
390+
}
391+
392+
// Headers comunes que son seguros
358393
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS,PATCH');
359394
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With, Accept, Origin, ngrok-skip-browser-warning');
395+
res.header('Access-Control-Max-Age', '3600'); // Cache preflight por 1 hora
360396

397+
// Manejar preflight requests
361398
if (req.method === 'OPTIONS') {
362-
res.status(204).send();
399+
if (isOriginAllowed || (isDevelopment && !requestOrigin)) {
400+
res.status(204).send();
401+
} else {
402+
Logger.security('CORS: Preflight bloqueado para origen no permitido', requestOrigin);
403+
res.status(403).json({
404+
success: false,
405+
message: 'Origen no permitido',
406+
error: 'CORS_ORIGIN_NOT_ALLOWED'
407+
});
408+
}
363409
return;
364410
}
365411

366412
next();
367413
});
368414

415+
// 🔒 MIDDLEWARE ADICIONAL DE SEGURIDAD CORS
416+
app.use((req, res, next) => {
417+
// Verificar que no se esté intentando un ataque CORS
418+
const origin = req.headers.origin;
419+
const referer = req.headers.referer;
420+
421+
// Detectar intentos de ataque CORS
422+
if (origin === 'null' && req.headers['access-control-request-method']) {
423+
Logger.security('CORS: Intento de ataque con origin null detectado', {
424+
ip: req.headers['x-forwarded-for'] || req.connection.remoteAddress,
425+
userAgent: req.headers['user-agent'],
426+
referer: referer
427+
});
428+
}
429+
430+
// Logging adicional en producción para monitoreo
431+
if (isProduction && origin && !allowedOrigins.some(allowed => {
432+
if (typeof allowed === 'string') return origin === allowed;
433+
if (allowed instanceof RegExp) return allowed.test(origin);
434+
return false;
435+
})) {
436+
Logger.security('CORS: Intento de acceso desde origen no autorizado en producción', {
437+
origin: origin,
438+
path: req.path,
439+
method: req.method
440+
});
441+
}
442+
443+
next();
444+
});
445+
369446
app.use(express.json({ limit: '50mb' }));
370447
app.use(express.urlencoded({ extended: true, limit: '50mb' }));
371448

@@ -379,7 +456,7 @@ app.use((req, res, next) => {
379456

380457
Logger.api(req.method, req.path, req.headers.origin);
381458

382-
// 🔒 NUEVO: Log rate limit info si está disponible
459+
// 🔒 Log rate limit info si está disponible
383460
if (req.rateLimit && !isProduction) {
384461
console.debug(`Rate Limit Info: ${req.rateLimit.remaining}/${req.rateLimit.limit} remaining for ${clientIP}`);
385462
}
@@ -506,7 +583,6 @@ app.get('/', (req, res) => {
506583
allowedOrigins: allowedOrigins.length,
507584
currentOrigin: req.headers.origin || 'No origin'
508585
},
509-
// 🔒 NUEVO: Rate limit info
510586
rateLimitInfo: req.rateLimit ? {
511587
remaining: req.rateLimit.remaining,
512588
total: req.rateLimit.limit,
@@ -746,7 +822,7 @@ function formatUptime(seconds) {
746822

747823
// ===== MANEJO DE ERRORES =====
748824
app.use((err, req, res, next) => {
749-
// 🔒 NUEVO: Log específico si es error de rate limiting
825+
// 🔒 Log específico si es error de rate limiting
750826
if (err.status === 429 || err.type === 'rate_limit') {
751827
Logger.rateLimitExceeded(req, 'MIDDLEWARE_ERROR');
752828
} else {
@@ -796,18 +872,18 @@ const server = https.createServer(sslOptions, app).listen(config.port, '0.0.0.0'
796872
Logger.startup(`Entorno: ${config.environment}`);
797873
Logger.startup(`Versión: ${config.version}`);
798874

799-
// 🔒 NUEVO: Log de configuración de rate limiting
875+
// 🔒 Log de configuración de seguridad
800876
Logger.startup(`Rate Limiting: ${isProduction ? 'STRICT' : 'PERMISSIVE'} mode`);
801877
Logger.startup(`Auth Rate Limit: ${isProduction ? '5' : '20'} attempts per 15min`);
802878
Logger.startup(`General Rate Limit: ${isProduction ? '100' : '200'} requests per 15min`);
803879
Logger.startup(`DB API Rate Limit: ${isProduction ? '50' : '100'} requests per 10min`);
804880
Logger.startup(`Config Rate Limit: ${isProduction ? '20' : '50'} requests per 5min`);
881+
Logger.startup(`CORS Security: Whitelist-only mode with ${allowedOrigins.length} allowed origins`);
805882

806883
if (!isProduction) {
807884
Logger.startup(`URL Local HTTPS: https://localhost:${config.port}`);
808885
Logger.startup(`URL Red HTTPS: https://10.0.0.19:${config.port}`);
809886
Logger.startup(`Sistema: ${config.system.platform} - ${config.system.hostname}`);
810-
Logger.startup(`CORS: Configurado para ${allowedOrigins.length} orígenes`);
811887
}
812888

813889
// Probar conexión a la base de datos

0 commit comments

Comments
 (0)