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

De Prototipo a Root

Prototype Pollution → RCE

// OBJETIVO

Encadenar la contaminación de Object.prototype con un motor de plantillas que compila con new Function, para ejecutar código en el servidor del lab y leer la flag.

// 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:

GitHub no deja bajar una carpeta suelta, así que el .zip trae todos los labs: el de este reto es prototype-pollution-rce/. Si usás git:

git clone https://github.com/Ismaeldevs/jsforge-labs
cd jsforge-labs/prototype-pollution-rce
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

    La misma primitiva del reto anterior (un deep merge que sigue __proto__) ahora alcanza un sink que compila código. El motor de plantillas construye sus opciones como un objeto fresco, así que hereda cualquier sourceURL contaminado, y lo concatena dentro de la función que crea con new Function:

    function compile(source, options) {
      const opts = options || {};
      let body = /* ...arma __out a partir de la plantilla... */;
      // El sourceURL se pega como comentario al inicio del cuerpo, sin sanear.
      const header = '//# sourceURL=' + (opts.sourceURL || 'jsforge-template') + '\n';
      return new Function('data', header + body);
    }
    
    app.post('/api/render', (req, res) => {
      const options = {};                       // ← hereda sourceURL del prototipo
      const render = compile(req.body.template, options);
      res.json({ output: render(req.body.data) });
    });

    Un body { "__proto__": { "sourceURL": "\n return process.env.FLAG //" } } deja ({}).sourceURL con un salto de línea seguido de código. Al compilar, el \n cierra el comentario //# y el return del atacante se ejecuta dentro de la función: ejecución de código en el proceso. Es la clase de CVE-2021-23337 (lodash.template).

    Remediación

    Cortá la cadena en sus dos eslabones: la primitiva y el sink.

    - deepMerge(settings, req.body);
    + deepMerge(settings, req.body); // endurecido: descarta __proto__/constructor/prototype
    +
    - const options = {};
    + const options = Object.assign(Object.create(null), SAFE_DEFAULTS, opcionesPropias);
      const render = compile(req.body.template, options);

    Por qué funciona: una vez que el merge no puede tocar Object.prototype, el sink deja de heredar opciones ajenas; y construir las opciones sobre Object.create(null) con defaults explícitos garantiza que ninguna propiedad provenga del prototipo. Sin una sourceURL controlada no hay string del atacante que termine dentro de la función compilada, y la cadena hacia RCE se rompe en su primer eslabón. Como refuerzo, no interpoles opciones dentro de código que compilás con new Function.

    Reto descargable. Permite ejecución de código: nunca lo expongas a internet. Descargá el lab Docker y corrélo aislado en tu propia máquina.

    Verificación

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