Skip to content
Field Detail
Platform PortSwigger Web Security Academy
Type Server-Side Prototype Pollution / RCE
Difficulty Practitioner
Objective Pollute Object.prototype via constructor.prototype to inject execArgv into Node.js child process spawning, then trigger RCE via the maintenance jobs endpoint to delete /home/carlos/morale.txt

Remote Code Execution via Server-Side Prototype Pollution

I logged in as wiener:peter — admin privileges are granted by default in this lab.

Screenshot

I confirmed the constructor.prototype pollution source on the address update endpoint — the json spaces gadget produced indented output and teto was reflected:

Screenshot

The admin panel had a "Run Maintenance Jobs" function:

Screenshot

Intercepting its POST showed it triggering db-cleanup and fs-cleanup tasks:

Screenshot

These tasks almost certainly spawn child processes via Node.js's child_process module. When Node spawns a child with child_process.fork(), it accepts an options object that includes execArgv — an array of arguments passed to the Node.js interpreter itself before the script runs. --eval is one of those arguments: it tells Node to execute a JavaScript string before the main script. If execArgv can be inherited from a polluted Object.prototype, the child process executes our code before doing anything else.

The gadget fires when a child process is spawned, not when pollution is injected — injecting into the prototype via the address form primes it; the maintenance jobs endpoint is the trigger. This two-step pattern (poison → trigger) requires identifying which application functionality spawns child processes.

I injected execArgv via the address form:

{
    "constructor": {
        "prototype": {
            "execArgv": [
                "--eval=require('child_process').execSync('rm /home/carlos/morale.txt')"
            ]
        }
    }
}

The response confirmed execArgv was now inherited from the prototype. Triggering execution by sending the /admin/jobs POST:

Screenshot

The server spawned child processes for each task; each inherited execArgv from the polluted prototype and executed rm /home/carlos/morale.txt before running the maintenance script. File deleted. The source here is constructor.prototype — same as the previous lab — with execArgv as the new gadget. Source and gadget are independent; once the pollution source is confirmed, gadget hunting is separate.

Lab solved

Resources