Una aplicación del gobierno saudí incluyó la clave privada de un banco. Reportarlo requería ser saudí.

@iam_zachi
INGLÉS08 sept 2026
235K
1.5K
95
28
1.1K

TL;DR

Un investigador de seguridad descubrió que la aplicación oficial saudí Nusuk filtró una clave RSA privada y credenciales OAuth del Saudi National Bank, protegidas por una contraseña de un solo dígito. Reportar el error requirió un tuit viral debido a que los portales de vulnerabilidad estaban restringidos geográficamente.

Un archivo de 2,835 bytes dentro de una aplicación gubernamental con más de 10 millones de instalaciones contenía un certificado de cliente activo del Banco Nacional Saudí. Lograr que alguien lo revisara requirió un tuit viral.

Resumen Ejecutivo

La aplicación oficial Nusuk (com.moh.nusukapp, Ministerio de Hajj y Umrah, más de 10 millones de instalaciones, con la insignia "Gubernamental" de Google Play) incluía un archivo PKCS#12 que contenía una clave privada RSA y un certificado de cliente emitido por el Banco Nacional Saudí. La contraseña de ese archivo estaba codificada a pocas líneas de distancia en el código de la propia aplicación. Era un solo carácter: 2.

Junto a él, en texto plano, se encontraban el ID de cliente OAuth2 y el secreto de cliente para la API de Banca como Servicio del banco, solicitando los alcances identity accounts cards verification kyc cardpay transfers.

Cualquiera que descargara la aplicación desde Google Play tenía acceso a todo esto.

Intenté reportarlo. Me dijeron que el portal de vulnerabilidades solo está disponible para usuarios dentro de Arabia Saudita. Así que lo tuiteé. El tuit alcanzó 1.5 millones de visitas, y de repente el mismo portal quería los detalles. Un día después, las credenciales desaparecieron de la aplicación.

https://x.com/iam_zachi/status/2094445016194207745

Este artículo cubre únicamente el hallazgo bancario. Todo lo que se menciona a continuación ha sido corregido en la versión actual de la aplicación, y el proveedor afirma que las credenciales han sido rotadas.

Qué es Nusuk

Nusuk es la plataforma oficial del gobierno saudí para el Hajj y la Umrah. Gestiona permisos de peregrinación, visas electrónicas, reservas y la Tarjeta Nusuk. Está operada por el Ministerio de Hajj y Umrah, está marcada como una aplicación gubernamental verificada en Google Play y tiene más de diez millones de instalaciones. También incluye una función de billetera, Nusuk Wallet, desarrollada en conjunto con el Banco Nacional Saudí y aprobada por SAMA, el banco central saudí. La billetera es la parte de la que trata este artículo.

El hallazgo

Descargué el conjunto de APK directamente desde Google Play (versión 17.4.9, versionCode 131215) y lo descomprimí. Nada exótico: Kotlin/Compose estándar, sin empaquetador, sin ofuscación significativa.

Dentro de los recursos de la aplicación, en res/raw/nusuk.pfx, había un contenedor PKCS#12 de 2,835 bytes. Un archivo .pfx es el formato estándar para empaquetar un certificado junto con su clave privada, cifrado con una contraseña.

La contraseña estaba en el código de la aplicación, a pocas líneas de donde se carga el archivo:

text
1const-string v3, "2"

Un solo carácter, presente como literal en el bytecode descompilado. Una llamada a openssl después:

text
1RSA private key, 2048 bit, 2 prime factors
2Subject: C=SA, ST=Jeddah, L=Jeddah, O=Nusuk.sa, CN=eshbeata@staq.io
3Issuer: C=SA, ST=Riyadh, L=Riyadh, O=The Saudi National Bank, OU=Finto,
4 CN=Application Issuer
5Serial: 0x24 (36)
6Valid: 2026-04-27 -> 2027-04-27
7Extended Key Usage (critical): TLS Web Client Authentication

