VNX-GO-026 – Missing file type validation on upload
Overview
This rule flags instances where file upload functionality is implemented using Go’s standard library multipart form handling but lacks apparent file type validation. Accepting file uploads without validating the file type can lead to malicious file upload vulnerabilities where attackers upload executable code, scripts, or other dangerous files.
This maps to CWE-434: Unrestricted Upload of File with Dangerous Type.
Severity: High | CWE: CWE-434 – Unrestricted Upload of File with Dangerous Type | OWASP ASVS: V5.2.2 – File Type Verification
Why This Matters
File upload functionality is a common feature in web applications, but it presents significant security risks if not properly implemented. Without file type validation, attackers can upload:
- Malicious scripts (.php, .py, .sh) that can be executed on the server
- Executable files (.exe, .bin) that can compromise the server
- HTML/JavaScript files that can lead to XSS when served to users
- Files with dangerous extensions that can bypass client-side validation
These vulnerabilities can lead to remote code execution, data theft, server compromise, or use of the server as a malware distribution point.
What Gets Flagged
The rule flags file upload handling patterns that use Go’s multipart form parsing without apparent file type validation:
// FLAGGED: File upload without type validation
func uploadHandler(w http.ResponseWriter, r *http.Request) {
if err := r.ParseMultipartForm(10 << 20); err != nil {
http.Error(w, "Unable to parse form", http.StatusBadRequest)
return
}
file, handler, err := r.FormFile("upload")
if err != nil {
http.Error(w, "Invalid file", http.StatusBadRequest)
return
}
defer file.Close()
// File saved without checking its type
dst, err := os.Create(filepath.Join("./uploads", handler.Filename))
if err != nil {
http.Error(w, "Unable to save file", http.StatusInternalServerError)
return
}
defer dst.Close()
io.Copy(dst, file) // File saved as-is without validation
}
// FLAGGED: Using UploadedFile without validation
func uploadHandler(w http.ResponseWriter, r *http.Request) {
uploadedFile, handler, err := r.FormFile("upload")
if err != nil {
http.Error(w, "Invalid file", http.StatusBadRequest)
return
}
defer uploadedFile.Close()
// No file type checking before processing or saving
processUploadedFile(uploadedFile, handler.Filename)
}
Remediation
Validate MIME type: Use Go’s
net/http.DetectContentTypeormimepackage to verify the actual file content:// SAFE: Validate MIME type func uploadHandler(w http.ResponseWriter, r *http.Request) { if err := r.ParseMultipartForm(10 << 20); err != nil { http.Error(w, "Unable to parse form", http.StatusBadRequest) return } file, handler, err := r.FormFile("upload") if err != nil { http.Error(w, "Invalid file", http.StatusBadRequest) return } defer file.Close() // Read first 512 bytes to detect content type buffer := make([]byte, 512) _, err := file.Read(buffer) if err != nil { http.Error(w, "Unable to read file", http.StatusInternalServerError) return } // Reset file pointer for later use file.Seek(0, io.SeekStart) contentType := http.DetectContentType(buffer) allowedTypes := map[string]bool{ "image/jpeg": true, "image/png": true, "application/pdf": true, } if !allowedTypes[contentType] { http.Error(w, "Invalid file type", http.StatusBadRequest) return } // Now safe to save the file dst, err := os.Create(filepath.Join("./uploads", handler.Filename)) // ... rest of upload logic }Validate file extension: Check that the file extension matches expected types:
// SAFE: Validate file extension func isAllowedExtension(filename string) bool { ext := strings.ToLower(filepath.Ext(filename)) allowed := map[string]bool{ ".jpg": true, ".jpeg": true, ".png": true, ".gif": true, ".pdf": true, ".txt": true, } return allowed[ext] } // Then in upload handler: if !isAllowedExtension(handler.Filename) { http.Error(w, "Invalid file extension", http.StatusBadRequest) return }Combine multiple validation approaches: Use both MIME type and extension validation:
// SAFE: Defense in depth - check both content and extension func validateFile(file multipart.File, filename string) error { // Check extension ext := strings.ToLower(filepath.Ext(filename)) if !isAllowedExtension(ext) { return fmt.Errorf("invalid file extension: %s", ext) } // Check content type buffer := make([]byte, 512) if _, err := file.Read(buffer); err != nil { return fmt.Errorf("unable to read file: %v", err) } file.Seek(0, io.SeekStart) // Reset for later use contentType := http.DetectContentType(buffer) if !isAllowedMIMEType(contentType) { return fmt.Errorf("invalid file type: %s", contentType) } // Optional: Additional validation like magic bytes for specific types return nil }Implement file quarantine and scanning: For high-security applications:
- Save uploaded files to a quarantine area outside web root
- Scan with antivirus software
- Validate file content matches expected format (e.g., use image libraries to verify real images)
- Only move to public location after passing all validations