Zip Slip with Bearer Tokens
How Zip Slip Manifests in Bearer Tokens
Zip Slip occurs when an application extracts files from an archive without validating that the extracted paths stay inside a safe directory. When the archive is obtained via an API call that uses a Bearer Token for authentication, the vulnerable code path often looks like this:
const express = require('express');
const axios = require('axios');
const AdmZip = require('adm-zip');
const app = express();
// Middleware that expects a Bearer Token in the Authorization header
app.use((req, res, next) => {
const auth = req.headers.authorization;
if (!auth || !auth.startsWith('Bearer ')) {
return res.status(401).send('Missing token');
}
req.token = auth.slice(7); // strip 'Bearer '
next();
});
app.get('/fetch-and-extract', async (req, res) => {
try {
// The token is used to authenticate a request to a third‑party service
const response = await axios.get('https://example.com/assets/plugin.zip', {
headers: { Authorization: `Bearer ${req.token}` }
});
// Write the zip to a temporary file
const tmpPath = '/tmp/plugin.zip';
require('fs').writeFileSync(tmpPath, response.data);
// Vulnerable extraction – no path validation
const zip = new AdmZip(tmpPath);
zip.extractAll('/var/www/plugins/', true); // <-- Zip Slip here
res.send('Plugin installed');
} catch (err) {
res.status(500).send('Error');
}
});
app.listen(3000);
If the attacker controls the contents of plugin.zip (for example, by compromising the third‑party service or by hosting a malicious zip under a URL that the token‑protected endpoint trusts), they can include an entry such as ../../../etc/passwd. When extractAll runs, the entry is written outside /var/www/plugins/, overwriting or creating arbitrary files on the host. Because the request that triggers the extraction is authenticated with a Bearer Token, the vulnerability is only reachable by a client that can present a valid token, making the issue a privileged‑access path‑traversal problem.
Bearer Tokens‑Specific Detection
Detecting this variant of Zip Slip requires looking for two correlated patterns:
- The presence of a Bearer Token used to authenticate an outbound HTTP request that returns binary data (commonly a zip, tar, or similar archive).
- Subsequent extraction of that data with a library that does not sanitize entry names (e.g.,
adm-zip,JSZip,unzipper,archiver,yauzl).
middleBrick’s Input Validation check scans the request/response flow for unauthenticated endpoints, but it also follows authenticated calls when a token is supplied in the request (the scanner can attach a dummy Bearer Token to test the endpoint). When it observes:
- An outbound request whose URL is built from user‑controlled input or a third‑party URL, authenticated with a Bearer Token header,
- The response body being written to a file and then passed to an extraction routine without any path‑sanitization logic,
- It flags a Zip Slip finding, annotating it with the specific line of code and the token‑usage context.
Because middleBrick works purely from the outside (black‑box), it does not need source code or agents; it simply sends a request with a Bearer Token, monitors whether the response contains an archive, and attempts to detect dangerous extraction signatures in the subsequent processing (e.g., by looking for known vulnerable library calls in the response timing or error messages). If the endpoint returns an error that reveals the extraction path, the scanner can confirm the presence of a traversal attempt.
Example of a finding that middleBrick might report:
Finding: Zip Slip (Path Traversal) – High Severity
Endpoint: GET /fetch-and-extract
Details: The endpoint authenticates with a Bearer Token, downloads a zip from https://example.com/assets/plugin.zip, and extracts it using adm‑zip without validating entry names. An attacker who controls the zip can write files outside the intended /var/www/plugins/ directory.
Remediation: Validate each entry’s resolved path stays within the target directory before extraction.Bearer Tokens‑Specific Remediation
The fix focuses on two areas: safe handling of the Bearer Token and safe archive extraction. Below is a corrected version of the previous example.
const express = require('express');
const axios = require('axios');
const path = require('path');
const { promisify } = require('util');
const fs = require('fs');
const AdmZip = require('adm-zip');
const app = express();
app.use((req, res, next) => {
const auth = req.headers.authorization;
if (!auth || !auth.startsWith('Bearer ')) {
return res.status(401).send('Missing token');
}
req.token = auth.slice(7);
next();
});
// Helper to safely check if a resolved path stays inside baseDir
function isSafe(baseDir, targetPath) {
const base = path.resolve(baseDir);
const target = path.resolve(targetPath);
return target.startsWith(base + path.sep) || target === base;
}
app.get('/fetch-and-extract', async (req, res) => {
try {
const response = await axios.get('https://example.com/assets/plugin.zip', {
headers: { Authorization: `Bearer ${req.token}` }
});
const tmpPath = '/tmp/plugin.zip';
fs.writeFileSync(tmpPath, response.data);
const zip = new AdmZip(tmpPath);
const extractDir = '/var/www/plugins/';
// Iterate entries and validate each before extraction
for (const entry of zip.getEntries()) {
if (entry.isDirectory) continue; // directories are created automatically
const entryPath = path.join(extractDir, entry.entryName);
if (!isSafe(extractDir, entryPath)) {
return res.status(400).send(`Unsafe entry detected: ${entry.entryName}`);
}
}
// All entries are safe – now extract
zip.extractAll(extractDir, true);
res.send('Plugin installed safely');
} catch (err) {
console.error(err);
res.status(500).send('Error');
}
});
app.listen(3000);
Key changes:
- The Bearer Token is still used to authenticate the outbound request, but it is never logged or exposed in error messages.
- Before extracting each entry, the code computes the absolute path and ensures it resides inside the intended extraction directory using a whitelist‑style check (
isSafe). - If any entry fails the check, the request is halted with a 400 response, preventing the Zip Slip.
Other language‑specific safe‑extraction patterns achieve the same goal:
- Python: use
tarfileorzipfilewithos.path.normpathand verifyos.path.commonpath. - Java: when using
java.util.zip.ZipFile, callZipEntry.getName(), resolve against the destination directory withjava.nio.file.Paths, and confirm the resolved path starts with the base directory. - Go: with the
archive/zippackage, iterateReader.File, usefilepath.Joinandfilepath.Absto check containment.
By combining proper token handling with rigorous path validation, the Zip Slip risk associated with Bearer Token‑authenticated archive downloads is eliminated.