En este writeup resolveremos la máquina "Crypto Failures". Este reto es un excelente ejemplo de por qué "nunca debes implementar tu propia criptografía" (Don't Roll Your Own Crypto).
Veremos cómo una implementación ingenua del algoritmo DES (hoy obsoleto) y una gestión de sesiones insegura nos permiten:
Falsificar nuestra identidad para ser Administradores.
Recuperar la Clave Secreta del servidor byte a byte utilizando un ataque de Oráculo.
Fase 1: Reconocimiento (Enumeration)
Como en cualquier auditoría, comenzamos identificando los servicios expuestos.
Escaneo de Puertos
Lanzamos nmap para buscar puertos abiertos.
nmap -p- --min-rate 5000 -sCV -oN scan.txt <IP_MAQUINA>
nmap -p- --min-rate 5000 -sCV -oN scan.txt <IP_MAQUINA>
El resultado nos muestra el puerto 80 (HTTP) abierto. Al navegar a la web, vemos un mensaje simple indicando que estamos logueados como "guest", pero no hay mucho más que hacer.
Fuzzing de Directorios
Nuestra intuición nos dice que busquemos "restos" o archivos olvidados por los desarrolladores. Usamos gobuster enfocándonos en extensiones de respaldo como .bak, .old o .php.bak.
gobuster dir -u http://<IP_MAQUINA>/ \
-w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt \
-x php,bak,php.bak,txt
gobuster dir -u http://<IP_MAQUINA>/ \
-w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt \
-x php,bak,php.bak,txt
Encontramos index.php.bak. Descargamos este archivo inmediatamente, ya que leer el código fuente es crítico en retos de criptografía.
wget http://<IP_MAQUINA>/index.php.bak
wget http://<IP_MAQUINA>/index.php.bak
Fase 2: Análisis del Código (La Vulnerabilidad)
Al abrir index.php.bak, encontramos la lógica de autenticación. Aquí reside el fallo crítico.
Cómo funciona la Cookie
El servidor genera una cookie llamada secure_cookie concatenando tres valores:
USUARIO : USER_AGENT : CLAVE_SECRETA
Luego, cifra esta cadena usando la función crypt().
// Fragmento del código fuente
function make_secure_cookie($text, $SALT) {
$secure_cookie = "";
// 1. Divide el texto en bloques de 8 caracteres
foreach (str_split($text, 8) as $el) {
// 2. Cifra cada bloque independientemente usando el Salt
$secure_cookie .= cryptstring($el, $SALT);
}
return $secure_cookie;
}
// Fragmento del código fuente
function make_secure_cookie($text, $SALT) {
$secure_cookie = "";
// 1. Divide el texto en bloques de 8 caracteres
foreach (str_split($text, 8) as $el) {
// 2. Cifra cada bloque independientemente usando el Salt
$secure_cookie .= cryptstring($el, $SALT);
}
return $secure_cookie;
}
El Fallo Lógico
El código implementa un cifrado por bloques tipo ECB (Electronic Codebook) de forma manual:
Divide la frase en trozos de 8 letras.
Cifra cada trozo por separado.
Une los resultados.
¿El problema? El servidor verifica la cookie comparando bloque a bloque. Si nosotros controlamos el contenido de un bloque específico (por ejemplo, el primero), podemos generar un hash válido para ese bloque sin conocer la clave secreta que está en los bloques siguientes.
Fase 3: Flag 1 - Falsificación de Identidad (Admin Spoofing)
El servidor construye el primer bloque así:
usuario + : + Inicio del User-Agent
Por defecto somos guest. La cadena empieza por guest:Mo... (si usamos Firefox).
Nuestro objetivo es ser admin. La cadena debe empezar por admin:Mo....
Como conocemos el Salt (los dos primeros caracteres de nuestra cookie actual, ej: 7g) y controlamos el texto plano (admin:...), podemos calcular el hash legítimo.
Script de Falsificación (PHP)
Creamos forge.php. Usamos PHP para asegurarnos de que el algoritmo crypt() sea idéntico al del servidor.
Para que este script funcione, debes modificar la variable $ua con tu propio User-Agent y la variable $salt con los dos primeros caracteres de tu cookie actual. Si usas los valores del ejemplo, el servidor rechazará tu cookie.
<?php
// --- CONFIGURACIÓN (¡EDITAR!) ---
// 1. Pega aquí tu User-Agent EXACTO
$ua = "Mozilla/5.0 (X11; Linux x86_64; rv:102.0)...";
// 2. Pega aquí los 2 primeros caracteres de tu cookie actual
$salt = "5D";
// Construimos el bloque de 8 caracteres
$payload = "admin:" . substr($ua, 0, 2);
echo "Texto Plano: " . $payload . "\n";
echo "Nuevo Hash: " . crypt($payload, $salt) . "\n";
?>
<?php
// --- CONFIGURACIÓN (¡EDITAR!) ---
// 1. Pega aquí tu User-Agent EXACTO
$ua = "Mozilla/5.0 (X11; Linux x86_64; rv:102.0)...";
// 2. Pega aquí los 2 primeros caracteres de tu cookie actual
$salt = "5D";
// Construimos el bloque de 8 caracteres
$payload = "admin:" . substr($ua, 0, 2);
echo "Texto Plano: " . $payload . "\n";
echo "Nuevo Hash: " . crypt($payload, $salt) . "\n";
?>
Ejecución
Corremos el script: php forge.php.
Copiamos los 13 caracteres generados.

En el navegador, editamos la cookie secure_cookie: Reemplazamos solo los primeros 13 caracteres con nuestro nuevo hash.
Cambiamos la cookie user de guest a admin.
Recargamos.

Fase 4: Flag 2 - Extracción de la Clave (Oracle Attack)
El servidor concatena la clave secreta al final: ...:KEY.
Podemos usar el User-Agent como "padding" (relleno) para empujar la clave secreta y alinearla byte a byte con el final de un bloque.
La Teoría del Ataque
Imagina que la clave es SECRET.
Enviamos un User-Agent calculado para que el bloque de 8 caracteres termine justo antes de la clave.
El siguiente bloque contendrá: AAAAAAA + S (la primera letra de la clave).
El servidor nos devuelve el hash de ese bloque.
Fuerza Bruta Local: En nuestro PC, probamos a cifrar AAAAAAA + a, AAAAAAA + b... hasta que nuestro hash coincida con el del servidor. ¡Así descubrimos la S!
Repetimos el proceso desplazando el padding para descubrir la siguiente letra.
El Script Híbrido (Python + OpenSSL)
Dado que versiones modernas de Python (3.13+) eliminaron la librería crypt, creamos un script robusto que utiliza curl para la red y llama a openssl (o PHP) del sistema para la criptografía.
Guarda esto como exploit.py:
import subprocess
import sys
import re
import time
# USO: python3 exploit.py <IP_MAQUINA>
if len(sys.argv) != 2:
print(f"Uso: python3 {sys.argv[0]} <IP_MAQUINA>")
sys.exit(1)
IP = sys.argv[1]
URL = f"http://{IP}/"
CHARSET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{}_!@#$%^&*()-.,?"
def get_cookie_via_curl(user_agent):
try:
cmd = ["curl", "-s", "-I", "-A", user_agent, URL]
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=5)
output = result.stdout.decode('utf-8', errors='ignore') + result.stderr.decode('utf-8', errors='ignore')
match = re.search(r'secure_cookie=([^;\r\n\s]+)', output)
if match:
from urllib.parse import unquote
return unquote(match.group(1))
return None
except: return None
def php_crypt(text, salt):
"""
Usa el motor PHP local para hashear.
"""
try:
safe_text = text.replace("'", "\\'")
safe_salt = salt.replace("'", "\\'")
php_code = f"echo crypt('{safe_text}', '{safe_salt}');"
cmd = ["php", "-r", php_code]
result = subprocess.check_output(cmd, stderr=subprocess.DEVNULL)
return result.decode().strip()
except: return ""
def solve():
print(f"[*] Objetivo: {URL}")
print("[*] Estrategia: Python + PHP")
print("[*] Iniciando ataque...")
known_key = ""
while True:
# 1. Padding Matemático
prefix = "guest:"
current_len = len(prefix) + 1 + len(known_key)
pad_len = ((7 - (current_len % 8)) % 8) + 8
ua_payload = "A" * pad_len
# 2. Obtener Hash Real
cookie = get_cookie_via_curl(ua_payload)
if not cookie: time.sleep(1); continue
salt = cookie[:2]
# 3. Identificar Bloque
total_len = len(prefix) + len(ua_payload) + 1 + len(known_key) + 1
block_index = (total_len - 1) // 8
target_hash = cookie[block_index*13 : (block_index+1)*13]
# 4. Crackear (PHP)
full_known = prefix + ua_payload + ":" + known_key
block_prefix = full_known[-(7):]
for char in CHARSET:
if php_crypt(block_prefix + char, salt) == target_hash:
known_key += char
sys.stdout.write(char)
sys.stdout.flush()
break
if known_key.endswith("}"):
print(f"\n\n[+] FLAG COMPLETA: {known_key}")
break
if __name__ == "__main__":
solve()
import subprocess
import sys
import re
import time
# USO: python3 exploit.py <IP_MAQUINA>
if len(sys.argv) != 2:
print(f"Uso: python3 {sys.argv[0]} <IP_MAQUINA>")
sys.exit(1)
IP = sys.argv[1]
URL = f"http://{IP}/"
CHARSET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{}_!@#$%^&*()-.,?"
def get_cookie_via_curl(user_agent):
try:
cmd = ["curl", "-s", "-I", "-A", user_agent, URL]
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=5)
output = result.stdout.decode('utf-8', errors='ignore') + result.stderr.decode('utf-8', errors='ignore')
match = re.search(r'secure_cookie=([^;\r\n\s]+)', output)
if match:
from urllib.parse import unquote
return unquote(match.group(1))
return None
except: return None
def php_crypt(text, salt):
"""
Usa el motor PHP local para hashear.
"""
try:
safe_text = text.replace("'", "\\'")
safe_salt = salt.replace("'", "\\'")
php_code = f"echo crypt('{safe_text}', '{safe_salt}');"
cmd = ["php", "-r", php_code]
result = subprocess.check_output(cmd, stderr=subprocess.DEVNULL)
return result.decode().strip()
except: return ""
def solve():
print(f"[*] Objetivo: {URL}")
print("[*] Estrategia: Python + PHP")
print("[*] Iniciando ataque...")
known_key = ""
while True:
# 1. Padding Matemático
prefix = "guest:"
current_len = len(prefix) + 1 + len(known_key)
pad_len = ((7 - (current_len % 8)) % 8) + 8
ua_payload = "A" * pad_len
# 2. Obtener Hash Real
cookie = get_cookie_via_curl(ua_payload)
if not cookie: time.sleep(1); continue
salt = cookie[:2]
# 3. Identificar Bloque
total_len = len(prefix) + len(ua_payload) + 1 + len(known_key) + 1
block_index = (total_len - 1) // 8
target_hash = cookie[block_index*13 : (block_index+1)*13]
# 4. Crackear (PHP)
full_known = prefix + ua_payload + ":" + known_key
block_prefix = full_known[-(7):]
for char in CHARSET:
if php_crypt(block_prefix + char, salt) == target_hash:
known_key += char
sys.stdout.write(char)
sys.stdout.flush()
break
if known_key.endswith("}"):
print(f"\n\n[+] FLAG COMPLETA: {known_key}")
break
if __name__ == "__main__":
solve()
Al ejecutarlo, la terminal comienza a revelar la clave letra por letra:
Fase 5: El "Rabbit Hole" (La Trampa Final)
Observamos tres partes:
Una frase con sentido: Traditional_Own_Crypto_is_Always_Surprising!
Un texto provocador: _and_this_hopefully_is_not_easy_to_crack_
Un hash SHA-256: e41d20b5...
El Instinto Fallido:
Lo primero que pensamos es: "Debo crackear ese SHA-256". Usamos hashcat con rockyou.txt, pero falla.
La Solución Real:
Este era un truco clásico de los CTF (un "Red Herring"). El hash y la frase extra eran parte literal de la flag. No había que desencriptar nada más.
La flag correcta es la cadena completa, tal cual salió del script.