CodePark/ questions / Deserialization, XML, archives & paths
START PRACTICINGPRACTICE
Backend question · Auth & security › Deserialization, XML, archives & paths

This code extracts an uploaded zip file. What is the vulnerability, and how do you fix it?

// `entries` comes from any zip library
for (const entry of entries) {
  const outPath = path.join("/srv/imports", entry.name);
  await fs.promises.mkdir(path.dirname(outPath), { recursive: true });
  await fs.promises.writeFile(outPath, await entry.read());
}

This is Zip Slip, a path traversal through archive entry names. The attacker chooses every name inside the zip. A name like ../../srv/app/server.js makes path.join climb out of /srv/imports. The code then overwrites your app's code or config. That can lead to remote code execution the next time the app starts. The same bug affects tar and other archive formats.

Fix:

  • Resolve and check every path:

const root = path.resolve("/srv/imports");
const out = path.resolve(root, entry.name);
if (!out.startsWith(root + path.sep)) throw new Error("Bad path");

  • Reject absolute paths and symlink entries. A symlink entry can point outside the folder, and a later file can be written through it.
  • Limit the total unpacked size and the file count. A zip bomb is a tiny file that unpacks to gigabytes.
  • Extract into a new temporary folder, as a user that has no write access to the app's code.
  • Keep the archive library updated. Many libraries fixed this bug after it was published in 2018.
  • Archive entry names are attacker input and can contain ../.
  • Resolve each output path and check that it stays inside the target folder.
  • Reject absolute paths and symlink entries.
  • Limit the unpacked size and file count to stop zip bombs.
  • Extract as a user that can't write to the app's code.