Un'app del governo saudita conteneva la chiave privata di una banca. Segnalarla richiedeva di essere sauditi.

@iam_zachi
INGLESE08 set 2026
235K
1.5K
95
28
1.1K

TL;DR

Un ricercatore di sicurezza ha scoperto che l'app ufficiale saudita Nusuk esponeva una chiave privata RSA e le credenziali OAuth della Saudi National Bank, protette da una password a una sola cifra. Segnalare la vulnerabilità ha richiesto un tweet virale a causa di portali di segnalazione con restrizioni geografiche.

Un file di 2.835 byte all'interno di un'app governativa con oltre 10 milioni di installazioni conteneva un certificato client attivo per la Saudi National Bank. Per convincere qualcuno a esaminarlo, è stato necessario un tweet virale.

TL;DR

L'app ufficiale Nusuk (com.moh.nusukapp, Ministero dell'Hajj e dell'Umrah, oltre 10 milioni di installazioni, con il badge "Governativo" di Google Play) includeva un file PKCS#12 contenente una chiave privata RSA e un certificato client emesso da The Saudi National Bank. La password per quel file era hardcodata poche righe più avanti nel codice dell'app stessa. Era un singolo carattere: 2.

Accanto, in chiaro, si trovavano l'ID client OAuth2 e il client secret per l'API Banking-as-a-Service della banca, che richiedevano gli ambiti identity accounts cards verification kyc cardpay transfers.

Chiunque avesse scaricato l'app da Google Play aveva accesso a tutto.

Ho provato a segnalarlo. Mi è stato detto che il portale per le vulnerabilità è disponibile solo per gli utenti all'interno dell'Arabia Saudita. Quindi l'ho twittato. Il tweet ha raggiunto 1,5 milioni di visualizzazioni e, improvvisamente, lo stesso portale ha chiesto i dettagli. Un giorno dopo, le credenziali sono scomparse dall'app.

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

Questo articolo riguarda esclusivamente il reperimento delle credenziali bancarie. Tutto quanto descritto di seguito è stato risolto nell'app in distribuzione e il fornitore dichiara che le credenziali sono state ruotate.

Cos'è Nusuk

Nusuk è la piattaforma ufficiale del governo saudita per l'Hajj e l'Umrah. Gestisce permessi di pellegrinaggio, e-visa, prenotazioni e la Nusuk Card. È gestita dal Ministero dell'Hajj e dell'Umrah, è contrassegnata come app governativa verificata su Google Play e ha oltre dieci milioni di installazioni. Contiene anche una funzione wallet, Nusuk Wallet, sviluppata insieme alla Saudi National Bank e approvata da SAMA, la banca centrale saudita. Il wallet è la parte di cui parla questo articolo.

Il reperimento

Ho scaricato il set APK direttamente da Google Play (versione 17.4.9, versionCode 131215) e l'ho decompresso. Niente di esotico: standard Kotlin/Compose, nessun packer, nessuna offuscazione significativa.

All'interno delle risorse dell'app, in res/raw/nusuk.pfx, si trovava un contenitore PKCS#12 di 2.835 byte. Un file .pfx è il formato standard per raggruppare un certificato con la sua chiave privata, crittografato con una password.

La password era nel codice dell'app, poche righe prima del punto in cui il file viene caricato:

text
1const-string v3, "2"

Un carattere, presente come letterale nel bytecode decompilato. Una chiamata a openssl dopo:

text
1Chiave privata RSA, 2048 bit, 2 fattori primi
2Soggetto: C=SA, ST=Jeddah, L=Jeddah, O=Nusuk.sa, CN=eshbeata@staq.io
3Emittente: C=SA, ST=Riyadh, L=Riyadh, O=The Saudi National Bank, OU=Finto,
4 CN=Application Issuer
5Seriale: 0x24 (36)
6Valido: 2026-04-27 -> 2027-04-27
7Utilizzo chiave esteso (critico): Autenticazione client TLS Web

