El módulo 7 terminó con una promesa: teníamos el proceso completo ensamblado —S-SDLC, threat modeling, DevSecOps, capacitación y un stack de herramientas para BazarNube— y llegaba el momento de aplicarlo con las manos. Este módulo cumple esa promesa. En estas cuatro lecciones dejamos de describir el proceso y lo ejecutamos sobre BazarNube: dos ejercicios guiados de identificación y remediación, y dos casos de estudio de análisis de incidente y de mejora. Empezamos por el principio de cualquier trabajo de AppSec: encontrar lo que está mal. En este laboratorio recibes un fragmento real de la aplicación —varios endpoints Node/Express, un componente React y su configuración— y tu tarea es identificar, clasificar y documentar cada vulnerabilidad como lo haría un analista, combinando revisión de código y escaneo con ZAP (visto a fondo en el módulo 6). No repares nada todavía: eso es el ejercicio 2. Aquí solo cazamos y catalogamos.

Contenido

  1. Objetivo y entorno del laboratorio
  2. Metodología: cómo abordar la identificación
  3. El checklist basado en Top Ten y ASVS
  4. El código a revisar: endpoints, componente y configuración
  5. Cómo documentar un hallazgo y registrarlo en el backlog
  6. Errores comunes y consejos
  7. Ejercicios
  8. Conclusión

Objetivo y entorno del laboratorio

Trabajas como AppSec en BazarNube junto a Lucía (backend lead) y Marc (frontend). La SRE ha desplegado en el entorno de staging una nueva rama del servicio de pedidos y catálogo, y antes de aprobar el merge debes revisarla. Dispones de dos entradas de información:

  • El código fuente del fragmento (abajo), sobre el que harás una revisión manual.
  • Un escaneo ZAP de la instancia de staging ya en marcha, cuyo informe consultarás para confirmar hallazgos y encontrar los que el código no revela a simple vista.

El objetivo del laboratorio no es explotar nada a fondo, sino identificar y clasificar: por cada problema, determinar su categoría del OWASP Top Ten 2021, su severidad, la evidencia que lo respalda y el requisito ASVS que incumple. El resultado es una lista de hallazgos lista para entrar en el backlog BZN-xxx, exactamente el formato que ZAP ya alimentaba en 06-03.

Recordatorio ético: todo esto se hace sobre nuestra aplicación en nuestro staging. La identificación de vulnerabilidades solo es legítima sobre sistemas propios o con autorización explícita, como establecimos en el módulo 1.

Metodología: cómo abordar la identificación

Improvisar lleva a olvidos. Seguimos un método repetible que combina las dos técnicas:

graph LR
    A[Entender el contexto] --> B[Revision de codigo guiada por checklist]
    B --> C[Escaneo ZAP pasivo y activo]
    C --> D[Correlacionar codigo y alertas]
    D --> E[Triaje: confirmar o descartar]
    E --> F[Documentar cada hallazgo BZN]
  1. Entender el contexto. ¿Qué hace este código? ¿Qué datos maneja? En BazarNube hay pedidos, precios, datos personales y pagos: cualquier fallo aquí toca dinero o PII.
  2. Revisión de código guiada por checklist. No leas el código "a ver qué encuentras"; recórrelo con una lista de preguntas concretas (siguiente sección). Presta atención especial a los cruces de límite de confianza del threat model de 07-02: entrada del usuario, consultas a BD, llamadas a terceros.
  3. Escaneo ZAP. El escaneo pasivo detecta cabeceras y cookies mal configuradas; el activo prueba inyección, XSS o path traversal. No re-explicamos ZAP: lo usamos como en 06-03.
  4. Correlacionar. Cada alerta de ZAP debe poder señalarse en el código, y cada sospecha del código debe intentar confirmarse dinámicamente. Lo que aparece por ambas vías es casi siempre real.
  5. Triaje. Descarta falsos positivos con evidencia (reproducir la petición), no por intuición.
  6. Documentar. Un hallazgo sin ficha reproducible no existe para el equipo.

El checklist basado en Top Ten y ASVS

