Une application gouvernementale saoudienne a intégré la clé privée d'une banque. Pour la signaler, il fallait être Saoudien.

@iam_zachi
ANGLAIS08 sept. 2026
235K
1.5K
95
28
1.1K

TL;DR

Un chercheur en sécurité a découvert que l'application officielle saoudienne Nusuk divulguait une clé privée RSA et des identifiants OAuth pour la Saudi National Bank, protégés par un mot de passe à un seul chiffre. Le signalement de la faille a nécessité un tweet viral en raison de portails de vulnérabilité restreints géographiquement.

Un fichier de 2 835 octets dans une application gouvernementale avec plus de 10 millions d'installations contenait un certificat client actif pour la Saudi National Bank. Il a fallu un tweet viral pour que quelqu'un s'y intéresse.

Résumé

L'application officielle Nusuk (com.moh.nusukapp, Ministère du Hajj et de la Omra, plus de 10 millions d'installations, arborant le badge "Gouvernement" de Google Play) contenait un fichier PKCS#12 avec une clé privée RSA et un certificat client émis par la Saudi National Bank. Le mot de passe de ce fichier était codé en dur quelques lignes plus loin dans le code de l'application. Il s'agissait d'un seul caractère : 2.

À côté, en texte clair, se trouvaient l'ID client OAuth2 et le secret client pour l'API Banking-as-a-Service de la banque, demandant les portées identity accounts cards verification kyc cardpay transfers.

Toute personne ayant téléchargé l'application depuis Google Play avait accès à tout cela.

J'ai essayé de le signaler. On m'a dit que le portail de vulnérabilités n'était accessible qu'aux utilisateurs en Arabie Saoudite. J'ai donc tweeté. Le tweet a fait 1,5 million de vues, et soudainement, le même portail demandait des détails. Un jour plus tard, les identifiants avaient disparu de l'application.

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

Cet article ne couvre que la découverte bancaire. Tout ce qui suit a été corrigé dans l'application en production, et le fournisseur affirme que les identifiants ont été renouvelés.

Qu'est-ce que Nusuk

Nusuk est la plateforme officielle du gouvernement saoudien pour le Hajj et la Omra. Elle gère les permis de pèlerinage, les e-visas, les réservations et la carte Nusuk. Elle est exploitée par le Ministère du Hajj et de la Omra, est signalée comme une application gouvernementale vérifiée sur Google Play et compte plus de dix millions d'installations. Elle contient également une fonctionnalité de portefeuille, Nusuk Wallet, développée conjointement avec la Saudi National Bank et approuvée par SAMA, la banque centrale saoudienne. Le portefeuille est la partie dont traite cet article.

La découverte

J'ai téléchargé l'ensemble APK directement depuis Google Play (version 17.4.9, versionCode 131215) et je l'ai décompressé. Rien d'exotique : Kotlin/Compose standard, pas de packer, pas d'obfuscation significative.

Dans les ressources de l'application, à res/raw/nusuk.pfx, se trouvait un conteneur PKCS#12 de 2 835 octets. Un fichier .pfx est le format standard pour regrouper un certificat et sa clé privée, chiffré avec un mot de passe.

Le mot de passe se trouvait dans le code de l'application, quelques lignes après l'endroit où le fichier est chargé :

text
1const-string v3, "2"

Un seul caractère, présent en tant que littéral dans le bytecode décompilé. Un appel à openssl plus tard :

text
1Clé privée RSA, 2048 bits, 2 facteurs premiers
2Sujet : C=SA, ST=Jeddah, L=Jeddah, O=Nusuk.sa, CN=eshbeata@staq.io
3Émetteur : C=SA, ST=Riyadh, L=Riyadh, O=The Saudi National Bank, OU=Finto,
4 CN=Application Issuer
5Série : 0x24 (36)
6Valide : 2026-04-27 -> 2027-04-27
7Utilisation étendue de la clé (critique) : Authentification client TLS Web

Un certificat client actif émis par une banque, valable encore un an, destiné à authentifier un client auprès d'un serveur via TLS.

