El navegador permite observar cómo una página construye su interfaz, ejecuta JavaScript y se comunica con un servidor. Sus herramientas de desarrollo también permiten descubrir fallos de seguridad: datos expuestos, validaciones que solo existen en el cliente, accesos a objetos ajenos y uso inseguro del DOM. En este taller trabajarás sobre Loot Lab, una tienda ficticia que incluye comportamientos vulnerables y versiones corregidas para poder comparar resultados.
Audio
La recomendación es utilizar Chrome como navegador principal y Firefox para una sesión de contraste. Se pueden realizar las prácticas esenciales con ambos. La elección organiza las explicaciones y las capturas; no implica que uno convierta una aplicación en más segura.
Al terminar deberás poder explicar un hallazgo con pruebas reproducibles, identificar dónde debe corregirse y verificar que la corrección funciona. Modificar un precio en pantalla será el inicio del análisis; comprobar cuánto acepta el servidor será la evidencia relevante.
1 Elección del navegador y equivalencias
Propuesta para este grupo
Utiliza una versión estable actual de Chrome para la demostración común. Instala también Firefox si el aula lo permite. Dedica aproximadamente el 80 % del trabajo a una interfaz común y el resto a repetir algunas operaciones en el segundo navegador. Anota la versión instalada al comenzar: las etiquetas y la posición de algunas opciones cambian entre versiones e idiomas.
Chrome agrupa en Application el almacenamiento y ofrece un flujo cómodo para depurar, analizar red y probar respuestas locales. Firefox resulta especialmente didáctico al editar y reenviar peticiones desde su monitor de red. Chrome también permite editar y reenviar peticiones como fetch; no es una capacidad exclusiva de Firefox. Firefox dispone asimismo de overrides de respuestas en versiones recientes. No conviene justificar la elección diciendo que solo Chrome puede hacerlo.
Estas observaciones se apoyan en las referencias oficiales R1–R5 y R9; la elección para el aula es una propuesta docente.
| Tarea | Chrome | Firefox |
|---|---|---|
| Ver y modificar DOM y CSS | Elements o Elementos | Inspector |
| Ejecutar JavaScript y leer errores | Console o Consola | Consola web |
| Detener y recorrer JavaScript | Sources o Fuentes | Debugger o Depurador |
| Analizar HTTP | Network o Red | Red o Monitor de red |
| Cookies y almacenamiento | Application o Aplicación | Almacenamiento |
| Editar y reenviar | Edit and resend as fetch o copiar como fetch | Edit and Resend o Editar y reenviar |
| Sustituir respuestas localmente | Local Overrides | Network Overrides |
| Medir ejecución | Performance o Rendimiento | Rendimiento o herramientas de perfilado |
| Inspeccionar accesibilidad | Panel de accesibilidad del elemento | Inspector de accesibilidad |
| Revisar HTTPS | Security o Privacy and security, según versión | Información de conexión y detalles de seguridad en red |
Si solo puedes instalar uno: Chrome simplifica seguir una única secuencia de instrucciones para el conjunto del taller. Si ya trabajáis con Firefox en Kali o Ubuntu: puedes impartir el núcleo completo con Firefox y utilizar la columna de equivalencias. No hace falta una edición Developer para los ejercicios.
2 Objetivos y organización del taller
Conocimientos previos
Se recomienda conocer etiquetas HTML, selectores CSS, variables y funciones de JavaScript, JSON, URL, métodos GET y POST y la diferencia básica entre cliente y servidor. No necesitas saber programar Python: el servidor se entrega terminado y se ejecuta con un comando.
Resultados observables
Al completar el trabajo podrás localizar el elemento que genera una parte de la interfaz; explicar una petición a partir de método, URL, cabeceras y cuerpo; seguir el origen de un dato durante una pausa; distinguir un fallo visual de un fallo de autorización; demostrar una inyección DOM sin extraer información; y verificar controles de servidor, cookies, CSP, CORS y CSRF sin confundir su función.
Normas de trabajo
Las modificaciones y peticiones de las prácticas se dirigen al laboratorio local. Utiliza datos ficticios y un perfil de navegador para clase. No pegues código en la consola de tu sesión personal sin comprenderlo. Si el navegador muestra protección frente a self-XSS, lee cada instrucción y aplica el flujo que muestre tu versión solo después de entender el código; no desactives protecciones del navegador para completar el taller.
DevTools no sustituye a un proxy de auditoría, a una revisión del servidor ni a una captura de red completa. Permite estudiar la actividad de la página y las respuestas que recibe; no permite leer el PHP, Python o Java que se ejecuta en el servidor ni observar tráfico de otros equipos.
3 Preparación y arranque del laboratorio
Contenido del paquete
La carpeta laboratorio contiene el servidor Python, una página HTML, JavaScript, CSS y una página de segundo origen. La carpeta documentacion incluye esta guía y el solucionario del profesor. El servidor no utiliza bibliotecas externas, no requiere cuentas y almacena las sesiones en memoria.
Arranque en Windows
- Comprueba Python con
py -3 --version. - Si no está instalado, prepara Python 3.10 o posterior antes del taller.
- Extrae el ZIP. No ejecutes el laboratorio dentro de la vista comprimida.
- Abre una terminal en la carpeta
laboratorio. - Ejecuta:
py -3 servidor.py
Arranque en macOS o Linux
Comprueba python3 --version, sitúate en la carpeta laboratorio y ejecuta:
python3 servidor.py
Abre http://localhost:8000. El servidor también utiliza http://localhost:8001. Mantén la terminal abierta; Ctrl+C detiene ambos servicios. Si localhost se resuelve de forma problemática en un equipo, revísalo antes de clase: la guía y los controles de origen utilizan literalmente localhost.
No uses file://, Live Server ni python3 -m http.server: solo servirían archivos y no implementarían las API del taller. No cambies un puerto aisladamente; existen referencias a ambos en el código y en las comprobaciones de seguridad.
Comprobación inicial
- Pulsa Cargar catálogo. Deben aparecer dos productos.
- Pulsa Iniciar sesión. La respuesta debe indicar estado 200.
- Pulsa Consultar sesión. Deben aparecer usuario alumno, rol alumno y un token CSRF.
- Abre la página de segundo origen desde el enlace de navegación.
- Regresa a la tienda y abre DevTools.
Credenciales ficticias: alumno / aula123. No existe cuenta administradora para el alumnado. Todas las compras son simuladas.
Reinicio entre ejercicios
Desactiva Offline, el bloqueo de peticiones y cualquier override. Limpia filtros de Network. Si has alterado archivos fuente, restaura la copia original del ZIP. Reinicia el servidor para borrar sesiones, contador y preferencias; las cookies y el almacenamiento del navegador no desaparecen por reiniciar Python. Puedes borrarlos desde DevTools y volver a iniciar sesión.
El programa escucha únicamente en la interfaz de bucle local. Cada estudiante ejecuta su propia copia. Esta configuración evita depender de una red común y mantiene las prácticas reproducibles; no es un despliegue para Internet.
4 Fundamentos de navegador y seguridad
Qué se recibe y qué se ejecuta
Al solicitar la página, el navegador recibe HTML. Después puede solicitar CSS, JavaScript y datos. El HTML se convierte en un árbol DOM; JavaScript puede modificarlo sin que cambie el archivo original del servidor. Por ello, Ver código fuente y Elements no tienen por qué mostrar lo mismo.
El servidor recibe peticiones y decide qué responder. Un botón oculto puede impedir que alguien encuentre una función por casualidad, pero no impide que envíe una petición. Un campo type="hidden" participa en los formularios; su valor sigue siendo controlable por el usuario.
Cuatro preguntas antes de declarar un fallo
- ¿Qué dato controlo: texto visible, atributo, parámetro, JSON, cookie o identificador?
- ¿Qué sistema consume ese dato: DOM, lógica JavaScript o servidor?
- ¿Qué efecto se ha confirmado: visualización, ejecución, lectura o modificación real?
- ¿Qué comprobación faltaba y en qué componente debe hacerse?
Cambiar tu propia pantalla no demuestra acceso a datos ajenos. Leer una respuesta con un pedido de otro usuario sí puede demostrarlo. La evidencia debe llegar hasta el efecto que afirmas.
Origen y site
Un origen combina esquema, host y puerto. http://localhost:8000 y http://localhost:8001 son distintos orígenes. Cambiar solo la ruta mantiene el origen. Cambiar entre localhost y 127.0.0.1 también cambia el host y puede separar cookies y almacenamiento.
El concepto de site utilizado por SameSite no equivale al origen: el puerto no lo separa. Los dos orígenes localhost del taller son same-site. Además, las cookies no se aíslan por puerto. El segundo servicio de este laboratorio es de confianza y comparte el entorno docente; no se simula aquí un dominio atacante totalmente independiente.
Controles y alcance
| Control | Qué aporta | Qué no permite concluir |
|---|---|---|
| Validación HTML | Mejor experiencia al introducir datos | Que el servidor validará la petición |
| Autenticación | Identificar una sesión | Que pueda leer cualquier objeto |
| Autorización | Decidir qué puede hacer esa identidad | Que cualquier dato del cliente sea fiable |
| HttpOnly | Impedir lectura de la cookie mediante document.cookie | Que no haya XSS o que la cookie no viaje por HTTP |
| Secure | Restringir el envío de la cookie a contextos HTTPS admitidos | Que la sesión esté autorizada para una acción |
| SameSite | Limitar envío de cookies según contexto de site | Que cubra todos los casos de CSRF |
| CORS | Controlar qué orígenes pueden leer ciertas respuestas con JavaScript | Que una API tenga autenticación o que no reciba peticiones |
| CSP | Restringir recursos y ciertas formas de ejecución | Que todo HTML insertado sea seguro |
| HTTPS | Proteger el transporte y verificar el servidor mediante certificados | Que la aplicación no tenga IDOR o XSS |
5 Práctica 1 Inspeccionar HTML y CSS
Objetivo: identificar qué cambios afectan al DOM y qué cambios llegan al servidor. Tiempo: 25 minutos.
- Abre la tienda y pulsa con el botón derecho sobre Figura astronauta. Elige Inspeccionar.
- Localiza
<p id="precio" data-precio="25">en Elements o Inspector. - Edita el texto y escribe Figura astronauta · 1 €.
- En Styles añade temporalmente
color: redy un tamaño de letra mayor. - Desactiva una regla CSS y observa qué propiedad pasa a aplicarse. Revisa Computed para conocer el valor final, no solo la regla escrita.
- Localiza el botón Administración con
id="admin"y eliminahiddendel DOM. Ahora se ve un control que antes no se mostraba. - Inicia sesión y pulsa el botón que acabas de revelar.
- Abre Network y revisa la respuesta de
/api/admin. - Recarga la página sin overrides activos.
Resultado esperado: el precio y el estilo vuelven a su estado inicial. El botón revelado no da acceso: el servidor devuelve 403 a la sesión alumno. Si no has iniciado sesión devuelve 401.
Interpretación: has modificado tu representación local. No has actualizado el catálogo del servidor ni conseguido autorización. El servidor del laboratorio rechaza el acceso administrativo aunque el control se vea.
Evidencia: captura del elemento modificado y respuesta de la API administrativa. Explica por qué una captura del precio alterado no demuestra una compra por ese importe.
Ampliación de desarrollo: inspecciona el modelo de caja de una sección. Identifica padding, borde y margen. Cambia el ancho de la ventana y explica qué regla @media entra en funcionamiento. Activa el estado :hover de un botón desde el inspector si tu versión lo ofrece.
6 Práctica 2 Usar la consola con criterio
Objetivo: consultar el DOM y trabajar con datos sin confundir la consola con el servidor. Tiempo: 25 minutos.
Abre Console y ejecuta una instrucción cada vez:
document.title
location.origin
document.querySelector('#precio').textContent
document.querySelector('#precio').dataset.precio
El último resultado es una cadena. Un atributo HTML no se convierte en número automáticamente.
const precioLeido = document.querySelector('#precio').dataset.precio;
console.log(typeof precioLeido);
console.log(Number(precioLeido) * 2);
console.table([{producto: 'Figura', precio: 25}, {producto: 'Robot', precio: 40}]);
Cambia el texto con una operación explícita:
document.querySelector('#precio').textContent = 'Texto de prueba';
Consulta datos de la API sin depender de funciones internas de la página:
fetch('/api/productos')
.then(respuesta => respuesta.json())
.then(datos => console.table(datos))
.catch(error => console.error(error));
Resultado esperado: se genera una petición que también aparece en Network. Console ejecuta JavaScript en el contexto seleccionado; no ejecuta Python en el servidor. Si hay iframes, comprueba el contexto antes de lanzar una prueba.
Errores didácticos: una promesa pendiente no significa que no haya respuesta; espera su resolución. Un HTTP 403 no provoca por sí mismo que fetch rechace su promesa: para tratarlo como error de aplicación comprueba respuesta.ok o respuesta.status. Un error de red o CORS sí puede hacer que fetch rechace.
fetch('/api/admin').then(async respuesta => {
const datos = await respuesta.json();
console.log('HTTP', respuesta.status, 'OK', respuesta.ok, datos);
});
Evidencia: anota el tipo de dato del precio y el estado HTTP de la petición administrativa. Recarga para limpiar los cambios del DOM. Si redeclaras una constante en una sesión de consola y aparece un error, cambia el nombre o recarga; el comportamiento de redeclaración puede variar entre herramientas.
7 Práctica 3 Seguir una petición HTTP
Objetivo: reconstruir una conversación entre navegador y API. Tiempo: 35 minutos.
Preparar la captura
Abre Network antes de pulsar botones. Limpia la lista, comprueba que la grabación está activa y selecciona Fetch/XHR. Activa Preserve log si vas a estudiar una navegación. Disable cache afecta a la caché del navegador mientras la herramienta aplica esa opción; no elimina por sí mismo cachés de servidor o de un service worker.
Pulsa Cargar catálogo y selecciona /api/productos. Examina URL, método GET, estado 200, cabeceras de petición, cabeceras de respuesta, contenido JSON e iniciador. La pestaña Initiator o la traza equivalente ayuda a encontrar la línea de JavaScript que originó la petición.
Comparar URL y cuerpo
Ejecuta esta petición desde la consola:
fetch('/api/eco?origen=consola', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({nombre: 'Ada', curso: 'ASIR'})
}).then(r => r.json()).then(console.log);
El parámetro origen=consola forma parte de la URL. nombre y curso están en el cuerpo JSON. En una petición real podrían registrarse por mecanismos distintos; no uses URLs para transportar contraseñas o tokens.
Leer cabeceras sin mezclarlas
Content-Type describe el formato del cuerpo correspondiente. Accept expresa formatos que admite el cliente. Cookie viaja en la petición cuando corresponde; Set-Cookie es una instrucción del servidor en la respuesta. El navegador controla cabeceras como Cookie y Origin en fetch; no puedes simular cualquier escenario cambiándolas como propiedades arbitrarias de JavaScript.
La API del laboratorio devuelve Cache-Control: no-store. Esto pide no almacenar las respuestas en caché; no sustituye al control de acceso ni impide capturas o copias del contenido ya recibido.
Analizar tiempos
Pulsa Cargar con retraso y examina Timing. El servidor espera aproximadamente 800 ms antes de responder. El tiempo total también incluye planificación, transporte y procesamiento. Un TTFB alto es una pista para investigar, no una prueba automática de que la base de datos sea lenta.
Exportar una evidencia
En el menú contextual busca Copy as fetch o la opción equivalente; conserva la petición de ejemplo sin cookies ni credenciales. Si exportas HAR, revisa su contenido antes de entregarlo. Aunque la herramienta ofrezca una exportación saneada, parámetros, cuerpos, respuestas y datos de negocio pueden contener información sensible. En este taller todos los datos son ficticios.
Entrega: una tabla con método, ruta, estado, datos enviados, datos recibidos e iniciador para catálogo, eco y petición administrativa. Adjunta un HAR mínimo opcional del laboratorio, revisado manualmente.
8 Práctica 4 Depurar JavaScript
Objetivo: localizar un fallo por tipos de datos siguiendo el flujo real. Tiempo: 35 minutos.
- Escribe 2 y 3 en los campos del cálculo y pulsa Sumar. Aparece 23.
- Abre Sources o Depurador y localiza
app.js. - Busca la función
sumary coloca un punto de ruptura enconst resultado = a + b. - Pulsa Sumar otra vez. La ejecución se detiene antes de ejecutar esa línea.
- Examina a y b en Scope. Añade
typeof a,typeof byNumber(a) + Number(b)a Watch o inspección equivalente. - Usa Step over para ejecutar una línea y comprueba el valor de resultado.
- Examina Call stack para identificar el manejador del clic.
- Reanuda la ejecución. Desactiva el punto de ruptura al terminar.
Resultado esperado: ambos valores son cadenas porque se leen de .value. El operador + las concatena. La conversión numérica explícita da 5.
Aplicar la corrección
Edita el archivo original laboratorio/app.js con un editor y sustituye las tres líneas del cálculo por:
const a = Number(document.querySelector('#a').value);
const b = Number(document.querySelector('#b').value);
const resultado = a + b;
Guarda, recarga y comprueba 2 + 3 y 10 + 5. No hace falta reiniciar el servidor para volver a servir un archivo estático. Considera además el caso de campos vacíos: Number('') vale 0; si eso no cumple el requisito, valida el texto original antes de convertirlo.
Puedes probar la corrección con un override en lugar de modificar el archivo. Anota qué método has usado: una edición temporal del depurador no equivale siempre a guardar el archivo del proyecto.
Otras pausas útiles
Activa una pausa XHR/fetch sobre una parte de la ruta /api/compra cuando tu navegador lo permita. También puedes usar una pausa de eventos de clic o de envío de formulario. Así encuentras el flujo sin conocer el nombre de la función. Una pausa DOM sobre cambios del subárbol de #salida ayuda a localizar quién actualiza la respuesta visible.
Evidencia: valores y tipos durante la pausa, línea causante y resultado después de corregir. Relaciona esta técnica con seguridad: seguir un dato de entrada hasta un uso sensible permite decidir si se valida o se interpreta de forma peligrosa.
9 Práctica 5 Saltar validaciones del cliente
Objetivo: comprobar por qué las restricciones HTML necesitan validación de servidor. Tiempo: 25 minutos.
Inicia sesión. En el campo Cantidad escribe 0 y pulsa Comprar. La validación del navegador impide el envío normal porque el mínimo es 1. Confirma en Network que no aparece una nueva compra.
Inspecciona el campo y elimina temporalmente min y max. Envía cantidad 0. Esta vez llega una petición, pero el servidor devuelve 400. Haz otra prueba con cantidad 11 usando la consola:
fetch('/api/compra?modo=seguro', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({producto: 1, cantidad: 11, precio: 25})
}).then(async r => console.log(r.status, await r.json()));
El servidor acepta únicamente enteros de 1 a 10. En el laboratorio este control existe incluso en la compra vulnerable; la vulnerabilidad de precio de la práctica siguiente es independiente.
Preguntas: ¿quién impidió el primer intento? ¿Quién rechazó el segundo? ¿Cómo distingues la ausencia de envío de una respuesta 400? ¿Cambiar input a text obliga al servidor a aceptar cualquier dato?
Resultado esperado: eliminar atributos cambia el comportamiento del formulario, pero no el contrato de la API. Las reglas del cliente ayudan a introducir datos; las del servidor establecen qué se acepta.
Entrega: compara tres intentos con cantidad 0, 1 y 11, indicando si hubo petición y cuál fue el estado. No des por segura toda la aplicación porque una regla se valide correctamente.
10 Práctica 6 Manipular una compra simulada
Objetivo: detectar confianza indebida en un importe proporcionado por el cliente. Tiempo: 35 minutos.
Establecer la referencia
Inicia sesión y compra una unidad sin modificar nada. En Network observa POST /api/compra, el JSON enviado y el total devuelto. El producto 1 cuesta 25 y la cantidad es 1.
Inspecciona #precioEnviado. Es un input oculto cuyo valor es 25. Cambia solo el texto visible del precio a 1 € y compra otra vez: el total sigue siendo 25 porque el campo enviado no ha cambiado. Ahora cambia el valor del input oculto a 1 y repite: la versión vulnerable devuelve total 1.
Repetir una petición controlada
En Firefox utiliza Editar y reenviar sobre la petición de compra. En Chrome utiliza Edit and resend as fetch o Copy as fetch y edita el cuerpo en la consola. Los nombres dependen de la versión; también puedes ejecutar directamente:
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 estado 200 y total 2. El laboratorio no crea pedidos persistentes ni pagos: el fallo confirmado es que el cálculo del servidor usa el precio enviado en esta simulación.
Verificar la corrección
Repite exactamente el cuerpo contra /api/compra?modo=seguro.
fetch('/api/compra?modo=seguro', {
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()));
Ahora el total debe ser 50: el servidor toma el precio del catálogo. Esta corrección no consiste en ocultar mejor el input ni en ofuscar JavaScript. En una aplicación real también comprobaría promociones, impuestos, stock y condiciones de la operación en el servidor.
Evidencia: cuerpo y respuesta de ambas variantes. En el informe escribe “el endpoint vulnerable calcula la simulación con un importe controlado por el cliente”. Evita afirmar que has modificado una base de datos o ejecutado un pago que este laboratorio no implementa.
11 Práctica 7 Examinar almacenamiento y cookies
Objetivo: distinguir persistencia, contexto de acceso y atributos de cookies. Tiempo: 35 minutos.
Local Storage y Session Storage
Abre Application > Local Storage en Chrome o Almacenamiento > Almacenamiento local en Firefox. Selecciona el origen de la tienda. Debe existir rol_demo=alumno. En Session Storage verás visita_demo=activa.
localStorage.getItem('rol_demo');
sessionStorage.getItem('visita_demo');
localStorage.setItem('rol_demo', 'admin');
Consulta ahora /api/me y /api/admin. El rol del servidor sigue siendo alumno y el acceso administrativo continúa denegado. La clave de almacenamiento es únicamente una demostración local y nunca participa en la autorización del servidor.
Local Storage persiste más allá de una recarga y normalmente de una sesión del navegador hasta que se elimina. Session Storage se asocia al origen y a la sesión de la pestaña; su comportamiento al duplicar o restaurar pestañas tiene matices. Comprueba recarga y cierre de una pestaña, sin asumir que toda apertura desde otra pestaña parte necesariamente de un almacén vacío. Ninguno de estos mecanismos es un almacén de secretos frente a JavaScript del mismo origen.
Analizar las cookies de aula
Inicia sesión y abre el apartado Cookies. Localiza sesion y preferencia. Revisa nombre, valor, dominio o host, ruta, HttpOnly, Secure y SameSite.
Ejecuta:
document.cookie
Debe aparecer preferencia, pero no sesion. La cookie sesion es HttpOnly y la API document.cookie no la expone. Eso no significa que el propietario del navegador no pueda verla en DevTools. Tampoco impide que el navegador la adjunte a peticiones donde corresponda.
Pulsa Consultar sesión y revisa la cabecera Cookie de la petición. En la respuesta de login observa los dos Set-Cookie. No confundas la visibilidad de la cookie en la interfaz con la posibilidad de leerla desde un script de la página.
Restricción del laboratorio HTTP
El taller utiliza HTTP local y no configura Secure en sus cookies. Esto facilita observarlas, pero no constituye una recomendación para una sesión en producción. En un servicio HTTPS se debe evaluar una configuración de sesión como:
Set-Cookie: sesion=VALOR_ALEATORIO; Path=/; HttpOnly; Secure; SameSite=Lax
La política SameSite debe elegirse según el flujo de la aplicación. No añadas SameSite=None como arreglo genérico: requiere Secure y tiene implicaciones de envío entre sites. No configures Domain si no necesitas compartir la cookie con subdominios. Los puertos no aíslan cookies.
Comprobar la sesión en el servidor
Cierra sesión mediante el botón y consulta /api/me: debe devolver 401. El servidor invalida el identificador además de pedir que se elimine la cookie. Borrar solo el almacenamiento local o cambiar una etiqueta de la interfaz no equivale a revocar una sesión del servidor.
Entrega: tabla comparativa entre Local Storage, Session Storage y cookies, con persistencia, acceso JavaScript y envío automático al servidor. No incluyas valores reales de cookies en informes fuera del laboratorio.
12 Práctica 8 Comprobar autorización e IDOR
Objetivo: reconocer acceso indebido a objetos aunque exista autenticación. Tiempo: 30 minutos.
IDOR describe referencias directas a objetos cuyo acceso no se comprueba correctamente. En API también se habla de fallos de autorización a nivel de objeto. Un identificador predecible ayuda a localizar un objeto, pero el fallo central es la ausencia de control de permisos sobre él.
Inicia sesión y consulta el pedido propio:
fetch('/api/pedido?id=1001').then(r => r.json()).then(console.log);
La respuesta identifica como propietario a alumno. Cambia el identificador al segundo pedido ficticio:
fetch('/api/pedido?id=1002').then(async r => console.log(r.status, await r.json()));
La variante vulnerable devuelve estado 200 y propietario otra_persona. El servidor ha comprobado que hay una sesión, pero no que le corresponda ese pedido.
Repite ambas consultas con modo seguro:
fetch('/api/pedido?id=1001&modo=seguro')
.then(async r => console.log(r.status, await r.json()));
fetch('/api/pedido?id=1002&modo=seguro')
.then(async r => console.log(r.status, await r.json()));
Se esperan 200 para el propio y 403 para el ajeno. En una aplicación real podría elegirse 404 para ocultar la existencia de un recurso; la respuesta concreta depende del diseño. El requisito es impedir el acceso.
Corrección razonada: el servidor obtiene la identidad de la sesión y compara propiedad o permisos del objeto. No debe confiar en un usuario enviado por JSON, un rol en Local Storage o un campo oculto. Sustituir números por UUID puede dificultar adivinar referencias, pero no sustituye la autorización.
Evidencia: cuatro respuestas y una frase que separe autenticación de autorización. No necesitas recorrer rangos de identificadores: los dos casos conocidos bastan para demostrar el comportamiento docente.
13 Práctica 9 Encontrar y corregir un DOM XSS
Objetivo: seguir una entrada controlada hasta un punto que interpreta HTML. Tiempo: 40 minutos.
Empezar por la interpretación de HTML
En Saludo introduce Ada. Después prueba <strong>Ada</strong>. En la versión vulnerable se mostrará en negrita. Esto demuestra que el contenido se interpreta como HTML; aún no demuestra ejecución de JavaScript.
En Sources localiza la lectura del parámetro q y la asignación a #saludo. El flujo es:
- Fuente de entrada: parámetro q de la URL.
- Lectura: URLSearchParams.
- Destino: propiedad innerHTML del elemento.
- Consecuencia: el navegador interpreta el texto como marcado.
El servidor entrega el HTML estático. La inyección se produce al renderizar en el navegador, por eso aquí se trata de DOM XSS.
Demostración inocua de ejecución
En el formulario de la página principal pega este contenido y pulsa Mostrar:
<img src="/imagen-inexistente" onerror="document.getElementById('marca').textContent='XSS demostrado en el laboratorio'">
La imagen inexistente provoca un error y el manejador modifica una marca visible. No extrae cookies, no envía datos fuera del equipo y no solicita recursos externos. En Network verás el 404 de la imagen; en Elements podrás encontrar el atributo onerror.
Resultado esperado: el texto Marca XSS sin activar cambia a XSS demostrado en el laboratorio. Has probado ejecución en el contexto de la página. No has demostrado acceso a archivos del sistema ni ejecución en el servidor.
Un <script> insertado mediante innerHTML no se ejecuta de la misma forma que durante el parseado inicial del documento. Si esa prueba no produce nada, no concluyas que innerHTML es seguro: existen otros vectores, como el manejador del ejemplo.
Comprobar el uso de texto
Prueba de nuevo conservando la entrada y añadiendo modo=seguro a la URL. Para evitar errores al codificarla puedes preparar la navegación desde Console:
const pruebaDOM = new URL(location.href);
pruebaDOM.searchParams.set('modo', 'seguro');
location.href = pruebaDOM.href;
Ahora el contenido debe verse literalmente y la marca no cambia. La variante segura utiliza textContent. Si pulsas Mostrar en el formulario se generará una URL nueva que solo contiene q: comprueba de nuevo el parámetro modo o usa el botón Mostrar entrada como texto para comparar la representación actual. Después de una ejecución ya producida, cambiar a texto no deshace los efectos anteriores: recarga en modo seguro para verificar desde cero.
Qué corregir en una aplicación real
Cuando el requisito sea mostrar texto, utiliza textContent o APIs de creación de nodos. Si necesitas permitir HTML, define una política y sanitiza con una solución mantenida adecuada al contexto. No construyas un filtro artesanal basado en borrar la palabra script. Un escape válido para texto HTML no sirve automáticamente para una URL, un atributo o código JavaScript.
CSP añade restricciones útiles, pero corregir el punto de inserción sigue siendo necesario. HttpOnly reduce la exposición de ciertas cookies; un XSS todavía podría ejecutar acciones con la sesión o leer datos a los que acceda la página.
Entrega: identifica fuente y destino, captura la marca antes y después, y demuestra la misma entrada como texto en la variante segura.
14 Práctica 10 Evaluar CSP y protección frente a marcos
Objetivo: interpretar una política entregada por el servidor y verificar su alcance. Tiempo: 30 minutos.
Comparación con y sin política
Repite la entrada XSS anterior en /csp.html. Esta página utiliza el mismo JavaScript vulnerable, pero la respuesta del documento incluye Content-Security-Policy. La política permite scripts del propio origen y no permite manejadores inline como onerror.
En Network selecciona el documento csp.html, abre Response Headers y lee Content-Security-Policy. Después revisa Console. Debe aparecer una infracción de política y la marca XSS no debe cambiar. El elemento img sí puede haberse insertado: impedir ese manejador no equivale a sanitizar el HTML.
La política del laboratorio es:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self'; connect-src 'self' http://localhost:8001; object-src 'none'; base-uri 'none'; frame-ancestors 'none'
script-src 'self' permite cargar app.js del mismo origen. connect-src controla conexiones como fetch. object-src 'none' impide objetos embebidos. base-uri 'none' limita el uso de base. frame-ancestors 'none' impide que otros documentos enmarquen la página. Esta última directiva no hereda default-src y debe enviarse como cabecera; una etiqueta meta no la sustituye.
Esta política es didáctica, no una receta universal. Un despliegue real debe inventariar recursos, decidir nonces o hashes según su arquitectura y probar las rutas necesarias. No añadas unsafe-inline solo para silenciar el mensaje: estarías relajando precisamente el control que se está demostrando.
Report Only
Repite la misma entrada en /reporte.html. La cabecera es Content-Security-Policy-Report-Only. El navegador informa de la infracción, pero la política no bloquea la ejecución. La marca debe cambiar. El laboratorio no configura un colector de reportes; la observación se realiza en la consola.
Conclusión: la presencia del nombre de una cabecera no demuestra que esté aplicando bloqueo. Hay que comprobar el tipo de política y el efecto observado.
Marcos y clickjacking
Abre http://localhost:8001/marco.html. Encontrarás dos marcos: uno intenta cargar una página sin protección y otro una con CSP frame-ancestors none y X-Frame-Options DENY. El primero debe cargar; el segundo debe bloquearse y generar un mensaje.
La demostración prueba protección frente al enmarcado de estos documentos. No implementa una interfaz engañosa de clickjacking. En una aplicación real valorarías qué páginas necesitan ser embebidas legítimamente antes de elegir el control.
Entrega: tabla con página, cabecera, resultado del XSS y resultado del marco cuando corresponda. Desactiva overrides antes de medir la política que entrega realmente el servidor.
15 Práctica 11 Entender CORS y el preflight
Objetivo: separar envío, lectura y autorización entre orígenes. Tiempo: 40 minutos.
Realiza los comandos desde la consola de http://localhost:8000. No desde una pestaña vacía ni desde el segundo origen. Trabaja en la página principal, que no impone CSP.
Leer sin autorización CORS
fetch('http://localhost:8001/api/publico')
.then(r => r.json()).then(console.log).catch(console.error);
El segundo servidor recibe la petición y responde, pero no autoriza que este origen lea la respuesta mediante JavaScript. Console muestra un error CORS. Según el navegador, Network puede mostrar estado 200, estado bloqueado o una combinación de datos y advertencias. El registro del servidor permite confirmar que llegó el GET.
Abre la misma URL directamente en una pestaña: puedes ver el JSON. Una navegación y una lectura con fetch desde otro origen son operaciones distintas.
Autorizar una lectura pública concreta
fetch('http://localhost:8001/api/publico?cors=si')
.then(r => r.json()).then(console.log).catch(console.error);
Ahora la respuesta incluye Access-Control-Allow-Origin: http://localhost:8000 y JavaScript puede leerla. El endpoint contiene solo un mensaje público, sin credenciales ni datos privados. No confundas este permiso de lectura con un control de acceso al servidor.
Observar una petición previa OPTIONS
fetch('http://localhost:8001/api/publico?cors=si', {
method: 'POST',
headers: {'Content-Type': 'application/json', 'X-Taller': 'devtools'},
body: JSON.stringify({prueba: true})
}).then(r => r.json()).then(console.log).catch(console.error);
El tipo JSON y la cabecera personalizada requieren preflight. En Network observa OPTIONS y después POST. La respuesta al OPTIONS permite el origen, el método y las cabeceras utilizadas. Quita cors=si y repite: el servidor rechaza el preflight y el POST no debería enviarse. Una caché de preflight puede reducir OPTIONS en pruebas repetidas; borra la sesión de pruebas o cambia el escenario de forma controlada antes de interpretar su ausencia.
Demostrar una escritura aunque no se pueda leer
Antes, abre http://localhost:8001/api/contador en otra pestaña y anota el valor. Desde la consola de la tienda ejecuta:
fetch('http://localhost:8001/api/registro', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'evento=prueba_cors'
}).then(r => r.json()).then(console.log).catch(console.error);
El POST utiliza método, cabeceras y tipo de contenido que no requieren preflight en este caso. La respuesta no autoriza su lectura mediante CORS y fetch falla al exponerla. Sin embargo, al recargar la pestaña del contador verás que ha aumentado. El servidor sí procesó el envío.
No utilices mode: 'no-cors' como arreglo general. La respuesta será opaca y no podrás leer su cuerpo desde JavaScript. Ese modo no proporciona autenticación ni hace desaparecer el modelo de seguridad.
En escenarios con cookies entre orígenes hay requisitos adicionales: credentials, autorización explícita del origen, Access-Control-Allow-Credentials y las reglas de cookies y SameSite que correspondan. El comodín * no autoriza una lectura con credenciales. Esta práctica pública evita esas variables para centrarse en envío y lectura.
Entrega: compara cuatro casos: GET sin permiso, GET permitido, POST JSON con OPTIONS y POST de formulario cuyo efecto ocurre aunque la lectura se bloquee.
16 Práctica 12 Reproducir un CSRF acotado
Objetivo: observar una acción iniciada desde otro origen con una sesión existente. Tiempo: 40 minutos.
CSRF consiste en inducir acciones que usan credenciales del usuario sin que la aplicación haya comprobado adecuadamente la intención de esa petición. El taller modifica solo una preferencia visual ficticia.
Establecer la sesión
- Vuelve a
http://localhost:8000e inicia sesión. - Consulta
/api/mecon el botón. Anota el tema, inicialmente claro. - Abre
http://localhost:8001/csrf.htmlen otra pestaña. - Abre también DevTools en esta segunda pestaña y activa Preserve log en Network.
- Pulsa Enviar cambio desde otro origen.
- Observa el POST a
http://localhost:8000/api/preferenciay la respuesta en el marco. - Regresa a la tienda y pulsa Consultar sesión.
Resultado esperado: la preferencia pasa a oscuro. El formulario ha enviado una acción con la cookie de sesión disponible. No ha necesitado leer el token CSRF ni la respuesta con fetch.
Qué demuestra este montaje
Los puertos distintos crean orígenes distintos, pero siguen siendo same-site y las cookies no se aíslan por puerto. La cookie SameSite=Lax no impide este caso same-site. El segundo servidor es parte del laboratorio; esta simulación no reproduce todas las condiciones de un dominio atacante independiente. Sirve para estudiar controles de origen y de intención sin depender de DNS, HTTPS o excepciones locales.
Comprobar la variante protegida
En la página de segundo origen pulsa Repetir sin token contra versión segura. El endpoint /api/preferencia?modo=seguro exige Origin igual al origen de la tienda y un token vinculado a la sesión. La respuesta debe ser 403 y el tema no debe cambiar.
Desde la consola de la tienda realiza una acción legítima:
fetch('/api/me').then(r => r.json()).then(sesion => {
return fetch('/api/preferencia?modo=seguro', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': sesion.csrf
},
body: JSON.stringify({tema: 'claro'})
});
}).then(async r => console.log(r.status, await r.json()));
Debe devolver 200 y guardar claro. El navegador añade Origin al POST; no lo establezcas manualmente desde fetch.
Interpretación y corrección
Para corregir una aplicación real evalúa tokens CSRF de sesión, validación de Origin y políticas SameSite adecuadas; evita efectos de estado con GET. Los controles deben encajar con los clientes y flujos legítimos. La variante docente exige ambos controles para poder verificarlos de forma visible.
CORS no sustituye a estos controles. Un formulario puede enviar determinadas acciones sin que su origen pueda leer la respuesta mediante JavaScript. Un XSS en el origen autorizado podría obtener el token de esta demostración y actuar como la página: los controles se complementan.
Evidencia: tema antes y después, Origin de la petición, 403 del formulario protegido y 200 de la acción legítima. No incluyas tokens reales en el informe.
17 Práctica 13 Analizar archivos y datos expuestos
Objetivo: distinguir configuración pública de información que debería permanecer en el servidor. Tiempo: 20 minutos.
- Abre Sources y busca
clave_demoen los recursos cargados. En Chrome suele estar disponible la búsqueda global con Ctrl+Shift+F o Cmd+Option+F; en Firefox busca la opción de búsqueda en todos los archivos del depurador. Si el atajo cambia, usa el menú. - Localiza
configuracionPublicaen app.js. - Consulta
/api/configy revisa su JSON. - Busca usuario, rol, rutas de API y posibles comentarios de depuración.
El valor PUBLICA_NO_ES_UN_SECRETO es una marca ficticia. El ejercicio muestra que un valor enviado al navegador deja de ser secreto frente a ese usuario. No demuestra que esta marca conceda acceso a un servicio real.
En una aplicación real clasifica el valor: una URL pública, un identificador público o una clave diseñada para uso público no equivalen a una contraseña privada. Determina qué permisos concede, dónde puede utilizarse y qué restricciones existen antes de valorar el impacto. Una clave con permisos de servidor nunca debe confiar su confidencialidad a un bundle JavaScript.
Source maps y código minimizado
El formateo Pretty print hace más legible un archivo minimizado; no recupera por sí solo el código fuente original. Un source map puede asociar código generado con fuentes originales si estas están disponibles o embebidas. El laboratorio entrega app.js legible y no incluye un mapa: no verás un archivo .map de ejemplo salvo que añadas tu propio proyecto compilado.
Puedes ampliar la actividad con una aplicación propia Angular o similar. Revisa si sus mapas contienen código o comentarios que no querías publicar. Publicar mapas no es automáticamente una vulnerabilidad; el problema es qué información exponen y qué permisos o capacidad permiten obtener. No introduzcas secretos en el frontend aunque se desactiven los mapas.
Datos de más en una respuesta
En una API propia, compara el JSON recibido con los campos que realmente usa la pantalla. Ocultar una propiedad con CSS o no pintarla no evita que el usuario lea la respuesta. El servidor debe seleccionar los campos que puede recibir esa identidad.
Entrega: una tabla con dato encontrado, ubicación, si es público por diseño, permiso que concede y corrección si corresponde. No declares una fuga de credenciales por la mera presencia de la palabra key.
18 Práctica 14 Simular fallos con bloqueos y overrides
Objetivo: probar la interfaz cuando faltan recursos y entender la sustitución local. Tiempo: 25 minutos.
Bloquear una petición
En Network selecciona /api/productos. Busca la opción para bloquear esa URL o patrón de petición. En algunas versiones de Chrome estas funciones aparecen en Request blocking o Request conditions; usa la opción equivalente disponible. Firefox también permite bloquear solicitudes desde el monitor de red.
Pulsa Cargar catálogo y revisa Console y Network. La página puede mostrar un error poco útil o mantener datos antiguos: eso es un problema de experiencia y gestión de errores que debes describir. Distingue un bloqueo impuesto por tu herramienta de una respuesta 500 enviada por el servidor.
Desactiva el bloqueo y vuelve a comprobar que carga. Una configuración olvidada puede hacer que el resto del taller parezca roto.
Sustituir una respuesta
En Chrome usa Override content o Local Overrides sobre el GET de catálogo. Autoriza una carpeta vacía de pruebas y guarda una respuesta JSON como esta:
[{"id": 1, "nombre": "Figura de prueba local", "precio": 999}]
Repite la carga y busca el indicador de respuesta sustituida. En Firefox utiliza Override response o la función equivalente y selecciona un archivo JSON local; consulta R5 para el flujo de tu versión. Si el aula utiliza una versión antigua sin la función, realiza este apartado con Chrome.
El catálogo mostrado cambia en ese navegador, pero el catálogo del servidor no cambia. En una compra segura el servidor sigue tomando el precio 25 del producto 1. Para una comprobación limpia desactiva primero el override y vuelve a pedir catálogo, o utiliza otro navegador sin esa configuración.
Los overrides ayudan a probar errores, datos límite y estados de interfaz antes de que exista un backend completo. No deben presentarse como prueba de que se ha alterado una respuesta del servidor para el resto de usuarios. Algunas cabeceras o respuestas tienen restricciones de sustitución; comprueba el indicador y el comportamiento en tu versión.
Entrega: respuesta original, respuesta local, configuración usada y comprobación de restauración. Elimina los overrides al terminar.
19 Práctica 15 Rendimiento accesibilidad y adaptación
Objetivo: relacionar la calidad de desarrollo con evidencias observables. Tiempo: 25 minutos.
Red lenta y desconexión
Selecciona una simulación de red lenta en Network. Carga catálogo y observa cuánto tarda y si hay indicación de espera. Activa Offline y repite. Un sitio bien diseñado debe comunicar el problema, permitir reintentar y evitar operaciones duplicadas. El laboratorio permite observar un manejo básico que convendría mejorar.
Restaura No throttling o Sin limitación y conexión normal. Si la velocidad no cambia en una petición local, comprueba el preset y la caché; la simulación no reproduce de forma perfecta todas las propiedades de una red real.
Medir ejecución en el hilo principal
Abre Performance o la herramienta de perfilado equivalente. Inicia una grabación breve, pulsa Bloquear el hilo durante 250 ms y detén la grabación. Busca la tarea asociada al manejador del clic. El bucle consume aproximadamente ese tiempo y puede retrasar respuesta visual y entrada del usuario.
La duración está acotada para la demostración. No hace falta usar bucles ilimitados ni saturar CPU. En una aplicación real podrías dividir trabajo, reducir su coste o mover tareas apropiadas a un worker. La medición debe compararse bajo condiciones similares, sin generalizar una única traza a todos los dispositivos.
Accesibilidad y adaptación
Activa la vista de dispositivo o diseño adaptable. Prueba aproximadamente 390 px de ancho y después un tamaño de escritorio. Comprueba desbordamientos, legibilidad de campos y controles. Esta emulación no reproduce todo un teléfono real, su teclado, sus sensores o su rendimiento.
Selecciona un input y revisa el nombre accesible: debe estar relacionado con una etiqueta. Navega usando Tab y activa un botón con teclado. El atributo aria-live de la salida intenta anunciar actualizaciones, pero un resultado concreto depende de tecnología asistiva y contexto. La inspección ayuda a detectar problemas; no equivale a una auditoría completa ni sustituye probar con usuarios y lectores de pantalla.
Entrega: una mejora de manejo de error, una observación de rendimiento con traza y una revisión de navegación por teclado. Si propones Lighthouse como ampliación, ejecútalo en un contexto de tu proyecto que lo admita; su puntuación no certifica la seguridad ni detecta por sí sola un IDOR.
20 Ampliación HTTPS y controles de despliegue
Este bloque es observacional. El laboratorio local utiliza HTTP, no entrega certificados TLS y no demuestra contenido mixto real. Para estudiar transporte utiliza una aplicación HTTPS propia del profesor con autorización y datos ficticios, o limita la actividad a observar la información pública de conexión de una página sin enviar pruebas activas.
Guion de observación
- Abre el panel Security o Privacy and security de Chrome, según la versión. Si no aparece, usa Más herramientas o el menú de comandos.
- En Firefox examina la información de conexión y la sección de seguridad de una petición HTTPS cuando esté disponible.
- Identifica protocolo de la URL, host, emisor del certificado, periodo de validez y nombres cubiertos.
- Revisa si existen recursos solicitados por HTTP desde el documento HTTPS.
- Comprueba las advertencias del navegador y la URL realmente solicitada: puede haber bloqueo o actualización automática de recursos, no una carga insegura efectiva.
HTTPS protege el transporte; Network puede mostrar los datos descifrados porque el navegador es uno de los extremos. Que DevTools permita leer la respuesta no demuestra que cualquiera en la red pueda hacerlo.
Revisión razonada de cabeceras
| Cabecera | Pregunta de revisión |
|---|---|
| Content-Security-Policy | ¿Restringe los recursos necesarios y bloquea lo que pretende bloquear? |
| Content-Security-Policy-Report-Only | ¿Solo informa o se esperaba que aplicase bloqueo? |
| Strict-Transport-Security | ¿Existe una política HTTPS adecuada al dominio y a sus subdominios? |
| X-Content-Type-Options | ¿Se evita interpretar contenido con un tipo distinto del declarado? |
| Referrer-Policy | ¿Se limita la información de referencia de acuerdo con los flujos del sitio? |
| Permissions-Policy | ¿Se limitan funciones del navegador que la aplicación no necesita? |
| Cache-Control | ¿Se evita almacenar respuestas sensibles donde no deben quedar? |
| CSP frame-ancestors o X-Frame-Options | ¿El documento puede ser enmarcado cuando no debería? |
HSTS se aprende por conexiones HTTPS válidas, con los matices de preload; añadirlo a la respuesta HTTP del taller no demuestra su protección. No utilices includeSubDomains o preload sin comprobar el impacto en todos los servicios implicados. La ausencia de una cabecera concreta es una observación para analizar en contexto; no basta para declarar una vulnerabilidad crítica.
21 Reto final y evidencias
Situación
El equipo de Loot Lab quiere publicar una tienda y te pide una revisión previa. Dispones del laboratorio y de 55 minutos. Debes documentar tres hallazgos confirmados y dos controles que funcionan. Puedes usar esta guía y fuentes oficiales.
Selecciona hallazgos diferentes: precio confiado al cliente, acceso a un pedido ajeno, DOM XSS o cambio de preferencia sin control de intención. Como controles válidos puedes verificar la compra segura, la autorización del pedido seguro, el rechazo administrativo, la variante CSRF protegida o CSP aplicada. No contabilices dos veces la misma causa porque cambies de navegador.
Plantilla para cada hallazgo
Identificador y título:
URL y variante examinada:
Precondiciones y sesión utilizada:
Pasos mínimos de reproducción:
Datos enviados:
Respuesta o cambio observado:
Impacto demostrado en el laboratorio:
Límite de la evidencia:
Causa en cliente o servidor:
Corrección propuesta:
Prueba de la variante corregida:
Una evidencia útil permite a otra persona repetir el caso. Incluye la petición y la respuesta relevantes, no veinte capturas sin contexto. Oculta cookies y tokens si has reutilizado la plantilla en otro entorno. Separa observación de interpretación.
Ejemplo de redacción
“El endpoint de compra vulnerable devuelve total 2 cuando una sesión alumno envía producto 1, cantidad 2 y precio 1. El servidor usa el importe proporcionado por el cliente en esta simulación. La variante segura devuelve total 50 para el mismo cuerpo al obtener el precio del catálogo. El taller no persiste pedidos ni procesa pagos.”
Distribución por parejas
Una persona reproduce y la otra toma notas y verifica las respuestas. Intercambiad funciones después del primer hallazgo. Al terminar, una tercera pareja debe poder repetir al menos un caso siguiendo únicamente el informe.
22 Prueba competencial y rúbrica
Parte en papel sin recursos
Tiempo recomendado de 15 minutos. La finalidad es comprobar comprensión de la base.
- Cambias un precio con Elements. ¿Qué has modificado y qué no puedes afirmar todavía?
- Explica la diferencia entre Cookie y Set-Cookie.
- ¿Por qué una cookie HttpOnly puede verse en DevTools y no en document.cookie?
- Un alumno autenticado obtiene un pedido ajeno cambiando su id. ¿Qué comprobación falta?
- ¿Por qué dos inputs con 2 y 3 pueden producir 23 en JavaScript?
- Un fetch falla por CORS. ¿Permite afirmar que el servidor no recibió nada? Justifica.
- Explica una diferencia entre CSP aplicada y Report Only.
- ¿Por qué ocultar un botón de administración no constituye autorización?
- En el taller, ¿8000 y 8001 son el mismo origen? ¿Son necesariamente distintos sites?
- Propón una forma de mostrar una entrada de usuario como texto sin interpretarla como HTML.
Parte práctica con recursos
Asigna una de estas variantes por alumno o pareja: compra manipulada, pedido ajeno, XSS, comparación de CSP, CORS con contador o preferencia CSRF. Debe reproducir el caso, explicar su causa y comprobar una corrección o un control relacionado. No basta con copiar un comando si no puede explicar qué demuestra.
Rúbrica de observación
| Criterio | Básico | Adecuado | Consolidado |
|---|---|---|---|
| Manejo de herramientas | Necesita guía continua | Selecciona paneles y filtros apropiados | Combina red y depuración para seguir el flujo |
| Evidencia | Solo muestra una pantalla | Incluye petición o cambio y resultado | Permite reproducción independiente y controla el estado |
| Comprensión de seguridad | Confunde interfaz y servidor | Distingue el control ausente | Explica alcance y límites sin exagerar |
| Corrección | Propone ocultar o bloquear en el cliente | Sitúa el control en el componente correcto | Verifica caso legítimo y caso rechazado |
| Comunicación | Describe acciones sueltas | Relaciona causa con efecto | Redacta un hallazgo concreto con verificación |
Puedes utilizarla como lista de seguimiento sin calificación numérica. Si necesitas nota, asigna 0, 1 o 2 por criterio y explica de antemano cómo se interpreta cada nivel.
23 Problemas frecuentes y glosario
Resolución de incidencias
| Síntoma | Comprobación y solución |
|---|---|
| Aparece HTML pero no funcionan las API | Comprueba que arrancaste servidor.py y abriste localhost:8000, no file o Live Server |
| No se puede abrir el puerto | Cierra la aplicación que ocupa 8000 o 8001 y vuelve a arrancar |
| API devuelve 401 | Inicia sesión de nuevo; reiniciar Python invalida las sesiones previas |
| El botón administrativo devuelve 403 | Es el resultado esperado para alumno |
| El catálogo no carga | Desactiva Offline, bloqueos y overrides, limpia filtros y mira Console |
| No veo la petición | Abre Network antes de pulsar, activa grabación y elimina filtros |
| La prueba XSS no se ejecuta | Comprueba q, la variante de la página y Console; csp.html la bloquea por diseño |
| La marca sigue activada al pulsar texto seguro | Esa acción no deshace una ejecución anterior; recarga con modo=seguro |
| La consola muestra una promesa | Espera o usa then; la petición es asíncrona |
| No puedo definir Origin o Cookie en fetch | Son cabeceras controladas por el navegador; analiza las que envía realmente |
| El CSRF devuelve 401 | Inicia sesión en localhost, sin mezclarlo con 127.0.0.1 |
| Dos puertos comparten cookies | Es esperable: las cookies no se aíslan por puerto |
| Un POST CORS parece fallar pero el contador sube | El envío se procesó y la lectura de respuesta se bloqueó |
| Mis cambios persisten tras recargar | Revisa si hay un override o si editaste el archivo original |
| Firefox no muestra una función del ejemplo | Revisa la versión, el menú equivalente o utiliza Chrome para ese apartado |
Glosario breve
DOM: representación del documento que puede modificar JavaScript. Breakpoint: punto en el que se detiene la ejecución para inspeccionarla. Payload: datos de una petición; su significado depende del contexto. Endpoint: ruta que ofrece una operación o recurso. Fetch: API del navegador para realizar solicitudes. HAR: formato de registro de actividad HTTP. Override: sustitución local de una respuesta o recurso para pruebas. XSS: ejecución de contenido de script no deseado en el contexto de una aplicación. IDOR: acceso a objetos por referencias sin autorización adecuada. CSRF: acción inducida que aprovecha credenciales disponibles sin una comprobación adecuada de intención. CORS: mecanismo de autorización de lectura entre orígenes para determinadas operaciones del navegador. CSP: política de restricciones de carga y ejecución entregada al navegador. Source map: correspondencia entre código generado y fuentes originales. Self-XSS: engaño que induce a una persona a ejecutar código contra su propia sesión, a menudo pegándolo en la consola.
24 Referencias oficiales
Las prácticas, el código y las situaciones de Loot Lab son material docente original. Estas referencias fundamentan el comportamiento de las herramientas y los controles. Consulta la documentación de tu versión si cambia un menú. Fecha de consulta: 20 de octubre de 2026.
- R1 Chrome DevTools Overview. Paneles, DOM, consola y depuración. https://developer.chrome.com/docs/devtools/overview
- R2 Chrome Network reference. Captura, filtros, reenvío, exportación y condiciones de red. https://developer.chrome.com/docs/devtools/network/reference
- R3 Chrome Breakpoints. Tipos de pausas y depuración. https://developer.chrome.com/docs/devtools/javascript/breakpoints
- R4 Firefox Network request details. Cabeceras, cuerpos y edición de peticiones. https://firefox-source-docs.mozilla.org/devtools-user/network_monitor/request_details/
- R5 Firefox Network Overrides. Sustitución local de respuestas; disponible desde Firefox 137. https://firefox-source-docs.mozilla.org/devtools-user/network_monitor/network_overrides/index.html
- R6 MDN Same origin policy. Límites entre orígenes. https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Same-origin_policy
- R7 MDN CORS. Autorización de lectura, preflight y credenciales. https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS
- R8 MDN Set Cookie. Atributos y reglas de cookies. https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie
- R9 Chrome Local Overrides. Sustitución local de contenido y cabeceras. https://developer.chrome.com/docs/devtools/overrides
- R10 MDN Cross site scripting. Contextos de inyección y defensas. https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/XSS
- R11 MDN Content Security Policy. Política de carga y ejecución. https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP
- R12 MDN Content Security Policy header. Directivas y alcance. https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy
- R13 MDN Cross site request forgery. Tokens, origen y SameSite. https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/CSRF
- R14 Chrome Privacy and security. Inspección HTTPS y cookies de terceros. https://developer.chrome.com/docs/devtools/security
- R15 MDN Secure cookie configuration. Configuración de cookies de sesión. https://developer.mozilla.org/en-US/docs/Web/Security/Practical_implementation_guides/Cookies