Este es el guion de preguntas con el que recorremos el código. Cada fila apunta a una categoría del Top Ten y a los capítulos ASVS que verifican ese control.

# Pregunta de revisión Top Ten ASVS
1 ¿Se comprueba que el usuario es dueño del objeto que pide (pedido, factura)? A01 V4 (Access Control)
2 ¿Alguna consulta concatena entrada del usuario en SQL/comandos? A03 V5 (Validation/Encoding)
3 ¿Se renderiza contenido del usuario como HTML sin escapar? A03 V5
4 ¿Hay secretos embebidos en el código o algoritmos criptográficos débiles? A02 V6 (Cryptography)
5 ¿La app hace peticiones a URLs que controla el usuario? A10 V12 (SSRF)
6 ¿Se accede a ficheros con nombres que vienen del usuario? A01 V4, V12
7 ¿Faltan cabeceras de seguridad (CSP, HSTS) o helmet? A05 V14 (Config)
8 ¿Las cookies llevan HttpOnly, Secure y SameSite? A05 V3 (Session)
9 ¿Los errores exponen stack traces o detalles internos? A05 V7 (Errors/Logging)
10 ¿Hay rate limiting en autenticación y endpoints sensibles? A07 V2 (Authentication)

El código a revisar: endpoints, componente y configuración

A continuación, el fragmento entregado. Léelo con el checklist en la mano antes de mirar la solución.

config.js (configuración del backend)

// config.js — configuracion del servicio de pedidos
module.exports = {
  jwtSecret: 'bazarnube-2021',        // usado para firmar los tokens de sesion
  jwtAlg: 'HS256',
  db: { host: 'db', user: 'app', password: 'app', ssl: false },
  cookie: { httpOnly: false, secure: false }, // opciones de la cookie de sesion
};

app.js (arranque de Express)

const express = require('express');
const cookieParser = require('cookie-parser');
const app = express();

app.use(express.json());
app.use(cookieParser());
// (no se instala helmet ni se define ninguna Content-Security-Policy)

app.use(require('./routes/orders'));
app.use(require('./routes/catalog'));

// Manejador de errores global
app.use((err, req, res, next) => {
  res.status(500).json({ error: err.message, stack: err.stack }); // detalle interno al cliente
});

app.listen(3000);

routes/orders.js

const router = require('express').Router();
const db = require('../db');
const path = require('path');
const auth = require('../middleware/auth'); // valida el JWT y pone req.user

// Detalle de un pedido
router.get('/api/orders/:id', auth, async (req, res) => {
  const { rows } = await db.query('SELECT * FROM orders WHERE id = $1', [req.params.id]);
  res.json(rows[0]);   // devuelve el pedido sin comprobar que sea del usuario
});

// Descarga de la factura en PDF
router.get('/api/invoices', auth, (req, res) => {
  const file = req.query.file; // ej. ?file=2026-000123.pdf
  res.sendFile(path.join('/var/bazarnube/invoices', file));
});

module.exports = router;

routes/catalog.js

const router = require('express').Router();
const db = require('../db');
const auth = require('../middleware/auth');

// Buscador de productos
router.get('/api/products/search', async (req, res) => {
  const q = req.query.q;
  const sql = `SELECT id, name, price FROM products WHERE name LIKE '%${q}%'`;
  const { rows } = await db.query(sql);  // se concatena q directamente
  res.json(rows);
});

// Importar producto: descarga la imagen desde una URL indicada por el vendedor
router.post('/api/products/import', auth, async (req, res) => {
  const { imageUrl } = req.body;
  const resp = await fetch(imageUrl); // se descarga cualquier URL que envie el usuario
  const buffer = Buffer.from(await resp.arrayBuffer());
  // ... guarda el buffer como imagen del producto
  res.json({ ok: true });
});

module.exports = router;

ProductReviews.jsx (componente React)

export function ProductReviews({ reviews }) {
  // reviews[].body es texto libre escrito por otros compradores
  return (
    <ul>
      {reviews.map((r) => (
        <li key={r.id} dangerouslySetInnerHTML={{ __html: r.body }} />
      ))}
    </ul>
  );
}

Extracto del informe ZAP (staging)

