Zip Slip with Basic Auth
How Zip Slip Manifests in Basic Auth
Zip Slip vulnerabilities in Basic Auth contexts typically emerge when authentication endpoints accept file uploads that get processed without proper path validation. The attack vector combines two critical elements: the Basic Auth mechanism itself and the file handling logic that processes uploaded archives.
Consider a typical Basic Auth upload endpoint:
app.post('/upload', authenticateBasic, (req, res) => {
const zip = req.files.upload;
const extractPath = path.join(__dirname, 'uploads', req.user.id);
// Vulnerable: no path traversal validation
unzip(zip.tempFilePath, extractPath, (err) => {
if (err) return res.status(500).send('Extraction failed');
res.send('File uploaded successfully');
});
});The vulnerability occurs because Basic Auth provides authenticated access to the endpoint, but the file extraction logic doesn't validate archive contents. An attacker with valid Basic Auth credentials can upload a ZIP file containing paths like ../../etc/passwd or ../../../../etc/shadow.
Real-world exploitation patterns include:
- Configuration file overwrite: Overwriting application configuration files like
config.jsonorsettings.yamlto manipulate application behavior - Web shell deployment: Extracting PHP/ASP/JSP shells into web-accessible directories to gain remote code execution
- SSH key injection: Planting malicious SSH keys to enable persistent access
- Database credential theft: Extracting database configuration files containing connection strings and credentials
The Basic Auth context matters because it provides the authenticated session needed to access the upload functionality. Without valid credentials, the attacker cannot reach the vulnerable endpoint. This creates a false sense of security—developers assume authentication prevents exploitation, but it only controls access to the vulnerable code path.
Common Basic Auth implementations where Zip Slip appears:
// Node.js with express-basic-auth
const basicAuth = require('express-basic-auth');
app.use('/admin', basicAuth({
users: { 'admin': 'password' }
}), adminRoutes);
// Spring Boot with HTTP Basic
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").authenticated()
.and().httpBasic();
}
}
// Django with Basic Auth
@require_http_basic
def upload_view(request):
if not request.user.is_authenticated:
return HttpResponse('Unauthorized', status=401)
# Process upload without path validation
The authentication layer (Basic Auth) and the vulnerability (Zip Slip) operate independently. Basic Auth ensures only authenticated users can upload files, but it doesn't inspect or validate the contents of those files.
Basic Auth-Specific Detection
Detecting Zip Slip in Basic Auth endpoints requires examining both the authentication mechanism and the file processing logic. The key insight is that Basic Auth endpoints accepting file uploads need special scrutiny because they combine authenticated access with potentially dangerous file operations.
Static analysis patterns for Basic Auth Zip Slip:
// Look for Basic Auth middleware + file operations
const authMiddleware = require('express-basic-auth');
const multer = require('multer');
// Vulnerable pattern:
app.post('/upload', authMiddleware, multer().any(), (req, res) => {
const file = req.files[0];
// No path validation before extraction
extract(file.path, targetDir);
});
// Safer pattern with validation:
app.post('/upload', authMiddleware, multer().any(), (req, res) => {
const file = req.files[0];
const safeExtract = validateAndExtract(file.path, targetDir);
if (!safeExtract) return res.status(400).send('Invalid file');
});
Dynamic scanning with middleBrick specifically targets Basic Auth endpoints by:
- Identifying endpoints protected by Basic Auth through authentication header analysis
- Testing file upload functionality on authenticated endpoints
- Submitting crafted ZIP archives with traversal payloads
- Verifying whether extracted files appear outside intended directories
middleBrick's Basic Auth-specific checks include:
| Check Type | Description | Detection Method |
|---|---|---|
| Authentication Bypass via Traversal | Tests if traversal payloads can bypass Basic Auth protections | Attempts to access protected resources using crafted paths |
| Archive Path Validation | Verifies proper validation of archive entry paths | Submits ZIP with ../../etc/passwd entries |
| Extraction Boundary Enforcement | Checks if extraction respects directory boundaries | Attempts extraction outside target directory | Symbolic Link Following | Tests if symlinks in archives are properly handled | Creates ZIP with symlinks pointing outside target |
Manual testing methodology for Basic Auth Zip Slip:
# 1. Identify Basic Auth endpoint
curl -X POST https://api.example.com/upload \
-H "Authorization: Basic $(echo -n 'user:pass' | base64)" \
-F "file=@malicious.zip"
# 2. Craft malicious ZIP
# Contains: ../../../../../var/www/html/shell.php
zip -r malicious.zip shell.php
# 3. Verify extraction location
# Check if shell.php appears in unintended locations
curl https://api.example.com/uploads/../../../../var/www/html/shell.php
Key detection indicators:
- Basic Auth middleware protecting file upload endpoints
- Direct use of
unzip,extract, or similar extraction functions without path validation - Dynamic path construction using user-controlled data (user ID, filename, etc.)
- Missing canonicalization of extraction paths
Basic Auth-Specific Remediation
Remediating Zip Slip in Basic Auth contexts requires addressing both the authentication layer and the file processing logic. The solution involves validating archive contents before extraction and ensuring Basic Auth doesn't provide unnecessary privileges.
Core remediation patterns:
// Node.js with proper validation
const basicAuth = require('express-basic-auth');
const multer = require('multer');
const unzipper = require('unzipper');
const upload = multer({
limits: { fileSize: 1024 * 1024 }, // 1MB max
fileFilter: (req, file, cb) => {
if (file.mimetype !== 'application/zip') {
return cb(new Error('Only ZIP files allowed'), false);
}
cb(null, true);
}
});
app.post('/upload', basicAuth({
users: process.env.API_USERS
}), upload.single('file'), async (req, res) => {
try {
const archive = await unzipper.Open.file(req.file.path);
// Validate all entries before extraction
for (const entry of archive.files) {
if (entry.type === 'Directory') continue;
const sanitizedPath = path.normalize(entry.path);
if (sanitizedPath.startsWith('..')) {
throw new Error('Archive contains path traversal');
}
}
// Extract to safe location
const extractPath = path.join(__dirname, 'uploads', req.auth.user);
await archive.extract({ path: extractPath });
res.send('Upload successful');
} catch (err) {
console.error('Upload error:', err);
res.status(400).send('Invalid file');
}
});
Python/Django implementation:
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse
import zipfile
import os
@login_required
def upload_view(request):
if request.method != 'POST':
return JsonResponse({'error': 'POST required'}, status=400)
uploaded_file = request.FILES['file']
if not uploaded_file.name.endswith('.zip'):
return JsonResponse({'error': 'ZIP required'}, status=400)
try:
with zipfile.ZipFile(uploaded_file) as archive:
# Validate all entries
for entry in archive.namelist():
if '..' in entry or entry.startswith('/'):
return JsonResponse({'error': 'Invalid path'}, status=400)
# Extract to user-specific directory
extract_dir = os.path.join('/var/uploads', request.user.username)
archive.extractall(extract_dir)
return JsonResponse({'message': 'Upload successful'})
except zipfile.BadZipFile:
return JsonResponse({'error': 'Invalid ZIP'}, status=400)
except Exception as e:
return JsonResponse({'error': str(e)}, status=500)
Spring Boot Java implementation:
@RestController
@RequestMapping("/api")
public class UploadController {
@PostMapping("/upload")
public ResponseEntity<Map<String, String>> upload(
@RequestParam("file") MultipartFile file,
Principal principal) {
if (!file.getContentType().equals("application/zip")) {
return ResponseEntity.badRequest()
.body(Map.of("error", "ZIP files only"));
}
try {
// Validate archive contents
ZipInputStream zipStream = new ZipInputStream(
new ByteArrayInputStream(file.getBytes()));
ZipEntry entry;
while ((entry = zipStream.getNextEntry()) != null) {
String entryName = entry.getName();
if (entryName.contains("..") || entryName.startsWith("/")) {
return ResponseEntity.badRequest()
.body(Map.of("error", "Invalid path"));
}
}
// Extract to safe location
String username = principal.getName();
String extractPath = "/var/uploads/" + username;
// Use Java's built-in extraction with validation
try (ZipInputStream zis = new ZipInputStream(
new ByteArrayInputStream(file.getBytes()))) {
while ((entry = zis.getNextEntry()) != null) {
String targetPath = extractPath + File.separator + entry.getName();
File targetFile = new File(targetPath);
// Verify path is within allowed directory
if (!targetFile.getCanonicalPath()
.startsWith(new File(extractPath).getCanonicalPath())) {
throw new IOException("Path traversal detected");
}
if (entry.isDirectory()) {
targetFile.mkdirs();
} else {
targetFile.getParentFile().mkdirs();
try (FileOutputStream fos = new FileOutputStream(targetFile)) {
byte[] buffer = new byte[1024];
int len;
while ((len = zis.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
}
}
}
}
return ResponseEntity.ok(Map.of("message", "Upload successful"));
} catch (IOException e) {
return ResponseEntity.status(500)
.body(Map.of("error", "Upload failed: " + e.getMessage()));
}
}
}
Additional security controls for Basic Auth Zip Slip:
- Content-Type validation: Only accept
application/zipor specific archive types - File size limits: Prevent large archive attacks with reasonable size caps
- Entry count limits: Restrict number of files in a single archive
- Allowed file types: Whitelist specific extensions (e.g., only .txt, .json, .csv)
- Extraction sandboxing: Use chroot or containerization for extraction processes
- Logging and monitoring: Log all archive uploads and extraction activities
middleBrick's remediation guidance for Basic Auth Zip Slip includes:
- Remove Basic Auth from file upload endpoints if authentication isn't strictly necessary
- Implement archive validation before extraction
- Use libraries with built-in path traversal protection
- Add content-type and size restrictions
- Implement rate limiting on upload endpoints
- Monitor for unusual upload patterns
Testing your remediation:
# Test with malicious ZIP
zip -r test.zip --symlink "../../../../etc/passwd" "../../etc/shadow"
# Verify your endpoint rejects it
curl -X POST https://api.example.com/upload \
-H "Authorization: Basic $(echo -n 'user:pass' | base64)" \
-F "file=@test.zip"
# Should return 400/403, not extract files
Frequently Asked Questions
How does Basic Auth make Zip Slip vulnerabilities more dangerous?
Basic Auth makes Zip Slip more dangerous because it provides authenticated access to the vulnerable endpoint. An attacker needs valid credentials to exploit the vulnerability, but once authenticated, they can execute path traversal attacks that might otherwise be blocked by unauthenticated access controls. This combination of authenticated access and insufficient file validation creates a high-severity vulnerability where legitimate users can be exploited to compromise the system.
Can middleBrick detect Zip Slip in Basic Auth endpoints without credentials?
middleBrick can detect the presence of Basic Auth-protected file upload endpoints through metadata analysis and public API documentation. However, full Zip Slip testing requires authentication to access the actual upload functionality. The scanner will identify endpoints that are likely vulnerable based on their configuration and authentication requirements, then flag them for manual testing with proper credentials. middleBrick's analysis includes checking for Basic Auth middleware usage patterns and file processing logic that could indicate Zip Slip vulnerabilities.