Ataque de repetición: cómo un token de autenticación válido puede convertirse en un arma

Ataque de repetición: cómo un token de autenticación válido puede convertirse en un arma

El ataque de repetición es una desagradable honestidad del mundo: el atacante no rompe los algoritmos ni la criptografía, simplemente reenvía su propio mensaje correcto una vez más. Si el sistema solo comprueba la firma y la autenticidad del remitente, pero no se preocupa por la novedad y la unicidad, la repetición resultará convincente. De ahí vienen los cargos duplicados, la reemisión de permisos, los pedidos duplicados y otras «maravillas» que luego requieren tiempo y dinero para solucionar.

En 2025 este tema está especialmente vigente. Trasladamos la lógica a las API, nos comunicamos mediante webhooks, aceleramos las conexiones con 0-RTT en TLS 1.3, confiamos en móviles y dispositivos IoT, y en algunos lugares aún no se ha impuesto la disciplina sobre claves y tokens. Más abajo hay una explicación ordenada de qué se repite exactamente, por qué una firma sola no basta, dónde suele fallar la arquitectura y cómo configurar una protección multinivel sin perjudicar la fiabilidad.

Qué es un ataque de repetición y qué no es

La esencia es sencilla: el atacante graba una petición o respuesta legítima y la envía de nuevo más tarde. El servidor ve una firma conocida, un token válido y ejecuta la acción por segunda vez. No es lo mismo que un «hombre en el medio», donde el atacante interviene activamente en el diálogo. Tampoco es una retransmisión en tiempo real para pasar controles de entorno. En la repetición el recurso principal es el tiempo: se grabó el mensaje, se espera el momento oportuno y se reenvía.

La criptografía responde a «¿quién lo envió?» y «¿se ha alterado el contenido?». Pero guarda silencio ante «¿ya vimos este mensaje y está obsoleto?». De ahí se desprende la conclusión básica: una firma no es suficiente; hacen falta mecanismos de frescura, unicidad y vinculación al contexto.

Cómo de una repetición surge un incidente: el camino del mensaje desde la grabación hasta el duplicado

Primero, el mensaje queda accesible en algún lugar. Las fuentes son banales: registros de depuración, proxies, extensiones del navegador, volcados de memoria con el token, tráfico grabado en tramos sin cifrado, copias de peticiones en sistemas integradores. En el mundo de los dispositivos a menudo falla el hardware básico: no hay reloj ni memoria no volátil, así que tras un reinicio el aparato envía el mismo paquete «autorizador».

Luego el atacante entra en la ventana de validez. Cuando un mensaje firmado vive minutos y no incluye sello temporal ni valor de un solo uso, la repetición se encaja fácilmente en ese intervalo. Incluso si existe una marca temporal, una ventana demasiado amplia y la desincronización de relojes crean la oportunidad de pasar.

Y al final aceptan el duplicado como un reintento normal. En un sistema distribuido los reintentos son normales. Si la protección contra repeticiones no está separada de las mecánicas de fiabilidad, usted mismo legaliza el duplicado: «sí, la red falló, el cliente reintentó». Por eso las comprobaciones defensivas deben ser explícitas e independientes de la lógica de reenvíos.

Dónde ocurren las repeticiones con más frecuencia: escenarios concretos

API de pagos: el mismo «crear pago» dos veces

El cliente envía una petición para crear una transacción. Usted verifica la firma, todo está en orden: el dinero se carga, la entidad se crea. Veinte segundos después llega el mismo flujo de bytes: se grabó en un nodo intermedio. Si la petición no tenía un valor de un solo uso ni marca temporal, y en el servidor no existe memoria de «ya lo vimos», se producirá un segundo cargo. Un error frecuente es suponer que el proveedor de pagos «salvará» la situación. El proveedor protegerá su lado, pero su servicio puede crear una operación duplicada y llegar a consecuencias de negocio.

Cómo arreglarlo sin magia. Introduzca una clave de idempotencia: un identificador único de la operación que viene con la petición. El servidor almacena el resultado por esa clave y ante una consulta repetida devuelve la respuesta anterior sin efectos secundarios. Paralelamente la firma de la petición debe incluir un valor de un solo uso (nonce) y una marca temporal; eso protege contra errores del cliente y cierra la vía del duplicado «limpio» que llegue dentro de la ventana.

Webhooks: «firmado = válido» no funciona

El proveedor de eventos firma las notificaciones, usted verifica la firma y cambia su estado. Si no comprueba la frescura y el identificador único del evento, el paquete grabado puede enviarse otra vez y su manejador volverá a realizar la tarea. La situación se complica porque las repeticiones de webhooks son una mecánica de fiabilidad habitual del proveedor: puede reenviar hasta recibir confirmación. Por tanto, su manejador debe ser idempotente y el registro de «ya vimos este identificador» debe ser rápido y con un plazo de retención razonable.

Una puntualización importante. Firme exactamente el flujo bruto de bytes del cuerpo de la petición. Cualquier «embellecimiento» automático del JSON en capas intermedias rompe la verificación de la firma. Si no puede operar con el flujo bruto, normalice estrictamente los datos a una forma canónica y firme ya esa representación en un formato preciso y determinista.

