JSForge
Acceder →
← Volver al catálogo
SERVIDORDIFÍCIL Descargable

Contaminación de Servidor

Prototype Pollution (servidor)

// OBJETIVO

Contaminar Object.prototype desde un request para alterar el comportamiento del servidor y habilitar una ruta que debería estar cerrada.

// ZONA DE ATAQUE

Reto descargable

Este reto ejecuta código del lado servidor, así que no corre en el sitio. Descargá el lab y ejecutalo aislado en tu máquina:

git clone https://github.com/Ismaeldevs/JSForge-Labs.git
cd JSForge-Labs/prototype-pollution-servidor
docker compose up

Abrí http://localhost:8080, resolvé el reto y volvé acá a enviar la flag. Nunca expongas el contenedor a internet.

// CONSULTÁ

Pistas

    Código vulnerable

    El deep merge del lado servidor sigue __proto__ igual que su primo del cliente:

    function deepMerge(target, source) {
      for (const key of Object.keys(source)) {
        if (isObject(source[key])) {
          deepMerge((target[key] ??= {}), source[key]); // ← __proto__ incluido
        } else {
          target[key] = source[key];
        }
      }
    }
    
    app.patch('/api/settings', (req, res) => {
      deepMerge(settings, req.body);
      res.json({ ok: true });
    });

    Un body { "__proto__": { "debugEndpointEnabled": true } } deja ({}).debugEndpointEnabled === true en todo el proceso, y el server empieza a tratar como habilitada una feature que nunca se activó explícitamente.

    Remediación

    Bloqueá las claves peligrosas y aislá los objetos intermedios:

      function deepMerge(target, source) {
        for (const key of Object.keys(source)) {
    +     if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue;
          if (isObject(source[key])) {
    -       deepMerge((target[key] ??= {}), source[key]);
    +       deepMerge((target[key] ??= Object.create(null)), source[key]);
          } else {
            target[key] = source[key];
          }
        }
      }

    Además, leé las flags de feature con Object.hasOwn(settings, 'x') en vez de truthiness heredada, para no confiar en propiedades que puedan venir del prototipo.

    Por qué funciona: descartar __proto__ corta el camino al prototipo global, y Object.create(null) deja los nodos intermedios sin uno. La lectura con Object.hasOwn ignora cualquier propiedad contaminada que igual llegue a existir.

    Reto descargable. Involucra ejecución del lado servidor, así que no corre en el sitio: descargá el lab Docker y ejecutalo aislado en tu máquina.

    Verificación

    Se verifica en tu navegador contra un hash. La flag no se envía a ningún lado.