Un certificato client attivo emesso da una banca, valido per un altro anno, destinato ad autenticare un client presso un server tramite TLS.

Non era solo. Lo stesso percorso di codice è il componente wallet, impacchettato come com.walletstaq e integrato nel Trustless SDK di Staq Technologies, la società che gestisce la piattaforma BaaS Finto di SNB. Altri tre valori erano presenti in chiaro:

con l'ambito OAuth richiesto:

text
1identity accounts cards verification kyc cardpay transfers

Entrambe le credenziali erano anche concatenate in un'unica stringa e passate a un logger di debug, insieme all'URL di base.

(Non pubblico il materiale della chiave o i valori segreti completi. Ciò che conta qui è la forma del problema.)

Perché è grave, in parole semplici

Pensa all'API della banca come a una porta con due serrature.

La prima serratura è il mutual TLS. Normalmente un server dimostra la sua identità con un certificato. Con mTLS, anche tu devi dimostrare la tua identità al server con un certificato. Questo è il file .pfx: il certificato e la chiave privata che dimostra che lo possiedi. Dovrebbe essere la cosa che solo il sistema client legittimo possiede.

La seconda serratura è il client secret OAuth, la password che l'applicazione usa per richiedere un token di accesso all'API della banca.

Entrambe le serrature erano incluse in un'app gratuita su Google Play, e la chiave per la scatola che conteneva la prima era la cifra 2.

Ho confermato che l'host di destinazione applica effettivamente mTLS: l'handshake TLS con api.baas.alahli.com richiede certificati client (invia Acceptable client certificate CA names) e il certificato del server riporta CN=*.baas.alahli.com, O=The Saudi National Bank. Quindi non era un certificato decorativo per un ambiente di test. Era la credenziale per la porta principale di un'API bancaria di produzione, con ambiti che coprono identità, conti, carte, KYC, pagamenti con carta e trasferimenti.

A parte la fuga di dati, c'è un problema di progettazione sottostante. Un certificato client distribuito identico a dieci milioni di dispositivi non può distinguere un'installazione da un'altra. Ogni copia presenta la stessa credenziale, quindi il certificato dice alla banca quale app sta chiamando e niente su chi sta chiamando. Le credenziali per un'API come questa dovrebbero stare dietro il tuo backend: l'app parla con il tuo server, il tuo server parla con la banca.

Cosa non ho fatto

Ho eseguito esattamente un controllo contro l'endpoint del token (POST /api/tppa/token) per vedere se le credenziali fossero attive. Ha restituito HTTP 403 da nginx. Lo stesso ha fatto una richiesta senza alcun certificato, e lo stesso ha fatto l'URL radice nudo. Si tratta di un blocco a livello di rete posto davanti all'API, quasi certamente geografico, e non dice nulla sul fatto che le credenziali funzionino.

Dall'esterno dell'Arabia Saudita, non ho potuto determinare se queste credenziali fossero attive. Mi sono fermato lì. Qualsiasi ulteriore passo sarebbe stato un tentativo di aggirare il controllo di accesso di una banca, e il reperimento non dipende da questo: una chiave privata e un segreto OAuth bancario con ambito di trasferimento in un artefatto pubblicamente scaricabile è il reperimento, indipendentemente dal fatto che io possa o meno raggiungere l'endpoint.

Tentativo di segnalazione

Questa è la parte che ha reso virale il tweet, ed è la metà più interessante.

Ho cercato un modo per segnalarlo responsabilmente. Ecco cosa esiste:

Canale

Risultato

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

Non esiste

Pagina di segnalazione vulnerabilità Saudi CERT (cert.gov.sa)

Reindirizza a NCA; pagina di segnalazione propria chiusa

Modulo vulnerabilità NCA (haseen.gov.sa)

Non raggiungibile dalla Germania: timeout, blocco geografico

bugbounty.sa

Programma chiuso, HTTP 403 dall'esterno

HackerOne / Bugcrowd

Nessun programma per Nusuk, il Ministero o Elm

Contatti elenco app store