Tokens y sesiones: el token de portador vive demasiado

El token de acceso, no vinculado a un cliente concreto, es un título al portador. Si lo roban, se usa hasta que expira. Incluso una vida corta no salva si dentro de la ventana no se puede distinguir la primera petición de la segunda. Una solución práctica es la confirmación de posesión de clave: el token se «adhiere» a la clave pública del cliente y cada petición se firma con su clave privada. Un token robado sin la clave es inútil. En el servidor conviene mantener un registro de corta vida de los identificadores de peticiones usados: si detecta un repetido, lo rechaza sin discusiones.

Móviles e IoT: un dispositivo sin reloj es un sueño para la repetición

Los controladores baratos a menudo no pueden guardar la hora ni el estado entre reinicios. Tras una falla envían el mismo paquete «autorizador» que el servidor acepta gustosamente «una vez más». Aquí las ventanas temporales no bastan. Funcionan los contadores monótonos y el estado persistente en el dispositivo: el servidor recuerda el último número de cuadro; el dispositivo no olvida su contador al reiniciar. Cualquier cuadro con número menor o igual al último se descarta. La firma del mensaje con la clave del dispositivo es obligatoria; de lo contrario no es difícil falsificar el contador.

0-RTT en TLS 1.3: aceleración que hay que saber dominar

Los datos tempranos permiten enviar la petición antes de completar el apretón de manos. Por naturaleza, esos datos son reproducibles. La regla básica es simple: las peticiones tempranas o bien son idiomáticamente «seguras» (lecturas, comprobación de estado), o están prohibidas en rutas sensibles. Si no hay restricción de negocio, habrá que llevar un pequeño «diccionario de peticiones tempranas» en una ventana corta y rechazar duplicados. Aun así, es mejor no permitir en 0-RTT lo que cambia dinero o estado.

Por qué una firma sola no basta: tres propiedades que hay que comprobar juntas

Autenticidad e integridad responden por que el mensaje venga de «quien corresponde» y no se haya estropeado en el camino. Frescura demuestra que no está obsoleto: la marca temporal entra en la firma y el servidor acepta solo en una ventana estrecha que tenga en cuenta la desincronización de relojes. Unicidad impide aceptar un segundo mensaje igual: valor de un solo uso (nonce) más un registro corto de «ya visto». Hay un tercer elemento: el contexto: la firma debe cubrir el método, la ruta, cabeceras importantes y el cuerpo. Así, una firma capturada no se puede «trasplantar» a otra petición.

Protección contra la repetición: capas que forman el sistema

En una buena implementación de protección no hay una bala de plata, sino varias capas sencillas que se solapan.

Valores de un solo uso y ventanas temporales

El nonce permite al servidor decir: «esto ya ocurrió». Puede emitirlo por adelantado o aceptar un identificador desde el cliente. En ambos casos el servidor necesita un registro rápido de valores consumidos con un breve periodo de retención. La marca temporal más una ventana estrecha hacen que el paquete grabado sea inútil en cuestión de decenas de segundos. A los sistemas muy cargados les ayuda un filtro ligero en memoria y una ventana deslizante; a los distribuidos, una caché rápida compartida. Lo principal es no intentar compensar la falta de reloj en los dispositivos con ventanas gigantes: eso anula la protección.

Vinculación al cliente y certificados mutuos

Cuando el token se «pega» a la clave pública del cliente, cada petición se firma con su clave privada. El token robado sin la clave es inútil, y con la clave se puede rastrear mediante el certificado. En el nivel de transporte este efecto se refuerza con certificados mutuos: la conexión no se establecerá sin la autenticación del cliente. Pero aquí es importante diseñar el ciclo de vida de las claves: rotación, reemplazo, soportar varios dispositivos, coexistencia temporal de versiones y revocación en caso de compromiso.

Firmar la carga útil y la representación canónica

Hay que firmar no «algo parecido a la petición», sino una representación inequívoca: método, ruta, cabeceras necesarias, cuerpo, nonce y marca temporal. Si en algún punto del trayecto los datos se formatean, deben normalizarse a una forma canónica y firmarse esa versión. Entonces un reintento legítimo «con la misma clave de idempotencia» devolverá la respuesta anterior, y el duplicado deshonesto será o bien caducado o ya «visto» por el servidor.

Idempotencia como seguro frente a errores humanos y de red

Aun con protección ideal, la repetición ocurrirá: la gente se equivoca, las redes titubean. La idempotencia garantiza que una petición redundante no creará efectos secundarios. La clave de idempotencia debe adquirirse de forma atómica para que dos peticiones paralelas no «forcen» la operación a la vez. El almacenamiento debe soportar inserciones rápidas y plazos de limpieza razonables. Al devolver la respuesta anterior por la clave, el sistema se mantiene a la vez fiable y seguro.

Cómo ensamblarlo en sistemas reales: recomendaciones por roles

Internet y API públicas

