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 := `
Zip Slip Vulnerability Demo
Zip Slip Vulnerability Demo
WARNING: This is a vulnerable application!
This server demonstrates the Zip Slip vulnerability (CVE-2019-10743) using the vulnerable version of mholt/archiver v3.1.1.
DO NOT use this in production!
How the vulnerability works:
- The server accepts ZIP files via HTTP POST
- It extracts files using the vulnerable mholt/archiver package
- Malicious ZIP files can contain path traversal filenames (e.g., "../../../etc/passwd")
- These paths can escape the intended extraction directory
- This can lead to arbitrary file overwrites and potential code execution
To test the vulnerability:
- Create a ZIP file with a file named "../../../test.txt"
- Upload it using the form below
- Check if the file was extracted outside the intended directory
Current directories:
Upload directory: ./uploads
Extract directory: ./extracted
`
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(`
Extraction Complete
Extraction Complete
File extracted successfully!
Uploaded file: %s
Extracted to: %s
VULNERABILITY DEMONSTRATION
This extraction used the vulnerable mholt/archiver v3.1.1 package.
If the ZIP contained path traversal filenames (like "../../../file.txt"), they could have been extracted outside the intended directory!
Extracted Files:
`, header.Filename, extractDir)
for _, file := range files {
response += fmt.Sprintf(" - %s
\n", file)
}
response += `
Back to upload form
`
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
}