Un certificado de cliente activo emitido por un banco, válido por un año más, destinado a autenticar un cliente ante un servidor a través de TLS.

No estaba solo. La misma ruta de código corresponde al componente de la billetera, empaquetado como com.walletstaq e integrado con el SDK Trustless de Staq Technologies, la empresa que opera la plataforma Finto BaaS de SNB. Tres valores adicionales estaban en texto claro:

con el alcance OAuth solicitado:

text
1identity accounts cards verification kyc cardpay transfers

Ambas credenciales también estaban concatenadas en una sola cadena y enviadas a un registrador de depuración, junto con la URL base.

(No estoy publicando el material de la clave ni los valores completos del secreto. Lo que importa aquí es la forma del problema).

Por qué esto es grave, en términos simples

Imagina la API del banco como una puerta con dos cerraduras.

La primera cerradura es TLS mutuo. Normalmente, un servidor prueba su identidad ante ti con un certificado. Con mTLS, tú también debes probar tu identidad ante el servidor con un certificado. Ese es el archivo .pfx: el certificado y la clave privada que demuestran que eres su propietario. Se supone que es algo que solo el sistema cliente legítimo posee.

La segunda cerradura es el secreto de cliente OAuth, la contraseña que la aplicación utiliza para solicitar un token de acceso a la API del banco.

Ambas cerraduras se enviaron dentro de una aplicación gratuita en Google Play, y la llave de la caja que contenía la primera era el dígito 2.

Confirmé que el host objetivo realmente aplica mTLS: el handshake TLS con api.baas.alahli.com solicita certificados de cliente (envía Acceptable client certificate CA names), y el certificado del servidor muestra CN=*.baas.alahli.com, O=The Saudi National Bank. Por lo tanto, no era un certificado decorativo para un entorno de prueba. Era la credencial para la puerta principal de una API bancaria en producción, con alcances que cubren identidad, cuentas, tarjetas, KYC, pagos con tarjeta y transferencias.

Independientemente de la filtración, hay un problema de diseño subyacente. Un certificado de cliente que se distribuye idénticamente a diez millones de dispositivos no puede distinguir una instalación de otra. Cada copia presenta la misma credencial, por lo que el certificado le dice al banco qué aplicación está llamando, pero nada sobre quién está llamando. Las credenciales para una API como esta deben estar detrás de tu propio backend: la aplicación se comunica con tu servidor, y tu servidor se comunica con el banco.

Lo que NO hice

Realicé exactamente una verificación contra el endpoint de token (POST /api/tppa/token) para ver si las credenciales estaban activas. Devolvió un HTTP 403 de nginx. Lo mismo ocurrió con una solicitud sin certificado y con la URL raíz desnuda. Eso es un bloqueo a nivel de red que se encuentra frente a la API, casi con certeza geográfico, y no dice nada sobre si las credenciales funcionan.

Desde fuera de Arabia Saudita, no pude determinar si estas credenciales estaban activas. Me detuve ahí. Cualquier otra cosa habría sido un intento de eludir el control de acceso de un banco, y el hallazgo no depende de ello: una clave privada y un secreto OAuth bancario con alcance de transferencias en un artefacto descargable públicamente es el hallazgo, independientemente de si puedo o no llegar al endpoint personalmente.

Intentando reportarlo

Esta es la parte que hizo viral el tuit, y es la mitad más interesante.

Busqué una forma de reportarlo de manera responsable. Esto es lo que existe:

Canal

Resultado

security.txt en nusuk.sa, haj.gov.sa, hajj.nusuk.sa

No existe

Página de reporte de vulnerabilidades de Saudi CERT (cert.gov.sa)

Redirige a NCA; su propia página de reporte está cerrada

Formulario de vulnerabilidades de NCA (haseen.gov.sa)

Inaccesible desde Alemania: tiempo de espera agotado, bloqueo geográfico

bugbounty.sa

Programa cerrado, HTTP 403 desde fuera

HackerOne / Bugcrowd

