Zip Slip on Digitalocean
How Zip Slip Manifests in Digitalocean
Zip Slip vulnerabilities in Digitalocean environments typically emerge through file upload features, particularly in applications deployed on Digitalocean App Platform or Droplets. The attack exploits path traversal in archive extraction, allowing attackers to write files outside intended directories.
Consider a Node.js application on Digitalocean App Platform handling file uploads:
const express = require('express');
const unzipper = require('unzipper');
const path = require('path');
app.post('/upload', (req, res) => {
const uploadDir = path.join(__dirname, 'uploads');
req.pipe(unzipper.Extract({
path: uploadDir
}))
.on('close', () => res.send('Upload complete'));
This Digitalocean-hosted code is vulnerable. An attacker can craft a ZIP archive containing ../../etc/passwd, which extracts to the parent directory, potentially overwriting critical system files.
In Digitalocean Droplets running Python applications, the vulnerability often appears in Django or Flask apps:
from django.views.decorators.csrf import csrf_exempt
from django.http import JsonResponse
import zipfile
import os
@csrf_exempt
def upload_view(request):
if request.method == 'POST':
zip_file = request.FILES['file']
extract_path = os.path.join('/var/www/app/uploads')
archive.extractall(extract_path)
Digitalocean Spaces (object storage) users face similar risks when processing archives. A common pattern involves extracting archives to temporary directories before uploading to Spaces:
import boto3
import tempfile
import zipfile
from botocore.client import Config
s3 = boto3.client('s3', config=Config(signature_version='s3v4'))
def process_zip_upload(file):
with tempfile.TemporaryDirectory() as tmpdir:
with zipfile.ZipFile(file, 'r') as archive:
archive.extractall(tmpdir)
# Upload contents to Digitalocean Spaces
for filename in files:
local_path = os.path.join(root, filename)
relative_path = os.path.relpath(local_path, tmpdir)
s3.upload_file(local_path, 'my-space', relative_path)The Digitalocean-specific context matters because many applications use Digitalocean's managed services. For instance, a Laravel application on Digitalocean App Platform might use the ZipArchive class without proper validation:
public function upload(Request $request)
{
$zip = new ZipArchive();
if ($zip->open($request->file('archive'))) {
$zip->extractTo($extractPath);
return response()->json(['status' => 'success']);
}
}Digitalocean Kubernetes users encounter Zip Slip when processing configmaps or secrets from untrusted sources. A malicious Helm chart could contain ZIP files designed to overwrite critical Kubernetes manifests.
Digitalocean-Specific Detection
Detecting Zip Slip in Digitalocean environments requires examining both application code and runtime behavior. Start with static analysis of your Digitalocean-hosted applications:
#!/usr/bin/env python3
import os
import zipfile
import fnmatch
def find_zip_extraction_vulnerabilities(root_dir):
vulnerabilities = []
for dirpath, _, filenames in os.walk(root_dir):
for filename in filenames:
if fnmatch.fnmatch(filename, '*.py'):
filepath = os.path.join(dirpath, filename)
with open(filepath, 'r') as f:
content = f.read()
if 'extractall' in content and '..' in content:
vulnerabilities.append({
'file': filepath,
'issue': 'extractall with path traversal'
})
if 'ZipArchive' in content and 'extractTo' in content:
vulnerabilities.append({
'file': filepath,
'issue': 'PHP ZipArchive without validation'
})
return vulnerabilities
issues = find_zip_extraction_vulnerabilities('/var/www/app')
for issue in issues:
print(f"VULNERABILITY: {issue['file']} - {issue['issue']}")For Digitalocean App Platform users, middleBrick provides automated scanning that detects Zip Slip vulnerabilities without requiring code access. The scanner tests archive extraction endpoints by submitting crafted ZIP files containing path traversal payloads.
Runtime detection on Digitalocean Droplets involves monitoring file system activity. Using auditd or similar tools:
auditctl -w /etc -p wa -k critical_files
auditctl -w /var/www -p wa -k web_root
# Monitor for suspicious archive extraction
auditctl -a exit,always -F arch=b64 -S open -F a0=/usr/bin/unzip -k unzip_activityDigitalocean Spaces users should enable Object Storage Access Logs and analyze for unusual file creation patterns. A Zip Slip attack might create files in unexpected locations or with unusual timestamps.
middleBrick's black-box scanning specifically tests for Zip Slip by:
- Submitting ZIP archives with
../../traversal sequences - Checking if extracted files appear outside intended directories
- Verifying that archive contents match expected file structures
- Testing with archives containing absolute paths like
/etc/passwd
The scanner runs 12 parallel security checks, including path traversal testing, and provides severity ratings with remediation guidance specific to your Digitalocean deployment architecture.
Digitalocean-Specific Remediation
Remediating Zip Slip in Digitalocean environments requires both code fixes and infrastructure considerations. For Node.js applications on Digitalocean App Platform:
const path = require('path');
const fs = require('fs');
function safeExtract(zipBuffer, extractDir) {
const safeExtractDir = path.resolve(extractDir);
return new Promise((resolve, reject) => {
const zipStream = unzipper.PassThrough();
zipStream
.on('entry', (entry) => {
const sanitized = path.normalize(entry.path)
.replace(/^(/)/, '');
if (sanitized.startsWith('..')) {
entry.autodrain();
return;
}
const targetPath = path.join(safeExtractDir, sanitized);
if (!targetPath.startsWith(safeExtractDir)) {
entry.autodrain();
return;
}
entry.pipe(fs.createWriteStream(targetPath));
})
.on('error', reject)
.on('finish', resolve);
zipStream.write(zipBuffer);
zipStream.end();
});
}For Python applications on Digitalocean Droplets, use the safer zipfile.ZipFile.extractall() with validation:
import zipfile
import os
from pathlib import Path
def safe_extract_all(zip_file, target_dir):
target_dir = Path(target_dir).resolve()
with zipfile.ZipFile(zip_file, 'r') as archive:
for member in archive.namelist():
# Validate path - reject absolute paths and parent directory references
if member.startswith('/') or '..' in member:
raise ValueError(f'Invalid path in archive: {member}')
# Calculate target path and validate it stays within target_dir
target_path = target_dir / member
if not str(target_path).startswith(str(target_dir)):
raise ValueError(f'Path traversal detected: {member}')
# All paths validated, perform extraction
archive.extractall(target_dir)Digitalocean Kubernetes users should implement admission controllers to prevent malicious archives in Helm charts:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: zip-slip-validator
webhooks:
- name: zip-slip.validation.io
rules:
- operations: ['CREATE']
apiGroups: ['']
apiVersions: ['v1']
resources: ['pods']
webhook:
url: https://validator.example.com/zip-slip
timeoutSeconds: 5
failurePolicy: FailFor Digitalocean Spaces, implement server-side validation before processing archives:
import re
def validate_archive_contents(file_like_object):
# Read first 100KB to check for suspicious patterns
sample = file_like_object.read(102400)
file_like_object.seek(0)
# Check for path traversal patterns
if b'../' in sample or b'..' + os.sep.encode() in sample:
return False, 'Path traversal detected'
# Check for absolute paths
if b'/' in sample[:100]:
return False, 'Absolute paths detected'
return True, 'Valid'Digitalocean App Platform users should configure deployment pipelines to scan for Zip Slip vulnerabilities using middleBrick's GitHub Action before deployment:
name: Security Scan
on: [push, pull_request]
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run middleBrick Scan
uses: middlebrick/middlebrick-action@v1
with:
target_url: ${{ secrets.APP_URL }}
fail_below_score: C
env:
MIDDLEBRICK_API_KEY: ${{ secrets.MIDDLEBRICK_API_KEY }}Implement runtime monitoring on Digitalocean Droplets to detect Zip Slip exploitation attempts:
#!/bin/bash
inotifywait -m /var/www/uploads -e create -e modify | while read path action file; do
if [[ $file == *.zip ]]; then
echo "ZIP file detected: $file"
# Trigger validation before extraction
python3 validate_zip.py "$path/$file"
fi
done