Si compruebas cualquier Security Advisory actualizado, es fácil encontrar nuevas vulnerabilidades reflected y stored XSS descubiertas cada mes. Sin embargo no es igual para las DOM-Based XSS. Está completamente basada en el navegador y no necesita ir al servidor y volver. Antes, podía crearse una PoC de DOM-Based XSS con una página HTML estática. Sin embargo, con la evolución de los navegadores y su seguridad se ha dejado de poder y se ha vuelto todavía más difícil.

Antes de sumergirnos en las DOM-Based XSS, veamos lo que es un Document Object Model (DOM). El DOM es una interfaz de programación que representa un documento web como un árbol. Hace posible acceder y manipular las diferentes partes de la web de forma programada usando JavaScript.

Considera el HTML de example.com como era en Febrero de 2024 (abajo).

DOM-Based XSS

El árbol DOM sigue este formato:

  • document
    • <!DOCTYPE html>
    • html
      • title
      • meta
      • meta
      • meta
      • style
    • body
      • div
        • h1
        • p
        • p
          • a

Podemos ver el DOM tree usando las herramientas de desarrollador del navegador.

Usando JavaScript podemos manipular el DOM tree. Por ejemplo, podemos crear un nuevo elemento usando document.createElement() y añadir un hijo a cualquier elemento usando element.append().

Aquí tienes un ejemplo de MDN Documentation.

javascript
let div = document.createElement("div");
let p = document.createElement("p");
div.append(p);

console.log(div.childNodes); // NodeList [ <p> ]

En este ejemplo hemos creado dos elementos dic y p. Luego hemos añadido p a div.


Vulnerable Web Applications

Las vulnerabilidades DOM-Based toman lugar en el navegador. No necesitan ir al servidor y volver, es decir que el atacante inyectará código, por ejemplo, en la URL y este será ejecutado en el propio navegador.


Vulnerable "Static" Site

Consideremos el siguiente ejemplo. Es tan sencillo que es surrealista. Sin embargo, es suficiente para demostrar la DOM-Based XSS.

html
<!DOCTYPE html>
<html>
<head>
    <title>Vulnerable Page</title>
</head>
<body>
    <div id="greeting"></div>
    <script>
        const name = new URLSearchParams(window.location.search).get('name');
        document.write("Hello, " + name);
    </script>
</body>
</html>

La página de arriba espera que el usuario le de su nombre en ?name=. En la imagen de abajo:

  1. El usuario ha introducido "Web Tester" después de ?name en la URL.
  2. El saludo funcionó como se esperaba, salió "Hello, Web Tester".
  3. Finalmente la estructura DOM en la derecha está intacta. El <body> tiene 3 hijos.

DOM-Based XSS

El usuario puede intentar inyectar un script malicioso. En la imagen de abajo vemos:

  1. El usuario añadió <script>alert("XSS")</script> en vez de sólo Web Tester como su nombre.
  2. El script se ejecutó y la caja de alerta se desplegó.
  3. Más importante, podemos ver cómo el DOM tree ahora tiene un nuevo elemento bajo <body>.

DOM-Based XSS

Este ejemplo básico muestra un par de cosas:

  • El servidor no tiene rol directo en las vulnerabilidades basadas en DOM.
  • El DOM fue modificado de forma insegura usando document.write().

Fixed "Static" Site

html
<!DOCTYPE html>
<html>
<head>
    <title>Secure Page</title>
</head>
<body>
    <div id="greeting"></div>
    <script>
        const name = new URLSearchParams(window.location.search).get('name');
        // Escape the user input to prevent XSS attacks
        const escapedName = encodeURIComponent(name);
        document.getElementById("greeting").textContent = "Hello, " + escapedName;
    </script>
</body>
</html>

Una manera de arreglar esta página es evitar añadir el input del usuario directamente con document.write(). En su lugar, primero lo escapamos con encodeURIComponent() y luego lo añadimos a textContent.

El intento anterior no funciona ahora.

DOM-Based XSS