Introducción al DOM

El DOM (Document Object Model) es una interfaz de programación para documentos HTML y XML. Representa la estructura del documento como un árbol de objetos, permitiendo a los programas y scripts acceder y modificar dinámicamente el contenido, estructura y estilo del documento.

En este laboratorio, exploraremos diferentes tipos de vulnerabilidades que pueden surgir cuando se manipula el DOM de manera insegura.

// Ejemplo básico de manipulación del DOM
const element = document.getElementById('miElemento');
element.innerHTML = 'Nuevo contenido'; // Potencialmente peligroso

Sinks de Vulnerabilidad

Los "sinks" son puntos en el código donde los datos controlados por el usuario pueden ejecutarse como código. Algunos sinks comunes incluyen:

  • innerHTML / outerHTML
  • document.write()
  • eval()
  • setTimeout() / setInterval() con cadenas
  • location propiedades (href, assign, replace)

Intenta explotar el siguiente sink para ejecutar código JavaScript.

function updateContent() {
  const userInput = document.getElementById('sinkInput').value;
  document.getElementById('sinkResult').innerHTML = userInput;
}

Prueba con: <img src=x onerror=alert('XSS')>

Bypass de Validación

Muchas aplicaciones implementan validaciones de entrada para prevenir ataques. Sin embargo, estas validaciones a menudo pueden ser eludidas mediante técnicas creativas.

En este desafío, intenta eludir la validación que filtra ciertas palabras clave para ejecutar código JavaScript.

function validateAndExecute() {
  let input = document.getElementById('bypassInput').value;
  // Filtro básico
  if (input.includes('script') || input.includes('onerror') || input.includes('javascript')) {
    document.getElementById('bypassResult').innerHTML = 'Entrada bloqueada!';
    return;
  }
  document.getElementById('bypassResult').innerHTML = input;
}

Prueba con: <img src=x onerror=alert(1)> pero modificado para evitar las palabras clave bloqueadas.

Gadgets DOM

Los gadgets DOM son funciones o propiedades del DOM que pueden ser utilizadas de manera no intencionada para ejecutar código. A menudo se encuentran en bibliotecas JavaScript o en código de aplicación.

En este desafío, identifica y utiliza un gadget DOM para ejecutar código.

// Código de una biblioteca ficticia
const AppUtils = {
  sanitizeInput: function(input) {
    // Supuesta sanitización
    return input.replace(/<script>/gi, '');
  },
  renderContent: function(elementId, content) {
    const element = document.getElementById(elementId);
    if (element && content) {
      element.innerHTML = this.sanitizeInput(content);
    }
  }
};

// Tu código
function useGadget() {
  const input = document.getElementById('gadgetInput').value;
  AppUtils.renderContent('gadgetResult', input);
}

La función de sanitización solo elimina etiquetas <script>. ¿Qué otras etiquetas HTML pueden ejecutar JavaScript?

XSS en el DOM

El XSS basado en DOM ocurre cuando el código JavaScript de una página web procesa datos de origen no confiable de manera insegura y los escribe en el DOM.

Este desafío simula una aplicación que utiliza datos de la URL para actualizar el contenido de la página. Intenta explotar esta funcionalidad para ejecutar código malicioso.

// Código que se ejecuta al cargar la página
function processURLParams() {
  const urlParams = new URLSearchParams(window.location.search);
  const message = urlParams.get('message');
  if (message) {
    document.getElementById('xssResult').innerHTML = decodeURIComponent(message);
  }
}

// Ejecutar al cargar
window.onload = processURLParams;

Construye una URL que contenga tu payload en el parámetro 'message'. Por ejemplo: ?message=<svg onload=alert(1)>

DOM Clobbering

DOM Clobbering es una técnica donde se inyectan elementos HTML en una página para "sobrescribir" (clobber) propiedades y métodos JavaScript globales.

Este ataque es particularmente útil cuando no se puede ejecutar JavaScript directamente, pero se puede inyectar HTML.

// Código vulnerable
function checkAdmin() {
  if (window.isAdmin) {
    document.getElementById('adminPanel').style.display = 'block';
    document.getElementById('clobberResult').innerHTML = 'Acceso de administrador concedido!';
  } else {
    document.getElementById('clobberResult').innerHTML = 'Acceso denegado';
  }
}

