# Amañamos una Tragamonedas y Retamos al Hacker Summer Camp a Romperla

> En la DEF CON, la Black Hat y la BSides Las Vegas montamos una tragamonedas que nunca paga — a menos que sepas leer la máquina. Cuatro bugs web plantados, un ranking y hasta tres meses de Tandera en juego. Así funcionaba.

La banca siempre gana. Esa era toda la propuesta en la pantalla de nuestro stand durante el Hacker Summer Camp — DEF CON, Black Hat y BSides Las Vegas. Elige un alias — sin correo, sin registro — recibe cinco créditos y tira de la palanca de una tragamonedas "777" en neón. Los rodillos giran, caen en una calavera, y el feed del crupier imprime `paid=false`. Tira otra vez. Calavera. Otra vez. Calavera.

Diez premios por evento, cuatro de ellos reservados para quien reventara el bug más difícil, y un contador en la cabecera bajando en vivo. El correo solo se pedía _después_ de que alguien ganara de verdad.

La máquina está amañada. Te lo avisamos, ahí en el pie de página: _esta máquina está amañada a propósito. Ese es el punto._ El cliente nunca puede ganar. Pero la máquina está _hablando_ con un servidor, y el servidor es descuidado. Lee la máquina, rompe la máquina, y el premio de verdad eran hasta tres meses de Tandera, gratis.

![La pantalla inicial de 777: Jackpot or Jail](/assets/hsc/hero.jpg)

Bajo el neón era un CTF web de cuatro flags, cada una un bug que vemos en la práctica — sobre todo en el rincón poco regulado de los juegos de "casino social" en línea. Aquí va el recorrido, ahora que los códigos ya se reclamaron.

## Flag 1 — el cliente es un mentiroso

Cada giro envía un trozo de telemetría al servidor: `client_outcome`, los rodillos que el navegador cree que mostró. El cliente honesto reporta lo que sacó. El detalle es que el servidor _decide_ el resultado e ignora tu reporte por completo — los rodillos nunca son tuyos para cantarlos.

¿Y si los cantas de todos modos? Abre la pestaña de red, repite el giro con `client_outcome` puesto en un trío ganador, y el servidor responde:

> nice try — the client cannot authorize a payout. the house signs its own outcomes.

![El feed del crupier con el giro manipulado y la captura de la flag 1](/assets/hsc/flag1-client-liar.jpg)

Esa es la flag uno, con 100 puntos de consolación y ningún premio. La lección es la más vieja de la seguridad web: **el cliente está controlado por el atacante.** Un número alarmante de front-ends reales de apuestas decide la victoria en JavaScript y simplemente confía en que el navegador la reporte de vuelta. El nuestro no confía — pero te entrega una flag por demostrar que entiendes por qué importa.

## Flag 2 — XOR no es cifrado

Cuando te sientas, `/api/hsc/start` devuelve un `vault`: un blob base64 que la máquina llama "cifrado". No lo es. Es JSON pasado por un XOR de clave corta repetida — ofuscación disfrazada de candado.

XOR cae con un crib de texto conocido. Sabes que el blob es JSON, así que sabes que empieza con `{"flag":"`. Haz el XOR del texto cifrado contra ese prefijo conocido y la clave repetida sale al instante; aplícala al resto y el vault se lee en texto claro, incluyendo una clave de firma por sesión y, de regalo, la receta para usarla. Envía la clave recuperada y te llevas la flag dos: 250 puntos y un mes de Tandera.

![Enviar el token recuperado del vault se lleva la flag 2](/assets/hsc/flag2-vault-cracked.jpg)

El punto no es que XOR sea débil — es que **un secreto que le entregas al cliente no es un secreto.** Toda clave que llega al navegador se puede recuperar desde el navegador. Ya hemos abierto respuestas de API "cifradas" exactamente así en engagements reales, y el cifrado era exactamente así de delgado.

## Flag 3 — el pago doble

Hay un botón de "reclamar bono" que vale cinco créditos gratis, una vez por sesión. El servidor comprueba si ya reclamaste y entonces concede los créditos. Léelo otra vez despacio: _comprueba_ y luego _actúa_, y no hay nada atómico entre ambas cosas.

