Pasty
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 funcionaba 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 daba justo lo que hacía falta para analizar el esquema, porque entregaba muchos pares válidos (id, sig). La lógica estaba en sig.php, y ahí se veía que la firma no usaba algo estándar como HMAC, sino una construcción propia a partir de SHA256.
En ese punto había que reconstruir suficiente material de la clave a partir de firmas legítimas.
Análisis
El algoritmo derivaba solo 24 bytes efectivos de la clave.
m0,m1ym2- tres bloques de 8 bytes sacados de
SHA256(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 se conocía el mensaje firmado y la firma resultante, se podía despejar 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 firmado id era conocido, también lo era SHA256(id). Reuniendo suficientes firmas válidas, se terminaban recuperando m0, m1 y m2 completos.
La parte importante aquí fue darse cuenta de que la firma "casera" no estaba ocultando bien el material secreto. Cada nueva firma filtraba un poco más.
Resolución
La estrategia fue esta.
- 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 consistía en reproducir el algoritmo del servidor con un id nuevo. No hacía falta romper SHA256. El problema estaba en la construcción alrededor del hash.
Flag
ENO{cr3at1v3_cr7pt0_c0nstruct5_cr4sh_c4rd5}