Indirizzi di supporto, nessun mandato di sicurezza

Niente di tutto ciò mi ha lasciato un canale con un mandato di sicurezza che potessi effettivamente raggiungere. Ho comunque inviato un'email. La risposta dal Supporto Haseen:

"L'accesso al portale Haseen è limitato agli utenti all'interno del Regno dell'Arabia Saudita. Per qualsiasi ulteriore richiesta, puoi contattarci tramite il servizio 'We Care' disponibile sul portale ufficiale Haseen."

Il portale Haseen, che è la cosa che non posso raggiungere. Ho risposto spiegando che non sono saudita, che si tratta di un certificato bancario privato esposto in un'app governativa e che volevo solo consegnarlo. La risposta, ancora una volta, è stata che il modulo funziona solo per i cittadini del KSA.

Quindi ho presentato un rapporto a CERT/CC tramite la loro piattaforma VINCE come intermediario di coordinamento (VRF#26-08-DXMKL), limitato a questo solo reperimento. È la strada da percorrere quando la parte interessata non ha un canale raggiungibile.

E poi ho twittato a riguardo, soprattutto per frustrazione.

Il tweet ha raggiunto 1,5 milioni di visualizzazioni. Nel giro di poche ore, il Supporto Haseen mi ha inviato un'email, senza sollecitazione, sullo stesso thread che mi aveva detto due volte che il portale non era per me:

"Secondo il nostro team competente, ti preghiamo di fornirci maggiori dettagli sulla vulnerabilità di sicurezza."

Ho inviato tutti i dettagli. Preferisco che la cosa venga risolta piuttosto che avere ragione sulla procedura.

La correzione

Il successivo aggiornamento dell'app è arrivato sia su Android che su iOS. Ho scaricato la nuova build Android (17.5.0, versionCode 156635) direttamente da Play e l'ho confrontata con quella che avevo analizzato. Tre controlli:

  1. Nessun contenitore di certificati in nessun punto del nuovo set APK: nessun file con estensione .pfx, .p12, .pkcs12, .jks, .bks, .pem o .key. Ho anche hashato il vecchio nusuk.pfx e l'ho confrontato byte per byte con ogni file della stessa dimensione nella nuova build, nel caso fosse stato semplicemente rinominato. Nessuna corrispondenza.
  2. Nessuna delle credenziali conosciute. Ho cercato i vecchi valori esatti per URL di base, ambito, ID client e client secret in tutti i file DEX, librerie native, asset, XML, JSON e risorse raw. Zero risultati per tutti e quattro.
  3. Nessun percorso di codice. La versione 17.4.9 conteneva 4.571 file nei pacchetti com.walletstaq e com.trustless; la 17.5.0 ne contiene zero. I marcatori baas.alahli, tppa/token, nusuk.pfx, il soggetto del certificato e il nome dell'emittente restituiscono tutti zero risultati nella build decodificata.

L'intero wallet e l'integrazione BaaS sono stati rimossi. Il controllo di rinomina nel passaggio 1 è ciò che esclude la possibilità che i valori siano stati semplicemente spostati altrove nel pacchetto.

La parte che nessuno dall'esterno può verificare

Rimuovere un segreto da un'app non invalida il segreto. Le vecchie copie APK rimangono disponibili per sempre e il certificato era valido fino ad aprile 2027. Quindi la domanda aperta è se sia stato revocato e se il segreto OAuth sia stato ruotato.

Ho cercato un modo per verificarlo in modo indipendente. Non ce n'è uno, e la ragione è di per sé un reperimento.

Il certificato non contiene alcuna estensione crlDistributionPoints, quindi non viene referenziato alcun elenco di revoca. Il suo unico endpoint di revoca è:

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

.cluster.local è il suffisso DNS interno di un cluster Kubernetes. Per definizione non è instradabile su internet pubblico e risolve in NXDOMAIN da qualsiasi luogo al di fuori di quel cluster, su HTTP semplice sulla porta 8080.

Quindi lo stato di revoca di questo certificato non può essere verificato dall'esterno dell'infrastruttura della banca, perché non c'è nulla qui fuori da interrogare. Per qualsiasi parte facente affidamento al di fuori di quel singolo cluster, i certificati di questo emittente sono effettivamente irrevocabili, il che merita un paragrafo nella revisione dell'architettura di qualcuno.

Lo stesso campo ha anche pubblicato il nome del cluster, il namespace, il nome del servizio e la porta della PKI di produzione della piattaforma BaaS di una banca, in un'app distribuita a dieci milioni di persone.

Solo SNB, Finto o Staq possono confermare la rotazione. Il fornitore dichiara che le credenziali sono state ruotate. Non ho modo di verificarlo in modo indipendente.

Cronologia

Data (2026)

Evento

29 agosto

APK analizzato, reperimento confermato localmente

29 agosto

Rapporto inviato via email; Haseen risponde che il portale è riservato agli utenti in KSA

31 agosto

Tentativi ripetuti, stessa risposta. Twitto a riguardo; ~1,5 milioni di visualizzazioni

1 settembre

Haseen riapre il thread senza sollecitazione, chiede dettagli. Dettagli inviati

1 settembre

Rapporto presentato anche a CERT/CC VINCE (VRF#26-08-DXMKL) come intermediario

1 settembre

Versione 17.5.0 pubblicata su Google Play

3 settembre

Nuovo test sul set APK 17.5.0 non modificato conferma la rimozione completa

8 settembre

Questo articolo

Non posso provare che l'aggiornamento sia stato causato dal mio rapporto. La 17.5.0 potrebbe essere già stata in preparazione. Quello che posso mostrare è che il materiale era nella 17.4.9 e non è nella 17.5.0.

Cosa imparare da questo

  1. Nascondere qualcosa nell'app non è un confine di sicurezza. Non nelle risorse, non in un .so nativo, non dietro offuscamento o una password memorizzata nello stesso binario. Se l'app può leggerlo, anche tutti coloro che installano l'app possono farlo. Diversi team lo imparano pubblicamente ogni anno.
  2. Un certificato client condiviso non è autenticazione. Se dieci milioni di dispositivi presentano lo stesso certificato, dice quale app sta chiamando e niente su chi sta chiamando, e l'app è un file che chiunque può scaricare. Le credenziali per un'API di terze parti, soprattutto di una banca, devono stare su un server che controlli.
  3. Geofencing del tuo canale di divulgazione delle vulnerabilità è di per sé una vulnerabilità. Gli aggressori non compilano moduli. Se l'unico modo per segnalare un difetto in un'app pubblicata a livello globale per dieci milioni di persone è essere fisicamente all'interno di un paese, allora le persone che non possono raggiungere il modulo sono esattamente quelle da cui desideri di più sentire. Ci è voluto un tweet virale per aprire un canale che un file security.txt avrebbe aperto gratuitamente.

Ho analizzato il set APK disponibile pubblicamente del Play Store in modalità ospite, senza account e senza dati personali reali. Non ho tentato di bypassare il blocco a livello di rete posto davanti all'API bancaria. Ogni valore in questo articolo è strutturale (percorsi, nomi di classi, metadati del certificato) o oscurato; nessun materiale di chiave privata e nessun segreto completo viene pubblicato.

Salva con un clic

Leggi in profondità gli articoli virali con l’AI di YouMind

Salva la fonte, fai domande mirate, riassumi l’argomentazione e trasforma un articolo virale in note riutilizzabili in un unico spazio di lavoro AI.

Scopri YouMind
Per i creator

Trasforma il tuo Markdown in un articolo 𝕏 pulito

Quando pubblichi i tuoi testi lunghi, formattare immagini, tabelle e blocchi di codice per 𝕏 è una seccatura. YouMind trasforma un'intera bozza Markdown in un articolo 𝕏 pulito e pronto da pubblicare.

Prova Markdown verso 𝕏

Altri pattern da decodificare

Articoli virali recenti

Esplora altri articoli virali