Securing File Uploads in Go: Defending Against 7 Common Attacks
In systems without traditional authentication, file uploads require robust request source identification. For a browser extension that converts PDF to Markdown, an ECDSA P-256-based model using the WebCrypto API is implemented. On first launch, a non-exportable key pair is generated:
const keyPair = await crypto.subtle.generateKey(
{ name: 'ECDSA', namedCurve: 'P-256' },
false, // extractable = false
['sign', 'verify']
);
The public key is registered on the server, returning a device_id and device_token. Every API request includes:
Authorization: Bearer <token>X-Timestamp(±5 minutes)X-Nonce(anti-replay)X-Body-SHA256X-Signature(ECDSA signature over METHOD+PATH+TIMESTAMP+NONCE+BODY_HASH)
The server verifies the signature using the public key, ensuring:
- Rate limiting per device
- Protection against replay attacks
- Slot control (max 3 active tasks)
- Registration limits (5/IP/hour)
This model increases attack cost—emulation requires full ECDSA signature implementation.
Attack 1: File Type Spoofing
An attacker disguises an executable as a PDF. Defense is layered across three levels.
Frontend: MIME type and extension validation:
const ALLOWED_TYPES = ['application/pdf'];
const ALLOWED_EXTENSIONS = ['.pdf'];
function validateFile(file) {
const isPdf = ALLOWED_TYPES.includes(file.type) ||
ALLOWED_EXTENSIONS.some(ext =>
file.name.toLowerCase().endsWith(ext));
if (!isPdf) return { valid: false, error: 'PDF only' };
}
Backend: Route based on Content-Type. Critical: Magic bytes:
pdfMagicBytes := "%PDF"
header4 := make([]byte, 4)
_, err := file.Read(header4)
if string(header4) != pdfMagicBytes {
respondError(w, http.StatusBadRequest,
models.ErrCodeValidationError,
"File is not a valid PDF")
}
file.Seek(0, 0)
Checking the first bytes bypasses headers and extensions. Polyglot files (valid PDFs with malicious content) require additional signatures.
Attack 2: Disk Exhaustion (DoS)
Denial-of-service via large files. Mitigation includes:
http.MaxBytesReader(w, r.Body, MaxFileSize + 1MB)— halts reading at limit.- Per-device slots: max 3 active tasks (
queued,processing,ready,error). - Rate limits on registration and requests.
Device identification renders IP or token rotation ineffective.
Attack 3: Path Traversal
An attacker uses ../../../etc/passwd. Solution: Fixed path {UUID}/input.pdf. User input in paths is completely ignored.
Frontend sanitization (additional layer):
function sanitizeFileName(name) {
return name
.replace(/[/\\?%*:|"<>]/g, '-')
.replace(/^\.+/, '')
.replace(/\.$/, '')
.substring(0, 255)
.trim();
}
Backend generates the path independently of the filename.
Attack 4: SSRF via URL Upload
For the "upload from URL" feature, the following are blocked:
- DNS resolution before download
- Redirects
- Private IPs (denylisted: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
Attack 5: Replay Attacks
X-Nonce + X-Timestamp + signature. Nonce is stored per device; timestamp is validated within ±5 minutes.
Attack 6: Device Impersonation
extractable: false ensures the private key cannot be exported. Stealing device_token is useless without valid signatures.
Attack 7: Application-Level Abuse
Rate limits + slot controls + cryptographic signing. Max 3 slots, 5 registrations/IP/hour.
Key Takeaways
- Cryptographic device-level identification replaces accounts for rate limiting.
- Magic bytes (
%PDF) are the only reliable way to verify file type. MaxBytesReader+ slot management prevent DoS.- Fixed paths eliminate traversal risks.
- ECDSA P-256 with nonce/timestamp blocks replay and impersonation.
— Editorial Team
No comments yet.