10M+ yükleme sayısına sahip bir devlet uygulamasının içindeki 2.835 baytlık bir dosya, Suudi Ulusal Bankası için canlı bir istemci sertifikası barındırıyordu. Bununla ilgilenecek birini bulmak ise viral bir tweet gerektirdi.
ÖZET
Resmi Nusuk uygulaması (com.moh.nusukapp, Hac ve Umre Bakanlığı, 10M+ yükleme, Google Play'in "Devlet" rozetini taşıyor), özel bir RSA anahtarı ve Suudi Ulusal Bankası tarafından düzenlenmiş bir istemci sertifikası içeren bir PKCS#12 dosyasıyla birlikte geldi. Bu dosyanın şifresi, uygulamanın kendi kodunda birkaç satır ötede sabit kodlanmıştı. Tek bir karakterdi: 2.
Hemen yanında, düz metin olarak, bankanın Bankacılık-Hizmet-Olarak (BaaS) API'si için OAuth2 istemci kimliği ve istemci sırrı yer alıyordu ve identity accounts cards verification kyc cardpay transfers kapsamlarını talep ediyordu.
Google Play'den uygulamayı indiren herkes tüm bunlara sahipti.
Bunu bildirmeye çalıştım. Bana güvenlik açığı portalının yalnızca Suudi Arabistan içindeki kullanıcılar için erişilebilir olduğu söylendi. Ben de tweet attım. Tweet 1,5 milyon görüntüleme aldı ve aniden aynı portal ayrıntıları istedi. Bir gün sonra kimlik bilgileri uygulamadan kaldırıldı.
https://x.com/iam_zachi/status/2094445016194207745
Bu yazı yalnızca bankacılık bulgusunu kapsamaktadır. Aşağıdaki her şey yayındaki uygulamada düzeltilmiştir ve satıcı tarafı kimlik bilgilerinin döndürüldüğünü belirtmektedir.
Nusuk Nedir?
Nusuk, Suudi hükümetinin Hac ve Umre için resmi platformudur. Hac izinlerini, e-vizeleri, rezervasyonları ve Nusuk Kartı'nı yönetir. Hac ve Umre Bakanlığı tarafından işletilir, Google Play'de doğrulanmış bir devlet uygulaması olarak işaretlenmiştir ve on milyondan fazla yüklemeye sahiptir. Ayrıca Suudi Ulusal Bankası ile birlikte oluşturulan ve Suudi Arabistan merkez bankası SAMA tarafından onaylanan bir cüzdan özelliği olan Nusuk Wallet'ı da içerir. Cüzdan, bu makalenin konusu olan kısımdır.
Bulgular
APK setini doğrudan Google Play'den (sürüm 17.4.9, versionCode 131215) çektim ve paketini açtım. Olağanüstü bir şey yoktu: standart Kotlin/Compose, paketleyici yok, anlamlı bir karartma yok.
Uygulamanın kaynaklarının içinde, res/raw/nusuk.pfx konumunda 2.835 baytlık bir PKCS#12 kapsayıcısı bulunuyordu. Bir .pfx dosyası, bir sertifika ve özel anahtarının bir şifreyle şifrelenmiş standart paket formatıdır.
Şifre, uygulamanın kodunda, dosyanın yüklendiği yerden birkaç satır ötedeydi:
1const-string v3, "2"
Ayrıştırılmış bayt kodunda bir değişmez değer olarak duran tek bir karakter. Bir openssl çağrısı sonrası:
1RSA private key, 2048 bit, 2 prime factors2Subject: C=SA, ST=Jeddah, L=Jeddah, O=Nusuk.sa, CN=eshbeata@staq.io3Issuer: C=SA, ST=Riyadh, L=Riyadh, O=The Saudi National Bank, OU=Finto,4 CN=Application Issuer5Serial: 0x24 (36)6Valid: 2026-04-27 -> 2027-04-277Extended Key Usage (critical): TLS Web Client Authentication
Bir banka tarafından düzenlenmiş, bir yıl daha geçerli, bir istemciyi bir sunucuya TLS üzerinden doğrulamak için tasarlanmış canlı bir istemci sertifikası.
Yalnız değildi. Aynı kod yolu, com.walletstaq olarak paketlenmiş ve SNB'nin Finto BaaS platformunu yürüten Staq Technologies'in Trustless SDK'sına bağlı olan cüzdan bileşenidir. Orada düz metin olarak üç değer daha duruyordu:
- CLIENT_ID = 379cc899…
- CLIENT_SECRET = ELrfNe9w… (64 ham bayt, base64 kodlu)
- SERVER_URL = https://api.baas.alahli.com/api/
istenen OAuth kapsamı ile birlikte:
1identity accounts cards verification kyc cardpay transfers
Her iki kimlik bilgisi de tek bir dizede birleştirilmiş ve temel URL ile birlikte bir hata ayıklama günlükçüsüne verilmişti.
(Anahtar malzemeyi veya tam gizli değerleri yayınlamıyorum. Burada önemli olan sorunun şeklidir.)
Bunun Neden Kötü Olduğu, Açık Bir Dille
Banka API'sini iki kilitli bir kapı olarak düşünün.
İlk kilit karşılıklı TLS'dir (mTLS). Normalde bir sunucu, kimliğini size bir sertifikayla kanıtlar. mTLS ile siz de kimliğinizi sunucuya bir sertifikayla kanıtlamak zorundasınız. İşte bu .pfx dosyasıdır: sertifika ve ona sahip olduğunuzu kanıtlayan özel anahtar. Bunun yalnızca meşru istemci sisteminde bulunması gereken şey olduğu varsayılır.
İkinci kilit, uygulamanın bankanın API'sinden bir erişim jetonu istemek için kullandığı şifre olan OAuth istemci sırrıdır.
Her iki kilit de Google Play'deki ücretsiz bir uygulamanın içinde gönderildi ve ilkini tutan kutunun anahtarı 2 rakamıydı.
Hedef ana bilgisayarın gerçekten mTLS'yi zorunlu kıldığını doğruladım: api.baas.alahli.com ile TLS anlaşması istemci sertifikaları talep ediyor (Kabul Edilebilir istemci sertifikası CA adlarını gönderiyor) ve sunucu sertifikası CN=*.baas.alahli.com, O=The Saudi National Bank olarak okunuyor. Yani bu, bir test ortamı için dekoratif bir sertifika değildi. Kimlik, hesaplar, kartlar, KYC, kart ödemeleri ve transferleri kapsamlarını kapsayan bir üretim bankacılık API'sinin ön kapısının kimlik bilgisiydi.
Sızıntıdan ayrı olarak, altında yatan bir tasarım sorunu var. On milyon cihaza aynı şekilde gönderilen bir istemci sertifikası, bir kurulumu diğerinden ayırt edemez. Her kopya aynı kimlik bilgisini sunar, bu nedenle sertifika bankaya hangi uygulamanın aradığını söyler, kimin aradığı hakkında hiçbir şey söylemez. Bunun gibi bir API için kimlik bilgileri kendi arka uç sisteminizin arkasında olmalıdır: uygulama sizin sunucunuzla, sizin sunucunuz da bankayla konuşur.
Yapmadığım Şeyler
Kimlik bilgilerinin canlı olup olmadığını görmek için jeton bitiş noktasına (POST /api/tppa/token) karşı tam olarak bir kontrol çalıştırdım. nginx'ten HTTP 403 döndürdü. Hiç sertifika olmadan yapılan bir istek de aynı şekilde yanıt verdi ve çıplak kök URL de öyle. Bu, API'nin önünde duran ve neredeyse kesinlikle coğrafi olan ağ düzeyinde bir engeldir ve kimlik bilgilerinin çalışıp çalışmadığı hakkında hiçbir şey söylemez.
Suudi Arabistan dışından bu kimlik bilgilerinin aktif olup olmadığını belirleyemedim. Orada durdum. Daha fazlası, bir bankanın erişim kontrolünü aşma girişimi olurdu ve bulgu buna bağlı değildir: herkese açık olarak indirilebilir bir yapıtta bulunan özel bir anahtar ve transfer kapsamına sahip bir bankacılık OAuth sırrı, şahsen uç noktaya ulaşabilsem de ulaşamasam da bulgudur.
Bildirmeye Çalışmak
Tweet'in viral olmasına neden olan kısım budur ve daha ilginç olan yarısıdır.
Bunu sorumlu bir şekilde bildirmenin bir yolunu aradım. Var olanlar:
Kanal
Sonuç
nusuk.sa, haj.gov.sa, hajj.nusuk.sa adreslerinde security.txt
Mevcut değil
Suudi CERT (cert.gov.sa) güvenlik açığı bildirim sayfası
NCA'ya yönlendiriyor; kendi bildirim sayfası kapatılmış
NCA güvenlik açığı formu (haseen.gov.sa)
Almanya'dan erişilemiyor: zaman aşımı, coğrafi engelleme
bugbounty.sa
Kapalı program, dışarıdan HTTP 403
HackerOne / Bugcrowd
Nusuk, Bakanlık veya Elm için program yok
Uygulama mağazası listeleme iletişim bilgileri
Destek adresleri, güvenlik yetkisi yok
Bunların hiçbiri bana güvenlik yetkisi olan ve gerçekten ulaşabileceğim bir kanal bırakmadı. Yine de e-posta gönderdim. Haseen Support'tan gelen yanıt:
"Haseen portalına erişim, Suudi Arabistan Krallığı içindeki kullanıcılarla sınırlıdır. Daha fazla sorunuz için resmi Haseen Portalı'nda bulunan 'Önemsiyoruz' hizmeti aracılığıyla bizimle iletişime geçebilirsiniz."
Ulaşamadığım şey olan Haseen Portalı. Suudi olmadığımı, bunun bir devlet uygulamasında ifşa edilmiş özel bir banka sertifikası olduğunu ve sadece teslim etmek istediğimi açıklayarak cevap yazdım. Cevap yine formun yalnızca KSA vatandaşları için çalıştığıydı.
Bunun üzerine, koordinasyon aracısı olarak CERT/CC'ye VINCE platformları aracılığıyla (VRF#26-08-DXMKL), yalnızca bu bulguya odaklanarak bir rapor gönderdim. Etkilenen tarafın kendine ait ulaşılabilir bir kanalı olmadığında izlenen yol budur.
Ve sonra, çoğunlukla hayal kırıklığından, bunun hakkında tweet attım.
Tweet 1,5 milyon görüntüleme aldı. Saatler içinde, Haseen Support, portalın benim için olmadığını iki kez söylemiş olan aynı e-posta dizisinde, talep edilmeksizin bana e-posta gönderdi:
"İlgili ekibimize göre, lütfen bize güvenlik açığı hakkında daha fazla ayrıntı sağlayın."
Tüm ayrıntıları gönderdim. Süreç hakkında haklı olmaktansa sorunun çözülmesini tercih ederim.
Düzeltme
Bir sonraki uygulama güncellemesi hem Android hem de iOS'ta yayınlandı. Yeni Android yapısını (17.5.0, versionCode 156635) doğrudan Play'den çektim ve analiz ettiğimle karşılaştırdım. Üç kontrol:
- Yeni APK setinde hiçbir yerde sertifika kapsayıcısı yok: .pfx, .p12, .pkcs12, .jks, .bks, .pem veya .key uzantılı dosya yok. Ayrıca eski nusuk.pfx'in hash'ini çıkardım ve yeniden adlandırılmış olması ihtimaline karşı yeni yapıdaki aynı boyuttaki her dosyayla bayt bayt karşılaştırdım. Eşleşme yok.
- Bilinen kimlik bilgilerinin hiçbiri yok. Temel URL, kapsam, istemci kimliği ve istemci sırrı için eski değerlerin aynısını tüm DEX dosyalarında, yerel kütüphanelerde, varlıklarda, XML, JSON ve ham kaynaklarda aradım. Dördünde de sıfır sonuç.
- Kod yolu yok. Sürüm 17.4.9,
com.walletstaqvecom.trustlesspaketleri altında 4.571 dosya içeriyordu; 17.5.0 sıfır içeriyor.baas.alahli,tppa/token,nusuk.pfx, sertifika konusu ve düzenleyen adı işaretleri, kodu çözülmüş yapıda sıfır sonuç veriyor.
Tüm cüzdan ve BaaS entegrasyonu kaldırıldı. 1. adımdaki yeniden adlandırma kontrolü, değerlerin pakette başka bir yere taşınmış olma ihtimalini ortadan kaldırır.
Dışarıdan Kimsenin Doğrulayamayacağı Kısım
Bir uygulamadan bir sırrı kaldırmak, sırrı geçersiz kılmaz. Eski APK kopyaları sonsuza kadar kullanılabilir kalır ve sertifika Nisan 2027'ye kadar geçerliydi. Bu nedenle açık soru, iptal edilip edilmediği ve OAuth sırrının döndürülüp döndürülmediğidir.
Bunu bağımsız olarak kontrol etmenin bir yolunu aradım. Yok ve bunun nedeni de başlı başına bir bulgu.
Sertifika, herhangi bir crlDistributionPoints uzantısı taşımaz, bu nedenle hiçbir iptal listesine atıfta bulunulmaz. Tek iptal uç noktası:
1OCSP - URI: http://finto-ocsp-responder.prod.svc.cluster.local:8080/api/v1/ocsp
.cluster.local, bir Kubernetes kümesinin dahili DNS sonekidir. Tanımı gereği genel internette yönlendirilemez ve bu kümenin dışındaki herhangi bir yerden, düz HTTP üzerinden 8080 numaralı bağlantı noktasında NXDOMAIN olarak çözümlenir.
Dolayısıyla, bu sertifikanın iptal durumu bankanın altyapısı dışından kontrol edilemez, çünkü burada sorgulanacak bir şey yoktur. Bu tek kümenin dışındaki herhangi bir güvenen taraf için, bu düzenleyiciden gelen sertifikalar etkili bir şekilde iptal edilemez; bu da başlı başına birinin mimari incelemesinde bir paragrafı hak eder.
Aynı alan ayrıca, on milyon kişiye dağıtılan bir uygulamada, bir bankanın BaaS platformunun üretim PKI'sının küme adını, ad alanını, hizmet adını ve bağlantı noktasını da yayınladı.
Yalnızca SNB, Finto veya Staq döndürmeyi onaylayabilir. Satıcı tarafı, kimlik bilgilerinin döndürüldüğünü belirtmektedir. Bunu bağımsız olarak doğrulamanın bir yolum yok.
Zaman Çizelgesi
Tarih (2026)
Olay
29 Ağu
APK analiz edildi, bulgu yerel olarak doğrulandı
29 Ağu
Rapor e-postayla gönderildi; Haseen portalın KSA içindeki kullanıcılarla sınırlı olduğunu yanıtladı
31 Ağu
Tekrarlanan girişimler, aynı yanıt. Tweet attım; ~1,5 milyon görüntüleme
1 Eyl
Haseen talep edilmeksizin konuyu yeniden açtı, ayrıntıları istedi. Ayrıntılar gönderildi
1 Eyl
Rapor ayrıca CERT/CC VINCE'e (VRF#26-08-DXMKL) aracı olarak iletildi
1 Eyl
Sürüm 17.5.0 Google Play'de yayınlandı
3 Eyl
Değiştirilmemiş 17.5.0 APK setinde yeniden test, tamamen kaldırıldığını doğruladı
8 Eyl
Bu yazı
Güncellemenin benim raporumdan kaynaklandığını kanıtlayamam. 17.5.0 zaten yolda olabilir. Gösterebileceğim şey, malzemenin 17.4.9'da olduğu ve 17.5.0'da olmadığıdır.
Bundan Çıkarılacak Dersler
- Bir şeyi uygulamanın içine gizlemek bir güvenlik sınırı değildir. Kaynaklarda, yerel bir .so dosyasında, karartma veya aynı ikili dosyada saklanan bir şifrenin arkasında da değildir. Uygulama okuyabiliyorsa, uygulamayı yükleyen herkes de okuyabilir. Birçok ekip bunu her yıl kamuoyu önünde öğrenir.
- Paylaşılan bir istemci sertifikası kimlik doğrulama değildir. On milyon cihaz aynı sertifikayı sunuyorsa, bu hangi uygulamanın aradığını söyler, kimin aradığı hakkında hiçbir şey söylemez ve uygulama herkesin indirebileceği bir dosyadır. Bir üçüncü taraf API'si için, özellikle de bir bankanınki için kimlik bilgileri, sizin kontrol ettiğiniz bir sunucuya ait olmalıdır.
- Güvenlik açığı bildirim kanalınızı coğrafi olarak sınırlamak, başlı başına bir güvenlik açığıdır. Saldırganlar form doldurmaz. Küresel olarak on milyon kişiye yayınlanan bir uygulamadaki bir kusuru bildirmenin tek yolu fiziksel olarak bir ülkenin içinde olmaksa, o zaman forma ulaşamayan insanlar tam olarak en çok duymak istediğiniz kişilerdir. Bir security.txt dosyasının ücretsiz olarak açacağı bir kanalı açmak için viral bir tweet gerekti.
Genel olarak erişilebilen Play Store APK setini misafir modunda, hesap olmadan ve gerçek kişisel veri olmadan analiz ettim. Bankacılık API'sinin önündeki ağ düzeyindeki engeli aşmaya yönelik herhangi bir girişimde bulunmadım. Bu makaledeki her değer ya yapısaldır (yollar, sınıf adları, sertifika meta verileri) ya da sansürlenmiştir; hiçbir özel anahtar malzemesi ve hiçbir tam sır yayınlanmamıştır.





