
Rock Paper Scissors DApp es una implementación totalmente descentralizada del clásico juego “Piedra, Papel o Tijera” sobre la red de Ethereum. El protocolo permite que los jugadores compitan de forma justa y transparente, realizando apuestas en ETH o mediante el uso de tokens especiales llamados Winner Tokens.
Características clave
Mecanismo de commit-reveal que garantiza un juego limpio (sin trampas).
Soporte tanto para partidas con apuestas en ETH como con tokens.
Juegos de múltiples rondas con sistema de puntuación tipo “mejor de N”.
Distribución automática de premios y emisión de Winner Tokens al ganador.
Protección contra jugadores inactivos mediante sistemas de tiempo límite.
El sistema de contratos inteligentes utiliza el patrón commit-reveal para evitar ataques de frontrunning y asegurar que ningún jugador pueda ver la jugada del oponente antes de enviar la suya.
Detalles del Contrato
Flujo del Juego
El jugador A crea una partida con una apuesta en ETH o token.
El jugador B se une con una apuesta equivalente.
Ambos jugadores envían sus movimientos en forma de hash.
Posteriormente, ambos revelan sus movimientos.
Se determina el ganador de la ronda actual.
Se repiten los pasos 3 a 5 hasta completar todas las rondas.
El ganador final recibe el premio y un Winner Token.
Timeouts
Timeout para unirse: 24 horas por defecto.
Timeout para revelar movimiento: Definido al crear la partida (mínimo 5 minutos).
Comisiones
10% de comisión del protocolo en juegos con ETH.
Sin comisiones en juegos únicamente con tokens.
Actores
Jugadores: Usuarios que crean o se unen a partidas, envían y revelan sus movimientos, y compiten en los enfrentamientos.
Administrador: Responsable de actualizar parámetros de tiempo y retirar las comisiones acumuladas.
Propietario del contrato: Inicialmente el desplegador del contrato, con facultad para asignar un nuevo administrador.
Tabla de vulnerabilidades validas
Criticidad | Vulnerabilidad |
Alta | Denial of Service via ETH Transfer Revert |
Alta | Invalid TimeoutReveal When Only One Commit Exists |
Media | Predictable Commit Due to Salt Reuse (Lack of Fresh Entropy) |
Media | Quality: Hardcoded Values and No Game Cancellation by Player B |
Vulnerabilidades Altas
Denial of Service via ETH Transfer Revert
Descripción
El contrato RockPaperScissors.sol intenta transferir ETH al ganador de una partida utilizando una llamada de bajo nivel (low-level call). Si el _winner es un contrato que revierte al recibir ETH (por ejemplo, mediante una función receive() que haga revert), la transacción fallará y toda la lógica se revertirá por completo.
Esto permite que un jugador malicioso gane una partida y luego bloquee su resolución por completo al rechazar el pago, haciendo que el juego quede atascado en un estado pendiente de forma indefinida. Esto constituye un vector de Denegación de Servicio (DoS).
Entonces
Si el _winner es un contrato atacante, su función receive() puede hacer lo siguiente:
Esto da como resultado
call{value:} falla
success == false
require(success, ...) revierte toda la ejecución
El juego no se marca como finalizado
Los fondos quedan bloqueados
Nadie puede continuar
Impacto
El juego no puede marcarse como terminado.
No se puede retirar el premio.
Los fondos permanecen bloqueados en el contrato.
El ataque de Denegación de Servicio (DoS) es totalmente reproducible y evita el flujo normal del juego.
El administrador no tiene ningún mecanismo para forzar la finalización.
La lógica crítica del juego está estrechamente acoplada a la ejecución de código externo.
Prueba de Concepto
Se ha creado un contrato de prueba llamado RPS_DoS.t.sol como prueba de concepto.
Para ejecutar la prueba, simplemente corre:
Test/Exploit
Output (-vvvv)
Recomendaciones para mitigación
Utiliza el patrón pull-payment para evitar transferencias de ETH durante la ejecución de la lógica del juego:
Esto garantiza que, incluso si un jugador se niega a aceptar el ETH, la lógica del juego continúe normalmente y los fondos aún puedan ser retirados manualmente.
Invalid TimeoutReveal When Only One Commit Exists
Descripción
La función timeoutReveal() está diseñada para permitir la cancelación de una partida cuando un jugador no revela su jugada durante la fase de revelación. Sin embargo, el contrato permite llamar a timeoutReveal() incluso cuando solo un jugador ha enviado su jugada comprometida, lo cual viola el flujo lógico del mecanismo commit-reveal.
Esto representa una vulnerabilidad lógica: la fase de revelación no debería estar activa a menos que ambos jugadores hayan enviado sus compromisos.
Impacto
Un jugador puede cancelar prematuramente la partida y recuperar sus fondos.
Puede ser utilizado para abusar del emparejamiento (por ejemplo, salirse del juego tras ver al oponente).
Puede llevar a una resolución inesperada del juego sin que ocurra una fase de revelación real.
Prueba de Concepto
Output (-vvvv)
La traza muestra que el juego fue cancelado y ambos jugadores fueron reembolsados, a pesar de que solo uno de ellos había enviado su compromiso.
Esto confirmó el problema: timeoutReveal() se ejecutó con éxito a pesar de que revealDeadline nunca fue inicializado. La prueba falló porque esperaba un revert ("Cannot timeout yet"), pero la llamada se completó correctamente.
Esto revela una condición lógica defectuosa que activa incorrectamente la ruta de cancelación.
Recomendaciones para mitigación
Agrega una verificación en timeoutReveal() para asegurarte de que ambos jugadores hayan enviado sus compromisos antes de aplicar la lógica de tiempo de espera.
Vulnerabilidades Medias
Predictable Commit Due to Salt Reuse (Lack of Fresh Entropy)
Descripción
El mecanismo commitMove se basa en keccak256(abi.encodePacked(move, salt)) para ocultar la jugada de un jugador hasta la fase de revelación. Sin embargo, el contrato no impone ni valida que el salt sea único por juego o por turno. Si un jugador reutiliza un salt que ya fue revelado en una partida anterior, un atacante puede reconstruir el commit fuera de la cadena y predecir la jugada antes de que sea revelada.
Impacto
Un oponente malicioso puede detectar commits repetidos comparándolos con jugadas y salts previamente revelados de forma pública.
Esto rompe la confidencialidad y equidad del esquema de commit-reveal.
Permite ataques de front-running y da una ventaja estratégica injusta a un jugador que monitorea la cadena.
Prueba de Concepto
Esto demuestra que cualquier combinación de salt y move previamente revelada puede ser utilizada para predecir jugadas futuras si se reutiliza.
Output (-vvvv)
Recomendaciones para mitigación
Obligar a que cada commit sea único por juego y por turno, utilizando un mapping(bytes32 => bool) usedCommits.
Rechazar los hashes de commit reutilizados.
Se recomienda que el frontend genere salts con alta entropía (por ejemplo: keccak256(abi.encodePacked(msg.sender, block.timestamp, nonce))).
Esto garantiza que, incluso si un jugador reutiliza accidentalmente su salt, la lógica del contrato lo impedirá y se mantendrá la equidad del juego.
Quality: Hardcoded Values and No Game Cancellation by Player B
Descripción
Actualmente, solo playerA tiene permitido cancelar una partida si nadie se ha unido aún (cancelGame). Sin embargo, si playerB se une y playerA nunca continúa, playerB queda atrapado sin ninguna opción para recuperar su participación.
Impacto
Mala experiencia de usuario
Las partidas pueden quedar atascadas indefinidamente para playerB
Frustración para los usuarios en juegos estancados o abandonados
Recomendaciones para mitigación
Permitir que ambos jugadores puedan cancelar la partida si no hay progreso después de un tiempo razonable (por ejemplo, si no se envían commits dentro de un período determinado).


Entradas relacionadas
HackSyndicate
Home
About
Contact
© 2025 HackSyndicate. Todos los derechos reservados.


