README.md
Rendering markdown...
package main
import (
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
"github.com/gorilla/mux"
"github.com/mholt/archiver"
)
const (
uploadDir = "./uploads"
extractDir = "./extracted"
)
func main() {
// Create directories if they don't exist
os.MkdirAll(uploadDir, 0755)
os.MkdirAll(extractDir, 0755)
r := mux.NewRouter()
// Serve static files (HTML form)
r.HandleFunc("/", serveHomePage).Methods("GET")
// Handle file upload and extraction
r.HandleFunc("/upload", handleUpload).Methods("POST")
fmt.Println("VULNERABLE Zip Slip Demo Server")
fmt.Println("Upload directory:", uploadDir)
fmt.Println("Extract directory:", extractDir)
fmt.Println("Server running on http://localhost:8080")
fmt.Println("This server is vulnerable to Zip Slip attacks!")
fmt.Println("")
log.Fatal(http.ListenAndServe(":8080", r))
}
func serveHomePage(w http.ResponseWriter, r *http.Request) {
html := `
<!DOCTYPE html>
<html>
<head>
<title>Zip Slip Vulnerability Demo</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
.warning { background: #fff3cd; border: 1px solid #ffeaa7; padding: 15px; border-radius: 5px; margin: 20px 0; }
.danger { background: #f8d7da; border: 1px solid #f5c6cb; padding: 15px; border-radius: 5px; margin: 20px 0; }
.info { background: #d1ecf1; border: 1px solid #bee5eb; padding: 15px; border-radius: 5px; margin: 20px 0; }
form { border: 1px solid #ddd; padding: 20px; border-radius: 5px; }
input[type="file"] { margin: 10px 0; }
button { background: #dc3545; color: white; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; }
button:hover { background: #c82333; }
</style>
</head>
<body>
<h1>Zip Slip Vulnerability Demo</h1>
<div class="danger">
<h3>WARNING: This is a vulnerable application!</h3>
<p>This server demonstrates the Zip Slip vulnerability (CVE-2019-10743) using the vulnerable version of mholt/archiver v3.1.1.</p>
<p><strong>DO NOT use this in production!</strong></p>
</div>
<div class="info">
<h3>How the vulnerability works:</h3>
<ul>
<li>The server accepts ZIP files via HTTP POST</li>
<li>It extracts files using the vulnerable mholt/archiver package</li>
<li>Malicious ZIP files can contain path traversal filenames (e.g., "../../../etc/passwd")</li>
<li>These paths can escape the intended extraction directory</li>
<li>This can lead to arbitrary file overwrites and potential code execution</li>
</ul>
</div>
<div class="warning">
<h3>To test the vulnerability:</h3>
<ol>
<li>Create a ZIP file with a file named "../../../test.txt"</li>
<li>Upload it using the form below</li>
<li>Check if the file was extracted outside the intended directory</li>
</ol>
</div>
<form action="/upload" method="post" enctype="multipart/form-data">
<h3>Upload ZIP File:</h3>
<input type="file" name="file" accept=".zip" required>
<br>
<button type="submit">Upload and Extract</button>
</form>
<div class="info">
<h3>Current directories:</h3>
<p><strong>Upload directory:</strong> ./uploads</p>
<p><strong>Extract directory:</strong> ./extracted</p>
</div>
</body>
</html>`
w.Header().Set("Content-Type", "text/html")
w.Write([]byte(html))
}
func handleUpload(w http.ResponseWriter, r *http.Request) {
// Parse multipart form
err := r.ParseMultipartForm(32 << 20) // 32MB max
if err != nil {
http.Error(w, "Failed to parse form: "+err.Error(), http.StatusBadRequest)
return
}
// Get uploaded file
file, header, err := r.FormFile("file")
if err != nil {
http.Error(w, "Failed to get uploaded file: "+err.Error(), http.StatusBadRequest)
return
}
defer file.Close()
// Check if it's a ZIP file
if filepath.Ext(header.Filename) != ".zip" {
http.Error(w, "Only ZIP files are allowed", http.StatusBadRequest)
return
}
// Save uploaded file
uploadPath := filepath.Join(uploadDir, header.Filename)
uploadFile, err := os.Create(uploadPath)
if err != nil {
http.Error(w, "Failed to save uploaded file: "+err.Error(), http.StatusInternalServerError)
return
}
defer uploadFile.Close()
_, err = io.Copy(uploadFile, file)
if err != nil {
http.Error(w, "Failed to save uploaded file: "+err.Error(), http.StatusInternalServerError)
return
}
// VULNERABLE: Extract the ZIP file using the vulnerable archiver package
fmt.Printf("Extracting %s to %s\n", uploadPath, extractDir)
// This is where the vulnerability occurs - the archiver doesn't validate paths
err = archiver.Unarchive(uploadPath, extractDir)
if err != nil {
http.Error(w, "Failed to extract archive: "+err.Error(), http.StatusInternalServerError)
return
}
// List extracted files
files, err := listFiles(extractDir)
if err != nil {
http.Error(w, "Failed to list extracted files: "+err.Error(), http.StatusInternalServerError)
return
}
// Create response
response := fmt.Sprintf(`
<!DOCTYPE html>
<html>
<head>
<title>Extraction Complete</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
.success { background: #d4edda; border: 1px solid #c3e6cb; padding: 15px; border-radius: 5px; margin: 20px 0; }
.warning { background: #fff3cd; border: 1px solid #ffeaa7; padding: 15px; border-radius: 5px; margin: 20px 0; }
.file-list { background: #f8f9fa; border: 1px solid #dee2e6; padding: 15px; border-radius: 5px; margin: 20px 0; }
a { color: #007bff; text-decoration: none; }
a:hover { text-decoration: underline; }
</style>
</head>
<body>
<h1>Extraction Complete</h1>
<div class="success">
<h3>File extracted successfully!</h3>
<p><strong>Uploaded file:</strong> %s</p>
<p><strong>Extracted to:</strong> %s</p>
</div>
<div class="warning">
<h3>VULNERABILITY DEMONSTRATION</h3>
<p>This extraction used the vulnerable mholt/archiver v3.1.1 package.</p>
<p>If the ZIP contained path traversal filenames (like "../../../file.txt"), they could have been extracted outside the intended directory!</p>
</div>
<div class="file-list">
<h3>Extracted Files:</h3>
<ul>
`, header.Filename, extractDir)
for _, file := range files {
response += fmt.Sprintf(" <li>%s</li>\n", file)
}
response += `
</ul>
</div>
<p><a href="/">Back to upload form</a></p>
</body>
</html>`
w.Header().Set("Content-Type", "text/html")
w.Write([]byte(response))
}
func listFiles(dir string) ([]string, error) {
var files []string
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// Skip the root directory itself
if path == dir {
return nil
}
// Get relative path
relPath, err := filepath.Rel(dir, path)
if err != nil {
return err
}
if info.IsDir() {
files = append(files, relPath+"/")
} else {
files = append(files, relPath)
}
return nil
})
return files, err
}