Reporte de Vulnerabilidad VR-001 — IDOR que expone PII y metadatos de tarjeta de crédito vía cédula uruguaya

Estado: Borrador, todavía no divulgado a UTE. Reportador: <admin@checkleaked.cc> Fecha de descubrimiento: 2026-05-20 Severidad (CVSS 3.1): 9.1 / CríticaAV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N Producto: UTE Mueve (plataforma de movilidad eléctrica de Administración Nacional de Usinas y Trasmisiones Eléctricas, Uruguay). Superficie afectada: API REST pública en https://movilidadelectrica.ute.com.uy/api/v2. Verificado contra APK versionCode 9922 (versionName 1.0.24).


1. Resumen

/api/v2 emite JWTs anónimos a cualquier cliente que presente un header uniquekeyuser auto-generado. Una vez con un token, varios endpoints user-scoped (notablemente GET /api/v2/customer/card/{key}) aceptan una cédula uruguaya pura como parámetro de path y devuelven el registro completo del cliente — incluyendo nombre y apellido, BIN + últimos 4 de la tarjeta de crédito vinculada a MercadoPago, fecha de expiración, marca, payerCardId interno de MercadoPago, cuenta RFID registrada, suscripciones a redes e historial de cargas remotas.

La cédula es un identificador nacional de 7-9 dígitos con menos de ~5 millones de valores emitidos. No se observa rate limiting en la emisión del token ni en las lecturas subsiguientes. Combinado, esto permite a un atacante enumerar toda la base de usuarios UTE Mueve uruguaya y extraer información financiera y personal suficiente para montar campañas de vishing/phishing altamente creíbles, ataques de identity-pivot y vigilancia de patrones de movilidad.

2. Reproducción (tres calls HTTP)

POST /api/v2/token HTTP/1.1
Host: movilidadelectrica.ute.com.uy
user-agent: Dart/3.4 (dart:io)
content-type: application/json; charset=utf-8
uniquekeyuser: <CUALQUIER-13-HEX>     # generado random, NO pre-registrado

{"clientIdIDP":"cargaME","identifier":"Anonymous"}

Respuesta (truncada): { "access_token": "<JWT>", "expires_in": 3600, "token_type": "Bearer", "scope": "apiME" }.

GET /api/v2/customer/card/<CI> HTTP/1.1
Host: movilidadelectrica.ute.com.uy
authorization: Bearer <JWT>
uniquekeyuser: <mismo que arriba>

Forma de la respuesta (campos reales, valores saneados — ver packages/openapi/fixtures/customer.card.example.json para el fixture JSON):

{
  "data": [
    {
      "id": "<INTERNAL_ID>",
      "cardId": "**XXXXXXXXXXXXX",
      "firstSixDigits": "<BIN>",
      "lastFourDigits": "<L4>",
      "expirationMonth": "<MM>",
      "expirationYear": "<YYYY>",
      "identifType": "CI",
      "identifNumber": "<CI>",
      "paymentMethodId": "<MARCA>",
      "firstName": "<NOMBRE>",
      "lastName": "<APELLIDO>",
      "status": "Habilitada",
      "cvvMandatory": true,
      "payerCardId": "<MP_PAYER_ID>",
      "issuerId": "<ISSUER_ID>",
      "minPay": 15,
      "cardUseType": 1,
      "cardUseTypeDesc": "Particular"
    }
  ],
  "messages": [], "success": true, "errors": [], "result": 0
}

Endpoints adicionales confirmados que exponen datos con el mismo contexto de autorización:

3. Impacto

Tipo de dato Expuesto sin autenticación
Membresía (¿tiene CI X cuenta UTE Mueve?)
Nombre y apellido en file
Marca de tarjeta
BIN de tarjeta (primeros 6 dígitos del PAN)
Últimos 4 del PAN
Mes + año de expiración
payerCardId de MercadoPago (pivot cross-platform)
Flag cvvMandatory de la cuenta
accountId interno
Redes de carga habilitadas por usuario
Sesiones de carga remota (timestamps, kWh, costo, estación/conector)

Escenarios de ataque

  1. Enumeración masiva de la base UTE Mueve. Un sweep de 5 millones de calls sobre el rango de CIs emitidos agota la superficie; a 100 req/s se completa en ~14 horas.
  2. Vishing altamente creíble. Un atacante puede verificar nombre + marca + últimos 4 + mes/año de expiración, induciendo a la víctima a "confirmar el CVV" o "aprobar una transacción".
  3. Phishing dirigido. Email con el nombre de la víctima + últimos 4 + contexto creíble UTE / MercadoPago.
  4. Vigilancia de patrones de movilidad sobre figuras públicas, activistas, periodistas, jueces — el uso de UTE Mueve revela visitas a estaciones de carga con timestamps.
  5. Pivot cross-platform vía payerCardId para enumerar estado de cuenta MercadoPago donde aplique.

Exposición regulatoria

4. Causa raíz

El JWT con audiencia apiME no tiene claim sub atándolo a un usuario. El parámetro del path ({customerKey}) es la única señal de autorización en los endpoints user-scoped. El header uniquekeyuser es un tag opaco free-form que el servidor acepta para cualquier valor — un hex random fresco es suficiente. El endpoint de lookup (POST /api/v2/card/accounts/) explícitamente acepta docType: "CI" como modalidad de búsqueda.

5. Correcciones recomendadas (orden de prioridad)

  1. Inmediato (en horas): Agregar rate limit por IP y por uniquekeyuser en POST /api/v2/token (p.ej. 60 tokens/IP/hora). Agregar rate limit por IP en GET /customer/card/*, /card/accounts/*, /network/*, /remotecharge/user/* (p.ej. 20 req/min). Rechazar valores de uniquekeyuser que no matchen un patrón firmado por el servidor (device key con HMAC).
  2. Corto plazo (en semanas): Requerir tokens de usuario autenticado (validación de Firebase ID token contra el verificador del proyecto, o flow OAuth completo de usuario vía identityserver.ute.com.uy/connect/*) en cada endpoint user-scoped. Validar que el customerKey del path matchea con el sub/uid del token.
  3. Mediano plazo: Mover los campos de datos de tarjeta/pago fuera de GET /customer/card/{key} (devolver solo campos seguros como cardId masked, marca, últimos 4). Hacer que BIN, expiración, payerCardId, issuerId, firstName, lastName estén disponibles solo vía un endpoint autenticado separado scoped al dueño.
  4. Largo plazo: Adoptar App Check / Play Integrity en la app Flutter oficial y enforce attestation en el backend. Emitir JWTs por usuario con sub seteado al customer key. Audit-log de cada lookup basado en CI.

6. Timeline de divulgación (propuesto)

Día Evento
0 (2026-05-20) Vulnerabilidad descubierta y reproducida. Borrador de este reporte.
0 – 3 Intentos de contacto: mailbox de seguridad de UTE si existe, sino el CSIRT nacional CERTuy (certuy.gub.uy) y AGESIC, agencia tecnológica uruguaya.
3 – 7 Acknowledgement recibido. Reunión de triage agendada.
30 Deadline recomendado para que las mitigaciones #1 (rate limit + binding de uniquekeyuser) estén deployeadas.
90 Divulgación pública del reporte conceptual (este documento). Sin código de exploit publicado.

7. Qué contiene este repositorio

8. Qué NO contiene este repositorio

9. Contacto

admin@checkleaked.cc — reportador.

Para UTE: este documento y la evidencia de soporte pueden compartirse bajo términos de divulgación coordinada bajo pedido.