Il n'était pas seul. Le même chemin de code est celui du composant portefeuille, empaqueté sous com.walletstaq et intégré au SDK Trustless de Staq Technologies, la société qui exploite la plateforme BaaS Finto de la SNB. Trois autres valeurs étaient présentes en texte clair :

  • CLIENT_ID = 379cc899…
  • CLIENT_SECRET = ELrfNe9w… (64 octets bruts, encodés en base64)
  • SERVER_URL = [https://api.baas.alahli.com/api/](https://api.baas.alahli.com/api/)

avec la portée OAuth demandée :

text
1identity accounts cards verification kyc cardpay transfers

Les deux identifiants étaient également concaténés en une seule chaîne et transmis à un débogueur, avec l'URL de base.

(Je ne publie pas le matériel de clé ni les valeurs complètes des secrets. Ce qui importe ici, c'est la forme du problème.)

Pourquoi c'est grave, en termes simples

Considérez l'API de la banque comme une porte avec deux serrures.

La première serrure est le TLS mutuel. Normalement, un serveur prouve son identité avec un certificat. Avec mTLS, vous devez également prouver votre identité au serveur avec un certificat. C'est le fichier .pfx : le certificat et la clé privée qui prouve que vous le possédez. Il est censé être la chose que seul le système client légitime possède.

La deuxième serrure est le secret client OAuth, le mot de passe que l'application utilise pour demander un jeton d'accès à l'API de la banque.

Les deux serrures ont été expédiées dans une application gratuite sur Google Play, et la clé de la boîte contenant la première était le chiffre 2.

J'ai confirmé que l'hôte cible applique effectivement le mTLS : la poignée de main TLS avec api.baas.alahli.com demande des certificats clients (elle envoie Acceptable client certificate CA names), et le certificat du serveur indique CN=*.baas.alahli.com, O=The Saudi National Bank. Il ne s'agissait donc pas d'un certificat décoratif pour un environnement de test. C'était l'identifiant pour la porte d'entrée d'une API bancaire de production, avec des portées couvrant l'identité, les comptes, les cartes, le KYC, les paiements par carte et les transferts.

Indépendamment de la fuite, il y a un problème de conception sous-jacent. Un certificat client expédié à l'identique sur dix millions d'appareils ne peut pas distinguer une installation d'une autre. Chaque copie présente le même identifiant, donc le certificat indique à la banque quelle application appelle, et rien sur qui appelle. Les identifiants pour une API comme celle-ci doivent se trouver derrière votre propre backend : l'application parle à votre serveur, votre serveur parle à la banque.

Ce que je n'ai pas fait

J'ai effectué exactement une vérification contre le point de terminaison du jeton (POST /api/tppa/token) pour voir si les identifiants étaient actifs. Il a renvoyé une erreur HTTP 403 de nginx. Il en a été de même pour une requête sans certificat du tout, et pour l'URL racine nue. Il s'agit d'un blocage au niveau du réseau situé devant l'API, presque certainement géographique, et cela ne dit rien sur le fait que les identifiants fonctionnent ou non.

Depuis l'extérieur de l'Arabie Saoudite, je n'ai pas pu déterminer si ces identifiants étaient actifs. Je me suis arrêté là. Aller plus loin aurait été une tentative de contourner le contrôle d'accès d'une banque, et la découverte n'en dépend pas : une clé privée et un secret OAuth bancaire avec une portée de transfert se trouvant dans un artefact téléchargeable publiquement est la découverte, que je puisse ou non atteindre personnellement le point de terminaison.

Tentative de signalement

C'est la partie qui a rendu le tweet viral, et c'est la moitié la plus intéressante.

J'ai cherché un moyen de signaler cela de manière responsable. Ce qui existe :

Canal

Résultat

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

N'existe pas

Page de signalement des vulnérabilités de Saudi CERT (cert.gov.sa)

Redirige vers NCA ; propre page de signalement fermée

Formulaire de vulnérabilité NCA (haseen.gov.sa)

Inaccessible depuis l'Allemagne : timeout, géo-blocage

bugbounty.sa

Programme fermé, HTTP 403 depuis l'extérieur

HackerOne / Bugcrowd

Aucun programme pour Nusuk, le Ministère ou Elm

Coordonnées de la boutique d'applications

Adresses de support, aucun mandat de sécurité

Aucun de ces canaux ne m'a laissé une voie avec un mandat de sécurité que je pouvais réellement atteindre. J'ai quand même envoyé un e-mail. La réponse du support Haseen :

"L'accès au portail Haseen est limité aux utilisateurs du Royaume d'Arabie Saoudite. Pour toute autre demande, vous pouvez nous contacter via le service 'Nous écoutons' disponible sur le portail officiel Haseen."

Le portail Haseen, qui est justement la chose que je ne peux pas atteindre. J'ai répondu en expliquant que je ne suis pas saoudien, qu'il s'agit d'un certificat bancaire privé exposé dans une application gouvernementale, et que je voulais simplement le transmettre. La réponse a été, à nouveau, que le formulaire fonctionne uniquement pour les citoyens d'Arabie Saoudite.

J'ai donc déposé un rapport auprès du CERT/CC via leur plateforme VINCE en tant qu'intermédiaire de coordination (VRF#26-08-DXMKL), limité à cette seule découverte. C'est la voie à suivre lorsque la partie concernée n'a pas de canal accessible.

Et puis j'ai tweeté à ce sujet, surtout par frustration.

Le tweet a fait 1,5 million de vues. En quelques heures, le support Haseen m'a envoyé un e-mail, de manière non sollicitée, sur le même fil de discussion qui m'avait dit deux fois que le portail n'était pas pour moi :

"Selon notre équipe concernée, veuillez nous fournir plus de détails sur la vulnérabilité de sécurité."

J'ai envoyé tous les détails. Je préfère que le problème soit résolu plutôt que d'avoir raison sur le processus.

La correction

La mise à jour suivante de l'application est arrivée sur Android et iOS. J'ai téléchargé la nouvelle version Android (17.5.0, versionCode 156635) directement depuis Play et je l'ai comparée à celle que j'avais analysée. Trois vérifications :

  1. Aucun conteneur de certificat dans le nouvel ensemble APK : aucun fichier avec une extension .pfx, .p12, .pkcs12, .jks, .bks, .pem ou .key. J'ai également haché l'ancien nusuk.pfx et l'ai comparé octet par octet à chaque fichier de même taille dans la nouvelle version, au cas où il aurait simplement été renommé. Aucune correspondance.
  2. Aucun des identifiants connus. J'ai recherché les valeurs exactes de l'ancienne URL de base, de la portée, de l'ID client et du secret client dans tous les fichiers DEX, bibliothèques natives, assets, XML, JSON et ressources brutes. Zéro résultat pour les quatre.
  3. Aucun chemin de code. La version 17.4.9 contenait 4 571 fichiers sous les packages com.walletstaq et com.trustless ; la version 17.5.0 en contient zéro. Les marqueurs baas.alahli, tppa/token, nusuk.pfx, le sujet du certificat et le nom de l'émetteur renvoient tous zéro résultat dans la version décodée.

L'ensemble du portefeuille et de l'intégration BaaS a été retiré. La vérification de renommage à l'étape 1 est ce qui exclut la possibilité que les valeurs aient simplement été déplacées ailleurs dans le package.

La partie que personne à l'extérieur ne peut vérifier

Supprimer un secret d'une application ne l'invalide pas. Les anciennes copies APK restent disponibles pour toujours, et le certificat était valide jusqu'en avril 2027. La question ouverte est donc de savoir s'il a été révoqué et si le secret OAuth a été renouvelé.

J'ai cherché un moyen de vérifier cela de manière indépendante. Il n'y en a pas, et la raison est en soi une découverte.

Le certificat ne comporte aucune extension crlDistributionPoints, donc aucune liste de révocation n'est référencée. Son seul point de terminaison de révocation est :

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

.cluster.local est le suffixe DNS interne d'un cluster Kubernetes. Il n'est par définition pas routable sur l'internet public, et il résout en NXDOMAIN depuis n'importe où en dehors de ce cluster, via HTTP simple sur le port 8080.

Ainsi, l'état de révocation de ce certificat ne peut pas être vérifié depuis l'extérieur de l'infrastructure de la banque, car il n'y a rien à interroger ici. Pour toute partie prenante en dehors de ce seul cluster, les certificats de cet émetteur sont effectivement irrévocables, ce qui mérite à lui seul un paragraphe dans une revue d'architecture.

Ce même champ a également publié le nom du cluster, l'espace de noms, le nom du service et le port de l'infrastructure de production PKI de la plateforme BaaS d'une banque, dans une application distribuée à dix millions de personnes.

Seules la SNB, Finto ou Staq peuvent confirmer le renouvellement. Le fournisseur affirme que les identifiants ont été renouvelés. Je n'ai aucun moyen de vérifier cela de manière indépendante.

Chronologie

Date (2026)

Événement

29 août

APK analysé, découverte confirmée localement

29 août

Rapport envoyé par e-mail ; Haseen répond que le portail est réservé aux utilisateurs en Arabie Saoudite

31 août

Tentatives répétées, même réponse. Je tweete à ce sujet ; ~1,5 million de vues

1er septembre

Haseen rouvre le fil de discussion de manière non sollicitée, demande des détails. Détails envoyés

1er septembre

Rapport également déposé auprès du CERT/CC VINCE (VRF#26-08-DXMKL) en tant qu'intermédiaire

1er septembre

Version 17.5.0 publiée sur Google Play

3 septembre

Nouveau test sur l'ensemble APK 17.5.0 non modifié confirme la suppression complète

8 septembre

Cet article

Je ne peux pas prouver que la mise à jour a été causée par mon rapport. La version 17.5.0 était peut-être déjà en préparation. Ce que je peux montrer, c'est que le matériel était dans la version 17.4.9 et ne l'est pas dans la version 17.5.0.

Ce que j'en retiens

  1. Cacher quelque chose dans l'application n'est pas une barrière de sécurité. Ni dans les ressources, ni dans un .so natif, ni derrière de l'obfuscation ou un mot de passe stocké dans le même binaire. Si l'application peut le lire, tous ceux qui installent l'application le peuvent aussi. Plusieurs équipes l'apprennent en public chaque année.
  2. Un certificat client partagé n'est pas une authentification. Si dix millions d'appareils présentent le même certificat, cela indique quelle application appelle et rien sur qui appelle, et l'application est un fichier que n'importe qui peut télécharger. Les identifiants pour une API tierce, surtout celle d'une banque, doivent se trouver sur un serveur que vous contrôlez.
  3. Géo-bloquer votre canal de divulgation des vulnérabilités est en soi une vulnérabilité. Les attaquants ne remplissent pas de formulaires. Si la seule façon de signaler une faille dans une application publiée mondialement auprès de dix millions de personnes est d'être physiquement dans un seul pays, alors les personnes qui ne peuvent pas accéder au formulaire sont exactement celles que vous voulez le plus entendre. Il a fallu un tweet viral pour ouvrir un canal qu'un fichier security.txt aurait ouvert gratuitement.

J'ai analysé l'ensemble APK du Play Store publiquement disponible en mode invité, sans compte et sans données personnelles réelles. Je n'ai fait aucune tentative pour contourner le blocage au niveau du réseau devant l'API bancaire. Chaque valeur dans cet article est soit structurelle (chemins, noms de classes, métadonnées de certificat), soit expurgée ; aucun matériel de clé privée et aucun secret complet n'est publié.

Enregistrer en un clic

Lire les articles viraux en profondeur avec l’IA de YouMind

Enregistrez la source, posez des questions ciblées, résumez l’argument et transformez un article viral en notes réutilisables dans un seul espace de travail IA.

Découvrir YouMind
Pour les créateurs

Transformez votre Markdown en un article 𝕏 impeccable

Quand vous publiez vos propres textes longs, la mise en forme 𝕏 des images, tableaux et blocs de code est pénible. YouMind transforme un brouillon Markdown complet en un article 𝕏 impeccable, prêt à publier.

Essayer Markdown vers 𝕏

D'autres patterns à décoder

Articles viraux récents

Explorer plus d'articles viraux