El escaneo de 06-03 sobre esta rama devolvió, entre otras, estas alertas pasivas y activas:

[High]   SQL Injection             GET /api/products/search (q)
[High]   Path Traversal            GET /api/invoices (file)
[Medium] CSP: Header Not Set        /
[Low]    Cookie No HttpOnly Flag    Set-Cookie: session
[Low]    Application Error Disclosure  500 con stack trace

Cómo documentar un hallazgo y registrarlo en el backlog

Un hallazgo útil es reproducible y accionable. Cada ficha BZN-xxx incluye los mismos campos que ZAP ya nos daba, más el requisito ASVS incumplido:

  • ID: BZN-xxx.
  • Título: qué es, en una línea.
  • Categoría OWASP: A01–A10 del Top Ten 2021.
  • Severidad: Crítica/Alta/Media/Baja (por riesgo = impacto x probabilidad).
  • Ubicación: fichero/endpoint y línea.
  • Evidencia: la petición o el fragmento de código que lo prueba.
  • Requisito ASVS: el control que incumple (para el ejercicio 2).
  • Cómo se detectó: revisión de código, ZAP, o ambas (mayor confianza).

Ejemplo de ficha bien redactada:

ID:          BZN-101
Titulo:      IDOR en GET /api/orders/:id (falta control de propiedad)
Categoria:   A01 Broken Access Control
Severidad:   Alta (acceso a PII y pedidos de otros clientes)
Ubicacion:   routes/orders.js, handler GET /api/orders/:id
Evidencia:   Autenticado como cliente A, GET /api/orders/778 (pedido de B)
             devuelve 200 con los datos de B. No se filtra por req.user.id.
ASVS:        V4.1.1 / V4.2.1 (autorizacion a nivel de objeto)
Deteccion:   Revision de codigo (ZAP no lo marca: requiere logica de negocio)

Nótese el matiz de la última línea: ZAP no detecta el IDOR porque no conoce quién debería poder ver qué. Los fallos de lógica de autorización se cazan casi siempre por revisión de código, mientras que inyección, XSS o path traversal aparecen también en el escaneo dinámico. Por eso el método combina ambas técnicas: ninguna sola es suficiente.

Errores Comunes y Consejos

  • Fiarse solo del escáner. ZAP no encuentra IDOR ni secretos embebidos ni criptografía débil. Si tu identificación se limita al informe de ZAP, se te escapará A01 y buena parte de A02. Combina siempre con revisión de código.
  • Confundir cantidad con calidad. Reportar cien alertas pasivas triviales entierra las dos críticas de verdad. Prioriza por riesgo real, como en el triaje de 06-03.
  • No reproducir antes de reportar. Un hallazgo sin evidencia reproducible se discute eternamente. Adjunta la petición o la línea exacta.
  • Ignorar el dangerouslySetInnerHTML. React escapa por defecto, así que muchos revisores asumen que "no hay XSS". Esa API concreta desactiva la protección: es un imán de A03.
  • Consejo: revisa siempre los cruces de límite de confianza primero (entrada de usuario, consultas a BD, llamadas a terceros, acceso a ficheros). Ahí vive la inmensa mayoría de los hallazgos, como anticipaba el threat model de 07-02.

Ejercicios

Ejercicio 1. Recorre el fragmento con el checklist y elabora la lista completa de hallazgos: por cada uno, indica endpoint/fichero, categoría del Top Ten 2021 y severidad. Deberías encontrar al menos nueve.

Ejercicio 2. Para el endpoint GET /api/invoices, redacta la ficha BZN-140 completa (todos los campos) e indica el payload concreto que usarías como evidencia.

Ejercicio 3. Justifica por qué el IDOR de pedidos no aparece en el informe de ZAP y qué técnica sí lo detecta. Generaliza: ¿qué categorías del Top Ten se le escapan estructuralmente a un DAST?

Soluciones

Solución 1. Lista de hallazgos del laboratorio:

