Reports
BAJA[TLS Y CRIPTOGRAFÍA]#3670955

CoinMate: la firma HMAC de su API no incluía ni el endpoint ni el cuerpo de la petición

La API REST de CoinMate firmaba las peticiones solo con nonce, clientId y clave pública. Una firma válida no quedaba ligada a ninguna operación concreta y servía para otro endpoint con otros parámetros.

Resumen
Resumen en castellano de un reporte público, no una traducción literal. El código y los comandos se mantienen como en el original.

Resumen

CoinMate, un exchange de criptomonedas, autentica las llamadas privadas de su API REST (https://coinmate.io/api) con una firma HMAC-SHA256. El problema estaba en qué datos entraban en esa firma. Según el reporte, se calculaba así:

HMAC(private_key, nonce + clientId + publicKey)

Ni la ruta del endpoint ni los parámetros enviados en el cuerpo forman parte del cálculo. En la práctica, la firma demuestra quién hace la petición, pero no qué pide. Si alguien consigue una firma válida con un nonce que el servidor aún no ha visto, puede usarla con cualquier endpoint y cualquier conjunto de parámetros, y el servidor la dará por buena.

El investigador lo clasificó como CWE-325 (Missing Required Cryptographic Step) y también citó CWE-345 (verificación insuficiente de la autenticidad de los datos). Calculó un CVSS 3.0 de 8.1, pero el programa dejó la severidad en baja. El reporte se envió el 13 de abril de 2026, se cerró como resuelto y se divulgó el 20 de mayo de 2026.

Pasos de reproducción

Para entender la prueba hay que tener en cuenta un detalle: CoinMate rechaza los nonces repetidos. Si se coge la firma de una petición que ya ha llegado al servidor y se reenvía a otro endpoint, la respuesta es Access denied. No es que la firma detecte el cambio, es que el nonce ya está gastado. En el escenario de ataque descrito, el atacante intercepta la petición original y no la deja llegar, así que el nonce sigue sin usarse.

El investigador adjuntó un script en Python (02_Coinmate_Exploit_E2E_Validator_Script.py) que reproduce el proceso con claves de prueba propias:

  1. Descargar el script, poner en sus variables CLIENT_ID, PUBLIC_KEY y PRIVATE_KEY las claves de API de una cuenta de prueba, instalar la dependencia y ejecutarlo:
pip install requests
python3 02_Coinmate_Exploit_E2E_Validator_Script.py
  1. Comprobación de claves: el script hace una llamada normal a /balances para confirmar que las credenciales funcionan.
  2. Nonce ya usado: reutiliza la firma de esa misma llamada y recibe Access denied, para mostrar que el bloqueo viene del nonce.
  3. Cuerpo alterado: añade un parámetro que no forma parte de la firma (MALICIOUS_INJECTION=HACKED_DATA) a una petición firmada. El servidor la procesa con normalidad, así que el cuerpo no influye en la verificación.
  4. Cambio de endpoint: genera una firma nueva pensada para /balances, no la envía, le añade el parámetro currencyPair=BTC_EUR y la manda a otro endpoint, /api/openOrders.
  5. Resultado: CoinMate acepta la petición y responde 200 OK (o un error de lógica de negocio dentro de una respuesta 200), aunque la firma se había calculado para otra operación.

El flujo de ataque que plantea el reporte:

[Víctima] --(POST /api/balances, firma X, nonce 1)--> [Atacante]
                                                         |
                               el atacante descarta la petición original
                               conserva la firma X y el nonce 1
                               añade parámetros de sellLimit
                                                         |
[CoinMate] <--(POST /api/sellLimit, firma X, nonce 1)----/
     |
la firma X es válida y el nonce no se ha usado → se ejecuta la orden

Impacto

Quien pueda interceptar y retener una petición firmada antes de que llegue al servidor puede cambiar el destino y los parámetros. El reporte pone como ejemplo convertir una consulta de saldos (/api/balances) en una orden de venta (/api/sellLimit) con el importe (amount), el precio (price) y el par (currencyPair) que elija el atacante. Según el investigador, esto permitiría vaciar fondos, manipular el mercado con órdenes falsas y saltarse las restricciones de endpoints que tuviera configuradas la clave de API.

Para que el ataque funcione hace falta poder interceptar el tráfico entre el cliente de la API y CoinMate. El reporte menciona una WiFi pública maliciosa, un proveedor comprometido o una extensión del navegador maliciosa. El programa le dio una severidad baja, no la alta que proponía el investigador, y el reporte no explica por qué.

Remediación

El reporte figura como resuelto, pero no explica qué cambió CoinMate. La propuesta del investigador era incluir en la firma todo el contexto de la petición, como hacen otros exchanges:

HMAC = SHA256( private_key, nonce + HTTP_METHOD + REQUEST_PATH + POST_BODY_STRING + clientId + publicKey )

Así, cambiar el endpoint o cualquier parámetro invalida la firma y el servidor rechaza la petición.

Qué aprender de este caso

  • Si una API firma peticiones con HMAC, revisa en su documentación o en sus SDK qué campos entran en la firma. Si solo aparecen nonce, timestamp o identificadores de la clave, prueba a cambiar la ruta o el cuerpo de una petición firmada que el servidor aún no haya recibido.
  • Al probarlo, ten en cuenta que la protección contra repeticiones confunde: un Access denied al reenviar una firma puede deberse al nonce gastado y no a que se verifique el contenido. Genera una firma nueva y envíala modificada sin haber mandado antes la original.
  • Si diseñas un esquema de firma, mete en el texto firmado el método HTTP, la ruta, la query y el cuerpo exactamente como se envían, además del nonce. Si no, la firma autentica a quien llama pero no la operación.
  • Las restricciones por endpoint de una clave de API solo protegen si la firma va ligada al endpoint. Si no, una firma de solo lectura interceptada antes de llegar al servidor puede servir para una operación de escritura.