Los elementos HTML con id se convierten en propiedades globales. Prueba con: <a id="isAdmin"></a>

document.URL / document.documentURI

document.URL y document.documentURI devuelven la URL completa del documento actual. Si esta fuente se utiliza sin la sanitización adecuada, puede conducir a vulnerabilidades XSS.

En este desafío, la aplicación utiliza la URL actual para mostrar un mensaje de bienvenida. Intenta explotar esta funcionalidad.

// Código vulnerable que usa document.URL
function displayWelcome() {
  const url = document.URL;
  const urlObj = new URL(url);
  const username = urlObj.searchParams.get('user') || 'Invitado';
  document.getElementById('urlResult').innerHTML = 'Bienvenido, ' + username + '!';
}

Intenta usar: ?user=<img src=x onerror=alert(1)>

document.referrer

document.referrer devuelve la URI de la página que enlazó a la página actual. Si este valor se refleja sin sanitización, un atacante puede crear un enlace malicioso que conduzca a XSS.

Esta aplicación muestra la página de referencia. Intenta explotar esta funcionalidad.

// Código vulnerable que usa document.referrer
function showReferrer() {
  const referrer = document.referrer;
  if (referrer) {
    document.getElementById('referrerResult').innerHTML = 'Viniste desde: ' + referrer;
  } else {
    document.getElementById('referrerResult').innerHTML = 'No hay referencia detectada';
  }
}

Para este desafío, crea una página HTML que enlace a esta página con un referrer malicioso.

Crea una página HTML con: <meta http-equiv="refresh" content="0;url=http://tu-dominio/lab.html?<script>alert(1)</script>">

window.name

window.name puede ser utilizado para transportar datos entre diferentes páginas y dominios. Su valor persiste durante la navegación y puede ser explotado para XSS si se refleja sin sanitización.

Esta aplicación utiliza window.name para recordar la última acción del usuario. Intenta explotar esta característica.

// Código vulnerable que usa window.name
function restoreState() {
  if (window.name) {
    document.getElementById('nameResult').innerHTML = 'Estado anterior: ' + window.name;
  } else {
    document.getElementById('nameResult').innerHTML = 'No hay estado guardado';
  }
}

function saveState() {
  const state = document.getElementById('nameInput').value;
  window.name = state;
  document.getElementById('nameResult').innerHTML = 'Estado guardado: ' + state;
}

Establece window.name con: <img src=x onerror=alert(1)> y luego recarga la página

Web Storage (localStorage, sessionStorage)

localStorage y sessionStorage permiten almacenar datos en el navegador. Si estos datos se reflejan sin sanitización, pueden conducir a vulnerabilidades XSS persistentes.

Esta aplicación guarda y muestra el tema de preferencia del usuario. Intenta explotar el almacenamiento local.

// Código vulnerable que usa localStorage
function loadTheme() {
  const theme = localStorage.getItem('userTheme') || 'predeterminado';
  document.getElementById('storageResult').innerHTML = 'Tema actual: ' + theme;
}

function saveTheme() {
  const theme = document.getElementById('storageInput').value;
  localStorage.setItem('userTheme', theme);
  document.getElementById('storageResult').innerHTML = 'Tema guardado: ' + theme;
}

Usa la consola para establecer: localStorage.setItem('userTheme', '<img src=x onerror=alert(1)>')

History API (pushState, replaceState)

history.pushState() y history.replaceState() permiten manipular el historial del navegador. Si los datos del estado se utilizan sin sanitización, pueden conducir a XSS.

Esta aplicación utiliza el estado del historial para mostrar información de navegación. Intenta explotar esta funcionalidad.

// Código vulnerable que usa history.state
function displayState() {
  if (history.state) {
    document.getElementById('historyResult').innerHTML = 'Estado: ' + JSON.stringify(history.state);
  } else {
    document.getElementById('historyResult').innerHTML = 'No hay estado en el historial';
  }
}

function setState() {
  const stateData = document.getElementById('historyInput').value;
  history.replaceState({data: stateData}, '', window.location);
  document.getElementById('historyResult').innerHTML = 'Estado establecido: ' + stateData;
}

Usa: history.replaceState({data: "<img src=x onerror=alert(1)>"}, '', location.href) en la consola