Ese hueco es un TOCTOU — time-of-check to time-of-use. Haz clic en el botón y recibes tus cinco créditos como buen ciudadano. Un doble clic perezoso no basta; dispara una ráfaga de verdad _a la vez_ y varias de ellas leen "aún no reclamado" antes de que cualquiera termine de escribir "reclamado". El bono se paga una y otra vez. El servidor nota que pagó varias veces y te entrega la flag tres — 500 puntos, dos meses.

![La ráfaga dispensa el bono varias veces y entrega la flag 3](/assets/hsc/flag3-race-won.jpg)

Esta es la carrera clásica detrás de todo incidente de "retiré mi saldo dos veces": una guarda correcta en aislamiento e inútil bajo concurrencia. Un servidor de dev serializado lo esconde por completo; producción, con workers realmente paralelos golpeando una sola fila compartida, no.

## Flag 4 — probablemente injusto

El gran premio se esconde detrás de la función de la que la máquina más se enorgullece: es _provably fair_ (demostrablemente justa). Cuando te sientas, `/start` se compromete con una server seed publicando su hash. El resultado de cada giro es `v = int(sha256(round_seed : client_seed : nonce)[:8], 16)`, y un giro es jackpot cuando `v % 20000 == 7770` — alrededor de uno entre veinte mil, así que nunca comprarás tu camino hasta ahí con cinco créditos. Cada giro incluso te devuelve su propio `round_seed` para que verifiques que la máquina no te hizo trampa.

Esa transparencia es el bug. Las round seeds son una cadena _hacia adelante_ — `round_seed[n+1] = sha256(round_seed[n])` — y la máquina revela cada una a medida que juegas. Revela una sola seed y puedes avanzar el hash para calcular cada ronda futura: cada seed, cada resultado, hasta donde quieras mirar. Así que giras una vez, tomas la seed revelada y mueles sha256 hacia adelante hasta encontrar el nonce cuyo resultado da el jackpot. Entonces lo reclamas — `POST /api/hsc/jackpot {nonce, seed}` — nombrando la ronda exacta en la que la máquina pagará, y adjuntando la seed de esa ronda para probar que puedes calcularla. Adivina una seed equivocada y el reclamo es rechazado; no hay nada que forzar. Cantaste el jackpot antes de que ocurriera. Esa es la flag cuatro — 1000 puntos y los tres meses completos.

Los casinos _provably fair_ de verdad se comprometen con la seed y la revelan _después_, en la dirección segura. Equivoca la dirección y la prueba de justicia se convierte en una bola de cristal.

![Cantar la ronda del jackpot antes de que ocurra se lleva la flag 4](/assets/hsc/flag4-jackpot-chain.jpg)

![Las cuatro flags capturadas, con el premio listo para reclamar](/assets/hsc/machine.jpg)

![El ranking High Rollers](/assets/hsc/leaderboard.jpg)

## ¿Por qué un casino?

Porque el código de casino es donde estos bugs sostienen el negocio y, demasiadas veces, están en producción. Resultados decididos en el cliente, "cifrado" casero, actualizaciones de saldo no atómicas y esquemas provably-fair que filtran el futuro no son exóticos — son los hallazgos del día a día de las pruebas de aplicaciones web, y el dinero los vuelve caros. Envolverlos en una tragamonedas solo hizo el riesgo legible.

Y para eso existe Tandera. Construimos la plataforma que los equipos de seguridad ofensiva usan para encontrar exactamente estos problemas y convertirlos en informes limpios, estandarizados y white-label — desde el descubrimiento de activos hasta los hallazgos y la entrega. La tragamonedas era un juguete. La metodología detrás de encontrar sus cuatro bugs es el trabajo de verdad.

A todos los que leyeron la máquina en vez de tirar de la palanca: el ranking era vuestro, y los meses también. Nos vemos en el próximo camp.

---

Canonical: https://tandera.io/es/blog/777-jackpot-or-jail
This page as markdown: https://tandera.io/es/blog/777-jackpot-or-jail.md
Index for agents: https://tandera.io/llms.txt

Every page here is also available as markdown: append `.md` to the path (e.g. `/recon.md`, `/index.md` for this homepage, `/blog/<slug>.md`), or request the canonical path with `Accept: text/markdown`.