Sin programa para Nusuk, el Ministerio o Elm

Contactos en la lista de la tienda de aplicaciones

Direcciones de soporte, sin mandato de seguridad

Nada de eso me dejó un canal con un mandato de seguridad al que pudiera acceder. Envié un correo electrónico de todos modos. La respuesta de Haseen Support:

"El acceso al portal Haseen está restringido a usuarios dentro del Reino de Arabia Saudita. Para cualquier consulta adicional, puede contactarnos a través del servicio 'Nos importa' disponible en el Portal Haseen oficial."

El Portal Haseen, que es justo a lo que no puedo acceder. Respondí explicando que no soy saudí, que se trata de un certificado bancario privado expuesto en una aplicación gubernamental y que solo quería entregarlo. La respuesta, nuevamente, fue que el formulario solo funciona para ciudadanos de KSA.

Así que presenté un informe ante CERT/CC a través de su plataforma VINCE como intermediario coordinador (VRF#26-08-DXMKL), limitado únicamente a este hallazgo. Esa es la ruta que tomas cuando la parte afectada no tiene un canal accesible propio.

Y luego tuiteé al respecto, principalmente por frustración.

El tuit alcanzó 1.5 millones de visitas. En cuestión de horas, Haseen Support me envió un correo electrónico, sin que yo lo solicitara, en el mismo hilo que me había dicho dos veces que el portal no era para mí:

"De acuerdo con nuestro equipo correspondiente, por favor proporcione más detalles sobre la vulnerabilidad de seguridad."

Envié todos los detalles. Prefiero que el problema se solucione a tener la razón sobre el proceso.

La solución

La siguiente actualización de la aplicación llegó tanto a Android como a iOS. Descargué la nueva compilación de Android (17.5.0, versionCode 156635) directamente desde Play y la comparé con lo que había analizado. Tres verificaciones:

  1. No hay ningún contenedor de certificados en el nuevo conjunto de APK: ningún archivo con extensión .pfx, .p12, .pkcs12, .jks, .bks, .pem o .key. También calculé el hash del antiguo nusuk.pfx y lo comparé byte por byte con cada archivo del mismo tamaño en la nueva compilación, por si acaso simplemente se había renombrado. Sin coincidencias.
  2. Ninguna de las credenciales conocidas. Busqué los valores antiguos exactos de la URL base, el alcance, el ID de cliente y el secreto de cliente en todos los archivos DEX, bibliotecas nativas, assets, XML, JSON y recursos sin procesar. Cero resultados para los cuatro.
  3. Sin ruta de código. La versión 17.4.9 contenía 4,571 archivos en los paquetes com.walletstaq y com.trustless; la 17.5.0 contiene cero. Los marcadores baas.alahli, tppa/token, nusuk.pfx, el sujeto del certificado y el nombre del emisor devuelven cero resultados en la compilación decodificada.

Se eliminó toda la integración de la billetera y BaaS. La verificación de cambio de nombre en el paso 1 es lo que descarta que los valores simplemente se hayan movido a otro lugar dentro del paquete.

La parte que nadie fuera puede verificar

Eliminar un secreto de una aplicación no invalida el secreto. Las copias antiguas del APK permanecen disponibles para siempre, y el certificado era válido hasta abril de 2027. Por lo tanto, la pregunta abierta es si fue revocado y si el secreto OAuth fue rotado.

Busqué una forma de verificar eso de forma independiente. No la hay, y la razón en sí misma es un hallazgo.

El certificado no incluye una extensión crlDistributionPoints, por lo que no se hace referencia a ninguna lista de revocación. Su único endpoint de revocación es:

text
1OCSP - URI: http://finto-ocsp-responder.prod.svc.cluster.local:8080/api/v1/ocsp

.cluster.local es el sufijo DNS interno de un clúster de Kubernetes. Por definición, no es enrutable en la internet pública y se resuelve como NXDOMAIN desde cualquier lugar fuera de ese clúster, a través de HTTP simple en el puerto 8080.

Por lo tanto, el estado de revocación de este certificado no se puede verificar desde fuera de la infraestructura del banco, porque no hay nada aquí afuera a lo que consultar. Para cualquier parte dependiente fuera de ese único clúster, los certificados de este emisor son efectivamente irrevocables, lo que por sí solo merece un párrafo en la revisión de arquitectura de alguien.

Ese mismo campo también publicó el nombre del clúster, el espacio de nombres, el nombre del servicio y el puerto de la PKI de producción de la plataforma BaaS de un banco, en una aplicación distribuida a diez millones de personas.

Solo SNB, Finto o Staq pueden confirmar la rotación. El proveedor afirma que las credenciales han sido rotadas. No tengo forma de verificar eso de forma independiente.

Cronología

Fecha (2026)

Evento

29 de agosto

APK analizado, hallazgo confirmado localmente

29 de agosto

Informe enviado por correo electrónico; Haseen responde que el portal está restringido a usuarios dentro de KSA

31 de agosto

Intentos repetidos, misma respuesta. Tuiteo al respecto; ~1.5M de visitas

1 de septiembre

Haseen reabre el hilo sin solicitarlo, solicita detalles. Detalles enviados

1 de septiembre

Informe también presentado ante CERT/CC VINCE (VRF#26-08-DXMKL) como intermediario

1 de septiembre

Versión 17.5.0 publicada en Google Play

3 de septiembre

Nueva prueba en el conjunto de APK 17.5.0 sin modificar confirma la eliminación completa

8 de septiembre

Este artículo

No puedo probar que la actualización fue causada por mi informe. Es posible que la 17.5.0 ya estuviera en proceso. Lo que puedo demostrar es que el material estaba en la 17.4.9 y no está en la 17.5.0.

Conclusiones

  1. Ocultar algo dentro de la aplicación no es un límite de seguridad. No en los recursos, no en un .so nativo, no detrás de ofuscación o una contraseña almacenada en el mismo binario. Si la aplicación puede leerlo, también puede hacerlo cualquiera que instale la aplicación. Varios equipos aprenden esto en público cada año.
  2. Un certificado de cliente compartido no es autenticación. Si diez millones de dispositivos presentan el mismo certificado, este indica qué aplicación está llamando y nada sobre quién está llamando, y la aplicación es un archivo que cualquiera puede descargar. Las credenciales para una API de terceros, y sobre todo de un banco, deben estar en un servidor que tú controles.
  3. Geocercar tu canal de divulgación de vulnerabilidades es en sí mismo una vulnerabilidad. Los atacantes no llenan formularios. Si la única forma de reportar una falla en una aplicación publicada globalmente para diez millones de personas es estar físicamente dentro de un país, entonces las personas que no pueden acceder al formulario son exactamente aquellas de las que más deseas recibir noticias. Se necesitó un tuit viral para abrir un canal que un archivo security.txt habría abierto de forma gratuita.

Analicé el conjunto de APK de Play Store disponible públicamente en modo invitado, sin cuenta y sin datos personales reales. No intenté eludir el bloqueo a nivel de red frente a la API bancaria. Cada valor en este artículo es estructural (rutas, nombres de clases, metadatos del certificado) o está redactado; no se publica ningún material de clave privada ni secretos completos.

Guardar con un clic

Lee artículos virales en profundidad con IA en YouMind

Guarda la fuente, haz preguntas concretas, resume el argumento y convierte un artículo viral en notas reutilizables en un único espacio de trabajo con IA.

Explora YouMind
Para creadores

Convierte tu Markdown en un artículo de 𝕏 impecable

Cuando publicas tus propios textos largos, dar formato en 𝕏 a imágenes, tablas y bloques de código es un fastidio. YouMind convierte un borrador completo en Markdown en un artículo de 𝕏 impecable y listo para publicar.

Prueba Markdown a 𝕏

Más patrones por descifrar

Artículos virales recientes

Explorar más artículos virales