Solucionario y conducción del taller de DevTools

Material del profesor. Entrega al alumnado la guía y el laboratorio; reserva este archivo si utilizas el reto como prueba de comprensión.

Decisión docente

Chrome es la referencia de las explicaciones por la organización conjunta de depuración, red, almacenamiento y overrides. Firefox sirve para verificar que los conceptos se trasladan entre navegadores y para practicar el editor de peticiones. Evita presentar capacidades de seguridad como exclusivas de uno: Chrome permite reenviar como fetch y Firefox reciente dispone de overrides.

Antes de clase prueba ambos puertos, login, catálogo, la marca XSS y la página CSRF en la imagen real del aula. Un navegador administrado puede restringir herramientas o carpetas de overrides. Las capturas del alumnado deben mostrar versión y variante de la página cuando eso afecte al resultado.

Secuencia de demostración inicial

Muestra el precio 25, cambia solo el texto a 1 y pregunta qué cree el grupo que sucederá al comprar. Envía: el total sigue siendo 25. Cambia después el campo oculto: en la variante vulnerable el total pasa a 1. Repite con la API segura: vuelve a 25. Esta secuencia permite introducir tres capas distintas con una evidencia visible en cada una.

Después revela el botón de administración. Su respuesta 403 contrasta con el fallo de precio: la aplicación incluye controles que funcionan y otros que fallan. No se busca que el alumnado marque todo como vulnerable, sino que justifique cada conclusión.

Resultados por práctica

Práctica Resultado que debe observarse Idea que debe explicar el alumno
P1 DOM y CSS Cambios locales; admin 403 con sesión; restauración tras recarga Una representación local no es una modificación de servidor
P2 Consola dataset produce cadena; fetch genera una petición El contexto es el de la página; HTTP 403 no rechaza fetch por sí solo
P3 HTTP GET de catálogo, POST de eco, JSON y retraso aproximado de 800 ms URL, cabeceras y cuerpo son partes distintas
P4 Depuración a y b son cadenas; resultado 23; conversión da 5 El tipo de dato explica el comportamiento
P5 Validación La interfaz impide cantidad 0; petición directa recibe 400 Cliente y servidor validan en capas distintas
P6 Compra Precio enviado 1 y cantidad 2 da 2 en vulnerable y 50 en seguro El precio debe obtenerse del catálogo de servidor
P7 Almacenamiento rol_demo cambia; rol servidor no; sesion no aparece en document.cookie HttpOnly limita APIs de página, no al dueño de DevTools
P8 IDOR 1002 devuelve objeto ajeno en vulnerable; 403 en seguro Sesión existente no implica permiso sobre todos los objetos
P9 XSS innerHTML ejecuta onerror; textContent muestra entrada literal Debe identificar fuente, destino e interpretación
P10 CSP CSP bloquea manejador; Report Only lo permite; marco protegido no carga Una política de observación no aplica bloqueo
P11 CORS Lectura denegada o autorizada; preflight; contador sube pese al error Hay que distinguir envío de petición y lectura de respuesta
P12 CSRF Formulario externo cambia tema; variante protegida 403; legítima 200 Origen, token de sesión y contexto de site tienen funciones distintas
P13 Exposición Configuración ficticia visible en JS y API La gravedad depende de qué permite hacer el valor
P14 Overrides Cambia catálogo local; compra segura conserva precio real Sustituir una respuesta local no altera el backend
P15 Calidad Error al desconectar, tarea acotada de 250 ms, revisión de etiquetas Medir y observar antes de proponer una mejora

Peticiones de referencia

Ejecuta desde la consola de http://localhost:8000, después de iniciar sesión. Los ejemplos no necesitan construir manualmente Cookie. El navegador la adjunta cuando procede.

Precio y cantidad

fetch('/api/compra', {
  method: 'POST', headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({producto: 1, cantidad: 2, precio: 1})
}).then(async r => console.log(r.status, await r.json()));

Se espera 200, cantidad 2, total 2 y simulacion true. Con ?modo=seguro, el mismo cuerpo devuelve total 50. Cantidad 0, 11, 1.5 o la cadena “2” devuelve 400 en las dos variantes. El fallo de precio es independiente de esa validación.

Autorización

Promise.all([
  '/api/pedido?id=1001',
  '/api/pedido?id=1002',
  '/api/pedido?id=1001&modo=seguro',
  '/api/pedido?id=1002&modo=seguro',
  '/api/admin'
].map(async url => {
  const r = await fetch(url);
  return {url, estado: r.status, respuesta: await r.json()};
})).then(console.table);

Estados esperados con sesión: 200, 200, 200, 403 y 403. Sin sesión los pedidos y admin responden 401.

XSS y modos

Entrada de referencia:

<img src="/imagen-inexistente" onerror="document.getElementById('marca').textContent='XSS demostrado en el laboratorio'">

Prueba la misma entrada en cuatro navegaciones desde cero:

No conviertas el botón Mostrar entrada como texto en una supuesta limpieza de toda la sesión: elimina el marcado del saludo, pero una ejecución anterior ya ha ocurrido. Para la comprobación de corrección hay que volver a cargar con modo=seguro.

Aspectos que conviene explicar despacio

SameSite frente a same origin

Los puertos 8000 y 8001 son orígenes distintos. Son same-site en este montaje y comparten el ámbito de host de las cookies. La prueba de preferencia es un escenario entre orígenes del mismo site. No afirmes que demuestra que SameSite=Lax falla siempre frente a un dominio atacante externo.

La política de seguridad de navegador exige precisión en estas palabras. Un alumno que detecte esta limitación y la incluya en su informe ha entendido mejor el ejercicio que quien lo denomine simplemente ataque desde otro dominio.

Por qué no se leen cookies de sesión en la consola

HttpOnly no oculta el valor al propietario del navegador. Evita que las APIs JavaScript de la página lo lean directamente. El navegador sigue enviando la cookie cuando corresponda. El XSS del laboratorio modifica el DOM sin extraer cookies para que la prueba no mezcle dos capacidades distintas.

CORS y servidor

El contador es la evidencia de que el POST se procesó aunque el script no pudo leer la respuesta. No basta con mirar el error rojo. Para un POST JSON con cabecera personalizada, el navegador realiza primero OPTIONS; si se rechaza ese permiso, no envía el POST posterior. Son dos recorridos distintos y ambos deben quedar claros.

Estado que condiciona los resultados

El token CSRF se vincula a la sesión del servidor; reiniciar Python la invalida. Volver a iniciar sesión establece una nueva cookie. Local Storage permanece en el navegador y no cambia el rol real. Un override o una conexión en modo Offline pueden interferir en prácticas posteriores.

El programa utiliza estado compartido en memoria y es un servidor didáctico de un equipo, no una solución multiusuario de aula. No hay base de datos, pedidos persistidos, pasarela de pago ni cuentas administrativas reales. Todo el impacto debe describirse con ese alcance.

Respuestas de la prueba en papel

  1. Se ha modificado el DOM local. Aún no se puede afirmar que cambió el catálogo ni el importe aceptado por el servidor.
  2. Cookie envía cookies del cliente al servidor; Set-Cookie indica en una respuesta qué cookie debe establecer o modificar el navegador.
  3. HttpOnly excluye la cookie de document.cookie y otras APIs de página correspondientes. DevTools tiene acceso de inspección del propietario del navegador.
  4. Falta comprobar autorización sobre ese pedido concreto para la identidad de la sesión.
  5. Los valores de input.value son cadenas y el operador + las concatena. Una conversión numérica apropiada permite sumarlos.
  6. No. Una petición sin preflight puede llegar y producir efectos aunque el navegador no permita leer su respuesta. Si hay preflight denegado puede no enviarse la petición final.
  7. La CSP aplicada restringe operaciones; Report Only registra infracciones sin bloquear por esa política.
  8. El usuario puede revelar el botón o enviar directamente la petición. El servidor debe verificar permisos para la acción.
  9. Son distintos orígenes por el puerto. No son distintos sites en este laboratorio; el puerto no establece ese límite.
  10. Asignar el valor con textContent o crear un nodo de texto. No debe insertarse como HTML si el requisito es mostrar texto.

Criterios para aceptar el reto final

Un hallazgo se considera confirmado si incluye una precondición, una petición o entrada concreta, un resultado observable y una explicación que no excede la evidencia. Para cada corrección pide al menos un caso permitido y otro que antes provocaba el problema. Una corrección que rompe toda la funcionalidad no demuestra por sí sola una solución adecuada.

Ejemplos de controles que el alumno puede acreditar:

No aceptes como impacto demostrado “control total del servidor”, “robo de dinero” o “acceso al disco” a partir de estas pruebas. Tampoco aceptes “todo es seguro” por una CSP o una cookie HttpOnly.

Preparación de una edición abreviada

Si dispones de dos sesiones de tres horas, conserva HTTP, depuración, compra, cookies, IDOR y XSS. Muestra CSP como comparación final. Conviene separar CORS y CSRF para una ampliación con tiempo suficiente. Si solo tienes noventa minutos, utiliza la demostración inicial de precio, la petición en Network y la comparación del pedido seguro; no intentes cubrir todas las siglas.

Mejoras de desarrollo propuestas

Como actividad posterior puedes pedir manejo explícito de errores en api, indicador de carga, bloqueo temporal del botón de compra y una estrategia de reintento que no duplique operaciones. Para evolucionar el backend harían falta persistencia, gestión real de usuarios, sesiones con caducidad, despliegue HTTPS, límites de uso y otros requisitos propios del proyecto. Estos cambios quedan fuera de la simulación actual y deben diseñarse según el entorno de destino.