Proteger las subidas de archivos en Go: Defensa contra 7 ataques comunes
En sistemas sin autenticación tradicional, las subidas de archivos requieren una identificación robusta de la fuente de la solicitud. Para una extensión del navegador que convierte PDF a Markdown, se implementa un modelo basado en ECDSA P-256 usando la API WebCrypto. En el primer lanzamiento, se genera un par de claves no exportables:
const keyPair = await crypto.subtle.generateKey(
{ name: 'ECDSA', namedCurve: 'P-256' },
false, // extractable = false
['sign', 'verify']
);
La clave pública se registra en el servidor, devolviendo un device_id y un device_token. Cada solicitud a la API incluye:
Authorization: Bearer <token>X-Timestamp(±5 minutos)X-Nonce(anti-replay)X-Body-SHA256X-Signature(firma ECDSA sobre METHOD+PATH+TIMESTAMP+NONCE+BODY_HASH)
El servidor verifica la firma usando la clave pública, asegurando:
- Límites de tasa por dispositivo
- Protección contra ataques de repetición
- Control de slots (máximo 3 tareas activas)
- Límites de registro (5/IP/hora)
Este modelo aumenta el costo del ataque: emularlo requiere implementar completamente la firma ECDSA.
Ataque 1: Spoofing de tipo de archivo
Un atacante disfraza un ejecutable como un PDF. La defensa es múltiple, con tres niveles de protección.
Frontend: Validación de tipo MIME y extensión:
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: 'Solo PDF' };
}
Backend: Ruta basada en Content-Type. Crucial: Bytes mágicos:
pdfMagicBytes := "%PDF"
header4 := make([]byte, 4)
_, err := file.Read(header4)
if string(header4) != pdfMagicBytes {
respondError(w, http.StatusBadRequest,
models.ErrCodeValidationError,
"El archivo no es un PDF válido")
}
file.Seek(0, 0)
Verificar los primeros bytes evita manipulaciones en encabezados o extensiones. Los archivos poliglotos (PDF válidos con contenido malicioso) requieren firmas adicionales.
Ataque 2: Agotamiento de disco (DoS)
Denegación de servicio mediante archivos grandes. Las medidas de mitigación incluyen:
http.MaxBytesReader(w, r.Body, MaxFileSize + 1MB)— detiene la lectura al alcanzar el límite.- Slots por dispositivo: máximo 3 tareas activas (
queued,processing,ready,error). - Límites de tasa en registro y solicitudes.
La identificación por dispositivo hace inefectivas rotaciones de IP o token.
Ataque 3: Traversía de rutas
Un atacante usa ../../../etc/passwd. Solución: Ruta fija {UUID}/input.pdf. La entrada del usuario en rutas se ignora completamente.
Sanitización en frontend (capa adicional):
function sanitizeFileName(name) {
return name
.replace(/[/\\?%*:|"<>]/g, '-')
.replace(/^\.+/, '')
.replace(/\.$/, '')
.substring(0, 255)
.trim();
}
El backend genera la ruta independientemente del nombre del archivo.
Ataque 4: SSRF mediante carga desde URL
Para la función "cargar desde URL", se bloquean:
- Resolución DNS antes de la descarga
- Redirecciones
- IPs privadas (denegadas: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)
Ataque 5: Repetición de ataques
X-Nonce + X-Timestamp + firma. El nonce se almacena por dispositivo; el timestamp se valida dentro de ±5 minutos.
Ataque 6: Impersonación de dispositivo
extractable: false garantiza que la clave privada no pueda exportarse. Robar device_token es inútil sin firmas válidas.
Ataque 7: Abuso a nivel de aplicación
Límites de tasa + control de slots + firma criptográfica. Máximo 3 slots, 5 registros/IP/hora.
Conclusiones clave
- La identificación criptográfica por dispositivo reemplaza las cuentas para límites de tasa.
- Los bytes mágicos (
%PDF) son la única forma confiable de verificar el tipo de archivo. MaxBytesReader+ gestión de slots previenen DoS.- Rutas fijas eliminan riesgos de traversía.
- ECDSA P-256 con nonce/timestamp bloquea repeticiones e impersonación.
— Editorial Team
Aún no hay comentarios.