Firme las peticiones incluyendo método, ruta, cabeceras significativas, cuerpo, nonce y marca temporal. Almacene los nonce usados en memoria con breve periodo de vida. Para operaciones que cambian estado, exija una clave de idempotencia y devuelva la respuesta anterior en caso de repetición. Registre los duplicados de firmas e identificadores: son al mismo tiempo indicador de ataques e indicador de fallos de integración de socios.

Autenticación y autorización

Renuncie a los «tokens al portador» donde sea posible. Vincule los tokens a la clave del cliente y firme cada petición. Mantenga periodos de vida cortos; la renovación mediante un token de actualización de un solo uso. Ante sospecha de compromiso, revoque no solo el acceso sino también la clave pública asociada para cortar la vía de reutilización.

Webhooks e integraciones

Acepte notificaciones de forma rápida: verifique firma, marca temporal e identificador del evento, marque como «visto» y responda «recibido». Mueva el procesamiento a una cola. Un evento con el mismo identificador no debe cambiar el estado por segunda vez. Así convive con las repeticiones del proveedor sin crear un lazo de duplicados.

Móviles e IoT

Active contadores monótonos de mensajes y guárdelos en memoria no volátil del dispositivo. Firme cada mensaje con la clave del dispositivo; en el servidor verifique contador, marca temporal e identificador único. No confíe ciegamente en la hora del dispositivo: permita un pequeño desfase, pero la decisión final la toma el servidor. Para comandos críticos verifique además la vinculación al contexto — identificador de sesión, modo de operación y privilegios.

Pagos y liquidaciones

Cualquier operación monetaria debe ser idempotente o de dos fases. Ate la confirmación a una intención concreta; la repetición de la confirmación debe ser inofensiva. Acepte notificaciones de sistemas de pago con firma, marca temporal e identificador único; almacénelas en un registro corto para que un duplicado no dispare una nueva contabilización.

Observabilidad: cómo detectar intentos de repetición antes de que sea tarde

Aun la arquitectura ideal necesita visibilidad. Configure la recolección de métricas sobre duplicados de firmas e identificadores, sobre activaciones de idempotencia, sobre webhooks repetidos con el mismo identificador. Configure alertas cuando en un breve intervalo llegue un gran número de peticiones idénticas desde distintas direcciones, cuando aumente repentinamente la fracción de «ya procesado», o cuando el mismo token se intente usar desde distintos huellas del cliente. Son señales baratas que ayudan a atrapar el problema antes de que sea un incidente.

Cómo probar la protección contra repetición: un plan corto pero eficaz

Grabe una petición de referencia tal como llega a su servidor. Envíela dos veces: con intervalo mínimo y en el límite de la ventana temporal. Espere que la primera se acepte y la segunda sea rechazada o que devuelva el mismo resultado anterior si la operación es idempotente. Simule el reinicio de un dispositivo móvil y verifique que el contador no ha retrocedido. Para webhooks haga girar el mismo identificador varias veces: el primero debe procesarse, los demás registrarse como duplicados sin efectos secundarios. Compruebe la rotación de claves y la revocación: un token con clave antigua tras la revocación no debe pasar, aunque formalmente siga en periodo de validez.

Antipatrones que vuelven a romper la protección

  • Existe firma, pero falta frescura y unicidad: se «olvida» la marca temporal y el nonce, no hay registro de valores consumidos.
  • Tokens de larga vida sin vinculación al cliente: si los roban, se usan durante semanas.
  • Confianza solo en TLS: 0-RTT activado en todas partes y la aplicación desconoce las repeticiones.
  • Webhooks sin idempotencia: «firmado = se puede contabilizar de nuevo».
  • Se firma la información equivocada: el formato del cuerpo cambia en el trayecto, la verificación falla y los duplicados pasan.
  • Intentar compensar la falta de relojes con ventanas amplias — y así matar la protección.

Preguntas frecuentes sin respuestas de marketing

¿Se puede resolver el problema con un solo producto? No. Es una disciplina arquitectónica: valores de un solo uso y marcas temporales, vinculación al cliente, idempotencia, observabilidad y sentido común con 0-RTT. Los productos ayudan, pero no reemplazan el enfoque.

¿Esto ralentizará mucho el sistema? No, si se hace con sentido. Ventanas estrechas y nonce son milisegundos. La idempotencia implica inserciones rápidas en caché. Al contrario, reducirá los costes de resolver duplicados y cargos disputados.

¿Tenemos que desactivar obligatoriamente 0-RTT? No es obligatorio. Restrínjalo: solo rutas seguras que no cambien estado. Todo lo demás tras un apretón de manos completo.

Conclusión

La reproducción no es un hack ingenioso sino la explotación de nuestras omisiones: verificamos autenticidad e integridad pero olvidamos frescura, unicidad y contexto. Se corrige con mecanismos básicos y comprensibles: nonce y marca temporal en la firma, ventanas estrechas, vinculación del token a la clave del cliente, certificados mutuos cuando procede, idempotencia para operaciones peligrosas, gestión cuidadosa de 0-RTT y observabilidad clara. Junte estos ladrillos y la repetición dejará de ser una amenaza para convertirse en el ruido inofensivo de reintentos de fondo.

Alt text