Zip Slip in Express with Basic Auth
Zip Slip in Express with Basic Auth — how this specific combination creates or exposes the vulnerability
Zip Slip is a path traversal vulnerability where an attacker-controlled archive (e.g., a ZIP file) is extracted without proper sanitization, allowing files to be written outside the intended extraction directory. In an Express API that accepts file uploads and uses Basic Auth for access control, the combination of user-supplied archives and per-request authentication can amplify risk in a few specific ways.
First, Basic Auth in Express is typically implemented by reading the Authorization header, decoding the base64-encoded username:password pair, and validating credentials per request. If the validation logic is applied only at the route level without ensuring strict input handling for uploaded files, an authenticated context can give an attacker a foothold to probe or exploit file extraction behavior. For example, an authenticated client might repeatedly send crafted archives containing malicious paths such as ../../../etc/passwd or executable scripts that overwrite critical files during extraction.
Second, because middleBrick scans the unauthenticated attack surface and also tests authentication and authorization checks (BOLA/IDOR and Authentication), it can detect whether path traversal is possible in endpoints that rely on Basic Auth without additional validation. An API that accepts a file payload and a target extraction path, and then uses user-controlled data to build filesystem paths, may expose directory traversal even when Basic Auth is present. This is because authentication verifies identity but does not sanitize or restrict what an authenticated user can request the server to extract.
Third, the interplay with middleware matters. If Express routes parse the Authorization header and then pass the request to an upload handler that uses libraries like adm-zip or yauzl without validating entry.name, an attacker can send a ZIP with absolute or relative paths that escape the destination folder. middleBrick’s checks for Input Validation and Property Authorization are designed to surface these classes of issues by correlating spec definitions with runtime behavior, highlighting missing constraints on path parameters and uploaded archive contents.
Concrete attack patterns in this scenario include an authenticated user uploading a ZIP containing entries like ../../../../../windows/system32/config/sam on Windows or ../../../../etc/shadow on Unix-like systems. If the server extracts entries using naive concatenation (e.g., const dest = path.join(uploadDir, entry.name) without normalization and containment checks), the resulting extracted file can overwrite system or application files, leading to information disclosure or code execution depending on the file type and server privileges.
To summarize, the risk in Express with Basic Auth arises when authentication is treated as the sole security boundary while file extraction logic fails to validate and sanitize paths inside user-provided archives. middleBrick’s parallel checks for Authentication, Input Validation, and Property Authorization help identify whether path traversal exists in endpoints that require credentials, providing findings with severity and remediation guidance rather than attempting to fix the extraction logic itself.
Basic Auth-Specific Remediation in Express — concrete code fixes
Securing Express endpoints that use Basic Auth and file extraction requires strict validation of paths and separation of authentication from file handling logic. Below are concrete, working code examples demonstrating safer approaches.
Example 1: Basic Auth middleware with proper path sanitization
Use a dedicated middleware to validate credentials, and ensure any file path derived from user input is resolved within a strict base directory using path.resolve and containment checks.
import express from 'express';
import { createHash } from 'crypto';
import path from 'path';
const app = express();
app.use(express.json());
app.use(express.raw({ type: 'application/zip' }));
const users = new Map([
['alice', createHash('sha256').update('s3cret').digest('hex')]
]);
function basicAuth(req, res, next) {
const header = req.headers.authorization;
if (!header || !header.startsWith('Basic ')) {
return res.status(401).send('Unauthorized');
}
const token = header.slice(6);
const decoded = Buffer.from(token, 'base64').toString('utf8');
const [user, pass] = decoded.split(':');
const expected = users.get(user);
if (!expected || expected !== createHash('sha256').update(pass).digest('hex')) {
return res.status(401).send('Unauthorized');
}
next();
}
app.post('/upload', basicAuth, (req, res) => {
const zipBuffer = req.body;
const safeBase = path.resolve('./uploads');
try {
const zip = new AdmZip(zipBuffer);
const entries = zip.getEntries();
for (const entry of entries) {
const normalized = path.normalize(entry.entryName).replace(/^(\/|\\)/, '');
const target = path.resolve(safeBase, normalized);
if (!target.startsWith(safeBase)) {
return res.status(400).send('Invalid entry path');
}
// Ensure entry is a file and does not overwrite outside safeBase
if (entry.entryName.endsWith('/')) {
// handle directories if needed
continue;
}
// write file to target
}
res.send('OK');
} catch (err) {
res.status(400).send('Failed to process archive');
}
});
app.listen(3000, () => console.log('Server running on port 3000'));
Example 2: Using multer with strict filename validation
If you prefer streaming uploads, combine Basic Auth with multer and a filename sanitizer that rejects paths and enforces extensions.
import express from 'express';
import multer from 'multer';
import { createHash } from 'crypto';
import path from 'path';
const app = express();
const safeBase = path.resolve('./uploads');
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, safeBase);
},
filename: (req, file, cb) => {
const cleanName = file.originalName.replace(/[^a-zA-Z0-9_.-]/g, '_');
cb(null, cleanName);
}
});
const upload = multer({ storage, limits: { fileSize: 5 * 1024 * 1024 } });
function basicAuth(req, res, next) {
const header = req.headers.authorization;
if (!header || !header.startsWith('Basic ')) return res.status(401).send('Unauthorized');
const token = Buffer.from(header.slice(6), 'base64').toString('utf8');
const [user, pass] = token.split(':');
if (user === 'alice' && pass === 's3cret') return next();
res.status(401).send('Unauthorized');
}
app.post('/upload-basic', basicAuth, upload.single('archive'), (req, res) => {
if (!req.file) return res.status(400).send('No file uploaded');
res.send('File received safely');
});
app.listen(3000, () => console.log('Server running on port 3000'));
Key remediation practices
- Always normalize and resolve paths with
path.resolveand verify they remain within the intended base directory. - Do not trust entry names from archives; sanitize and validate before using them in filesystem operations.
- Keep authentication separate from file processing logic; avoid coupling credential checks with extraction routines.
- Limit file size and inspect content types to reduce impact of malicious uploads.
middleBrick’s scans can highlight missing validation around path parameters and weak authentication coupling, giving prioritized findings and remediation guidance without claiming to fix or block anything.