SourCode a FreeFungi app

Challenges FF-012

Toffee CWE-22 A01:2021

Recipe Card

Path traversal in the allergen-sheet download.

Each sweet has an allergen sheet you can download. The shop builds the file path by tacking your filename onto its allergens folder and reading whatever comes out. Because it never checks where that path actually points, a few ../ steps walk out of the folder and read files the shop never meant to hand over. This is path traversal.

New to this? What is path traversal?

Path traversal (or directory traversal) is when a filename you supply is used to build a path on the server without confining it. The special sequence ../ means “up one folder”, so enough of them climb out of the intended directory.

From there you can read configuration, source code, secrets, or system files like /etc/passwd. The fix is to resolve the final path and confirm it still sits inside the folder you meant.

Make sure the shop is running at http://localhost:3000. No account needed.

1 · Find it

On a sweet’s page, click Allergen sheet. Look at your browser’s address bar: the download is served from a URL that names the file at the end, after sheet=:

http://localhost:3000/allergens?sheet=rhubarb-and-custard.txt

The sheet parameter is just a filename, handed straight to the server. That is the thing to poke at: change the value after sheet= and press Enter.

2 · Exploit it

Point it up and out of the allergens folder with ../ (which means “go up one folder”). Put this in the address bar:

http://localhost:3000/allergens?sheet=../package.json

Back comes the app’s package.json, which lives outside the allergens folder entirely. Add more steps to climb further, all the way to system files:

http://localhost:3000/allergens?sheet=../../../../../../etc/passwd

Why it matters: the same trick reads source code, environment files, keys and credentials. A read-only file bug quickly becomes a full compromise once it finds a secret.

3 · Understand it

The handler joins your filename onto the allergens directory and reads it. Joining does not stop ../, so the final path can land anywhere:

app.get('/allergens', (req, res) => {
  const file = path.join(allergensDir, req.query.sheet);
  res.send(readFileSync(file, 'utf8'));   // wherever that points
});

path.join(allergensDir, '../package.json') quietly resolves to a file one level up. The code trusted the filename to stay put. It did not.

4 · Fix it

Resolve the final path and confirm it is still inside the allergens folder before reading. If it escaped, refuse:

✗ Vulnerable
const file =
  path.join(allergensDir, sheet);
res.send(readFileSync(file));
✓ Patched
const file =
  path.resolve(allergensDir, sheet);
if (!file.startsWith(allergensDir + path.sep)) {
  return res.sendStatus(400);
}
res.send(readFileSync(file));

Now ../package.json resolves to a path outside the folder, the check fails, and the shop hands back nothing but a polite refusal.

The general rule: never build a file path straight from user input. Map an id to a known filename where you can, and when you must take a name, resolve it and confirm it stays inside the intended directory. Reject anything that climbs out.