Skip to content

6 - Exploiting DOM clobbering to enable XSS

#portswigger #dombased #xss #domclobbering

https://www.youtube.com/watch?v=eWD4LH5W2Es

El ==DOM Clobbering== es una vulnerabilidad de seguridad en aplicaciones web que ocurre cuando elementos del Documento Object Model (DOM) de una página web sobrescriben o "clobber" (destruyen o alteran) objetos o variables globales de JavaScript en el entorno del navegador.

¿Cómo ocurre?

En HTML, los elementos pueden tener atributos id o name, y estos nombres pueden crear referencias globales en el entorno JavaScript. Por ejemplo, si un elemento tiene un id llamado form, en JavaScript podría generarse automáticamente una variable global window.form que apunta a ese elemento. Sin embargo, si la aplicación ya usa una variable llamada form, esta referencia puede ser sobrescrita por el elemento DOM, lo que puede causar comportamientos inesperados o vulnerabilidades de seguridad.

Ejemplo básico

Supongamos que una aplicación tiene un código JavaScript que utiliza una variable form para referirse a un objeto que maneja la lógica de formularios:

javascript
var form = new FormHandler();
form.submit();

Ahora, si en el HTML se define un elemento con id="form":

html
<form id="form" action="/submit">
  ...
</form>

En algunos navegadores, la variable form en JavaScript será sobrescrita por la referencia al elemento DOM <form>. Esto se conoce como clobbering. Ahora, si se intenta llamar a form.submit(), en lugar de ejecutar la lógica del objeto FormHandler, se llamará al método submit() del elemento <form>, lo que podría llevar a un comportamiento no deseado.

==loadCommentsWithDomClobbering.js==

javascript
function loadComments(postCommentPath) {
  • Descripción: Es la función principal que se ejecuta cuando se desea cargar los comentarios de un post.
  • Parámetro: postCommentPath es la ruta del servidor desde la que se obtendrán los comentarios.
javascript
    let xhr = new XMLHttpRequest();
  • Descripción: Crea un nuevo objeto XMLHttpRequest, que se utiliza para realizar una solicitud HTTP para obtener datos sin recargar la página.
javascript
    xhr.onreadystatechange = function() {
  • Descripción: Define una función que se ejecuta cada vez que el estado (readyState) de la solicitud cambia.
javascript
        if (this.readyState == 4 && this.status == 200) {
  • Condición: Si readyState es 4 (la solicitud está completa) y el status es 200 (OK), entonces se procesan los comentarios.
javascript
            let comments = JSON.parse(this.responseText);
  • Acción: La respuesta de la solicitud (asumida como JSON) se convierte en un objeto JavaScript usando JSON.parse.
javascript
            displayComments(comments);
  • Acción: Se llama a la función displayComments para mostrar estos comentarios.
javascript
        }
    };
  • Fin de la función onreadystatechange.
javascript
    xhr.open("GET", postCommentPath + window.location.search);
  • Descripción: Configura la solicitud para ser de tipo GET y construye la URL de destino agregando los parámetros de la URL actual (window.location.search) a postCommentPath.
javascript
    xhr.send();
  • Descripción: Envía la solicitud al servidor.
javascript
    function escapeHTML(data) {
        return data.replace(/[<>'"]/g, function(c){
            return '&#' + c.charCodeAt(0) + ';';
        });
    }
  • Descripción: Es una función auxiliar para evitar que caracteres especiales como <, >, ', y " sean interpretados como HTML, evitando así inyecciones de código.
  • Uso: Reemplaza caracteres problemáticos por sus equivalentes HTML.
javascript
    function displayComments(comments) {
  • Descripción: Es la función que se encarga de generar y mostrar los comentarios en el DOM (Document Object Model).
  • Parámetro: comments es un array de objetos, donde cada objeto representa un comentario.
javascript
        let userComments = document.getElementById("user-comments");
  • Descripción: Obtiene el elemento del DOM con el ID "user-comments" donde se agregarán los comentarios.
javascript
        for (let i = 0; i < comments.length; ++i) {
  • Descripción: Un bucle for recorre cada comentario en comments.
javascript
            comment = comments[i];
  • Descripción: Se accede al comentario actual en la iteración del bucle.
javascript
            let commentSection = document.createElement("section");
            commentSection.setAttribute("class", "comment");
  • Descripción: Se crea un elemento section para encapsular cada comentario y se le asigna la clase comment.
javascript
            let firstPElement = document.createElement("p");
  • Descripción: Se crea un párrafo (<p>) para contener el nombre del autor, el avatar y la fecha.
javascript
            let defaultAvatar = window.defaultAvatar || {avatar: '/resources/images/avatarDefault.svg'};
            let avatarImgHTML = '<img class="avatar" src="' + (comment.avatar ? escapeHTML(comment.avatar) : defaultAvatar.avatar) + '">';
  • Descripción: Si hay un avatar en el comentario, se utiliza; si no, se muestra un avatar por defecto. ==¿comment.avatar? si -> escape(avatar) , no -> default avatar==
  • escapeHTML: Se utiliza para evitar posibles inyecciones a través de URLs del avatar.
javascript
            let divImgContainer = document.createElement("div");
            divImgContainer.innerHTML = avatarImgHTML;
  • Descripción: Se crea un div para contener la imagen del avatar.
javascript
            if (comment.author) {
                if (comment.website) {
                    let websiteElement = document.createElement("a");
                    websiteElement.setAttribute("id", "author");
                    websiteElement.setAttribute("href", comment.website);
                    firstPElement.appendChild(websiteElement);
                }
  • Descripción: Si el autor está presente y tiene un sitio web, se crea un enlace (<a>) que apunta a ese sitio.
javascript
                let newInnerHtml = firstPElement.innerHTML + DOMPurify.sanitize(comment.author);
                firstPElement.innerHTML = newInnerHtml;
            }
  • Descripción: Se "sanitiza" el nombre del autor y se actualiza el contenido de firstPElement.
javascript
            if (comment.date) {
                let dateObj = new Date(comment.date);
                let month = '' + (dateObj.getMonth() + 1);
                let day = '' + dateObj.getDate();
                let year = dateObj.getFullYear();
  • Descripción: Si hay una fecha, se convierte en un objeto Date para su manipulación.
javascript
                if (month.length < 2) month = '0' + month;
                if (day.length < 2) day = '0' + day;
  • Descripción: Se asegura que el día y el mes tengan dos dígitos.
javascript
                dateStr = [day, month, year].join('-');
  • Descripción: La fecha se formatea como dd-mm-yyyy.
javascript
                let newInnerHtml = firstPElement.innerHTML + " | " + dateStr;
                firstPElement.innerHTML = newInnerHtml;
            }
  • Descripción: Se agrega la fecha al contenido de firstPElement.
javascript
            firstPElement.appendChild(divImgContainer);
  • Descripción: Se añade el div con la imagen del avatar a firstPElement.
javascript
            commentSection.appendChild(firstPElement);
  • Descripción: Se agrega firstPElement a la section del comentario.
javascript
            if (comment.body) {
                let commentBodyPElement = document.createElement("p");
                commentBodyPElement.innerHTML = DOMPurify.sanitize(comment.body);
  • Descripción: Si hay un cuerpo de texto, se crea un nuevo párrafo con el contenido del comentario. Este contenido se "sanitiza" con DOMPurify.
javascript
                commentSection.appendChild(commentBodyPElement);
            }
            commentSection.appendChild(document.createElement("p"));
  • Descripción: Se añade el párrafo del cuerpo del comentario a la section del comentario y se agrega otro párrafo vacío como separación.
javascript
            userComments.appendChild(commentSection);
        }
    }
  • Descripción: Cada comentario procesado se inserta en userComments.
javascript
};
  • Fin de la función loadComments.