ID Hallazgo Ubicación OWASP 2021 Severidad Detección
BZN-134 SQLi por concatenación de q catalog.js /search A03 Injection Crítica Código + ZAP
BZN-101 IDOR: pedido de otro usuario orders.js /orders/:id A01 Broken Access Control Alta Código
BZN-140 Path traversal en file= orders.js /invoices A01 Broken Access Control Alta Código + ZAP
BZN-131 SSRF: descarga de URL arbitraria catalog.js /import A10 SSRF Alta Código
BZN-087 XSS almacenado vía dangerouslySetInnerHTML ProductReviews.jsx A03 Injection Alta Código
BZN-155 Secreto JWT embebido y débil config.js A02 Cryptographic Failures Alta Código
BZN-041 Sin CSP ni helmet app.js A05 Security Misconfiguration Media ZAP
BZN-039 Cookie sin HttpOnly/Secure/SameSite config.js A05 Security Misconfiguration Media ZAP
BZN-060 Stack trace expuesto al cliente app.js (handler de error) A05 Security Misconfiguration Baja Código + ZAP

Varios (BZN-140, BZN-131, BZN-087, BZN-041, BZN-039, BZN-060) ya existían en el backlog desde el escaneo de 06-03: este ejercicio los confirma por revisión de código y añade dos nuevos (BZN-134, BZN-101).

Solución 2. Ficha de BZN-140:

ID:          BZN-140
Titulo:      Path Traversal en GET /api/invoices (parametro file)
Categoria:   A01 Broken Access Control
Severidad:   Alta (lectura de ficheros arbitrarios del servidor)
Ubicacion:   routes/orders.js, handler GET /api/invoices
Evidencia:   GET /api/invoices?file=../../../../etc/passwd  -> 200 con el fichero
             sendFile une el path sin normalizar ni validar que quede dentro de
             /var/bazarnube/invoices.
ASVS:        V4.1.3 / V12.3.1 (control de acceso a ficheros, path canonizado)
Deteccion:   Revision de codigo + alerta activa de ZAP "Path Traversal"

El payload de evidencia es ?file=../../../../etc/passwd (o cualquier ruta fuera del directorio de facturas). Confirma la lectura arbitraria.

Solución 3. ZAP no detecta el IDOR porque un DAST prueba entradas y respuestas sin conocer las reglas de negocio: no sabe que el pedido 778 pertenece a otro usuario, así que un 200 le parece correcto. Detectarlo requiere entender la lógica de autorización, lo que se hace por revisión de código o con pruebas manuales autenticadas con dos usuarios. En general, a un DAST se le escapan de forma estructural: A01 (control de acceso/IDOR/escalada), buena parte de A02 (secretos y cripto débil en el código), A04 (fallos de diseño) y A08 (integridad), porque dependen de contexto y lógica, no de patrones observables en la respuesta.

Conclusión

Hemos convertido un fragmento de BazarNube en una lista de nueve hallazgos priorizados, cada uno con su categoría del Top Ten, su severidad, su evidencia y su requisito ASVS incumplido, todos registrados como fichas BZN-xxx en el backlog. Lo importante no es solo la lista, sino el método: contexto, checklist Top Ten/ASVS, revisión de código, escaneo ZAP y correlación, con la lección clave de que ninguna técnica sola basta —el escáner no ve el IDOR, y la revisión manual no escala como el escáner—. Ahora tenemos el diagnóstico; falta la cura. En la siguiente lección, 08-02, tomamos exactamente estas nueve fichas y las remediamos una a una: código corregido, mapeo a requisitos ASVS y re-escaneo para verificar que el control funciona de verdad.

Curso de OWASP: Directrices y Estándares para la Seguridad en Aplicaciones Web

Módulo 1: Introducción a OWASP

Módulo 2: Principales Proyectos de OWASP

Módulo 3: OWASP Top Ten 2021 en Profundidad

Módulo 4: OWASP ASVS (Application Security Verification Standard)

Módulo 5: OWASP SAMM (Software Assurance Maturity Model)

Módulo 6: OWASP ZAP (Zed Attack Proxy)

Módulo 7: Buenas Prácticas y Recomendaciones

Módulo 8: Ejercicios Prácticos y Casos de Estudio

Módulo 9: Evaluación y Certificación

© Copyright 2026. Todos los derechos reservados