Datos del reto
| Campo | Valor |
|---|---|
| CTF | Nullcon CTF 2026 |
| Reto | Pasty |
| Categoría | Web / Crypto |
| Flag | ENO{cr3at1v3_cr7pt0_c0nstruct5_cr4sh_c4rd5} |
Descripción del reto
El servicio era un pastebin que protegía cada paste con una firma "casera". La interfaz solo dejaba ver un paste si id y sig coincidían, así que todo dependía de entender cómo estaba implementada esa firma y por qué se podía falsificar.
Reconocimiento
Al crear un paste, el servidor devolvía una URL de este estilo:
view.php?id=<HEX_ID>&sig=<HEX_SIG>
Eso nos daba exactamente lo que necesitábamos para analizar el esquema: muchos pares válidos (id, sig). La lógica estaba en sig.php, y ahí se veía que la firma no usaba un estándar como HMAC sino una construcción propia a partir de SHA256.
Análisis
El algoritmo derivaba solo 24 bytes efectivos de la clave:
m0,m1ym2, tres bloques de 8 bytes sacados deSHA256(k).
Luego, para cada bloque de la firma, elegía uno de esos subbloques según h[8*i] % 3 y lo mezclaba con el hash del mensaje usando XOR y encadenamiento.
El problema era que, si conocíamos el mensaje firmado y la firma resultante, podíamos sacar cuál era el subbloque usado en cada posición:
c0 = b0 XOR o0ci = bi XOR oi XOR o(i-1)parai > 0
Como al crear pastes el mensaje (id) era conocido, también lo era SHA256(id). Reuniendo suficientes firmas válidas, se terminaban recuperando m0, m1 y m2 completos.
Resolución
La estrategia fue:
- Crear muchos pastes y guardar cada par
(id, sig). - Calcular
SHA256(id)para cada uno. - Separar
sigen cuatro bloques de 8 bytes. - Despejar qué bloque secreto se usó en cada posición.
- Rellenar
m0,m1ym2hasta tenerlos completos. - Recalcular una firma válida para
id = "flag". - Pedir
view.php?id=flag&sig=<firma_falsificada>.
Una vez recuperados los tres subbloques de clave, el resto era simplemente reproducir el algoritmo del servidor con un id nuevo.
Flag
ENO{cr3at1v3_cr7pt0_c0nstruct5_cr4sh_c4rd5}