==Solución :==

html
<a id=defaultAvatar><a id=defaultAvatar name=avatar href="cid:&quot;onerror=alert(1)//">

Estamos clobbeando default avatar,si nos fijamos en la elección del default avatar (se evalúan los OR de izquierda a derecha):

javascript
let defaultAvatar = window.defaultAvatar || {avatar: '/resources/images/avatarDefault.svg'};

¿Existe comment.avatar? No -> el src será el atributo avatar del objeto defaultAvatar:

html
let avatarImgHTML = '<img class="avatar" src="' + (comment.avatar ? escapeHTML(comment.avatar) : defaultAvatar.avatar) + '">';

==¿Porque dos anchor?== -> Si el primer elemento con id="defaultAvatar" no sobrescribe completamente la variable global (porque el DOM trata de mantener una referencia a ambos), el segundo elemento podría asegurar que la propiedad avatar sea clobberada, ya que ahora está intentando sobrescribir una referencia a un grupo de elementos en lugar de un único elemento. ==(Ponerlo por si acaso)==

El valor href="cid:&quot;onerror=alert(1)//" es un intento de inyección maliciosa de código en un atributo HTML, con la intención de ejecutar un ataque XSS (Cross-Site Scripting) utilizando el evento onerror de una etiqueta <img>.

Vamos a desglosar lo que hace cada parte:

1. href="cid:&quot;onerror=alert(1)//"

  • href="cid::

    • href es un atributo comúnmente usado en elementos como <a> o <img>. Aquí, el valor comienza con cid:, que es un esquema URI que se usa a veces para identificar contenido incrustado en correos electrónicos (como imágenes).
    • En este caso, cid: ==se explica abajo.==
  • &quot;:

    • &quot; es la entidad HTML que representa una comilla doble ("). Está siendo utilizada para "cerrar" el atributo href en el HTML interpretado, terminando la URL de forma prematura. Es un truco para salir del contexto de la cadena del href y comenzar a introducir código arbitrario.
  • onerror=alert(1)//:

    • onerror es un evento que se dispara en ciertos elementos HTML, como imágenes (<img>), cuando ocurre un error en la carga del recurso.
    • alert(1) es un código JavaScript que simplemente muestra un cuadro de alerta con el número 1.
    • // es un comentario en JavaScript que hace que todo lo que sigue en la misma línea se ignore, lo que asegura que no haya errores sintácticos por el código malicioso inyectado.

Como podemos ver usar el esquema URI cid: implica que la comilla sea url encodeada o no lo sea.

Notas personales de seguridad ofensiva.