सरकारी ऐप के अंदर एक 2,835-बाइट फ़ाइल, जिसके 10M+ इंस्टॉल हैं, में सऊदी नेशनल बैंक का एक लाइव क्लाइंट सर्टिफिकेट था। किसी को इसकी ओर ध्यान दिलाने के लिए एक वायरल ट्वीट की ज़रूरत पड़ी।
संक्षेप में (TL;DR)
आधिकारिक Nusuk ऐप (com.moh.nusukapp, हज और उमरा मंत्रालय, 10M+ इंस्टॉल, Google Play का "सरकारी" बैज रखता है) में एक PKCS#12 फ़ाइल भेजी गई थी जिसमें एक निजी RSA कुंजी और सऊदी नेशनल बैंक द्वारा जारी एक क्लाइंट सर्टिफिकेट था। उस फ़ाइल का पासवर्ड ऐप के अपने कोड में कुछ पंक्तियाँ दूर हार्डकोड किया गया था। यह एक अकेला अक्षर था: 2।
इसके बगल में, सादे टेक्स्ट में, बैंक के Banking-as-a-Service API के लिए OAuth2 क्लाइंट ID और क्लाइंट सीक्रेट थे, जो scopes identity accounts cards verification kyc cardpay transfers का अनुरोध करते थे।
Google Play से ऐप डाउनलोड करने वाले किसी भी व्यक्ति के पास यह सब कुछ था।
मैंने इसकी रिपोर्ट करने की कोशिश की। मुझे बताया गया कि भेद्यता पोर्टल केवल सऊदी अरब के अंदर के उपयोगकर्ताओं के लिए उपलब्ध है। इसलिए मैंने इसके बारे में ट्वीट किया। ट्वीट को 1.5M व्यूज़ मिले, और अचानक उसी पोर्टल ने विवरण मांगा। एक दिन बाद क्रेडेंशियल ऐप से गायब हो गए।
https://x.com/iam_zachi/status/2094445016194207745
यह लेख केवल बैंकिंग खोज को कवर करता है। नीचे दी गई सभी चीज़ें शिपिंग ऐप में ठीक कर दी गई हैं, और विक्रेता पक्ष का कहना है कि क्रेडेंशियल को बदल दिया गया है।
Nusuk क्या है
Nusuk हज और उमरा के लिए सऊदी सरकार का आधिकारिक प्लेटफ़ॉर्म है। यह तीर्थयात्रा परमिट, ई-वीज़ा, बुकिंग और Nusuk कार्ड को संभालता है। यह हज और उमरा मंत्रालय द्वारा संचालित है, Google Play पर एक सत्यापित सरकारी ऐप के रूप में चिह्नित है, और इसके दस मिलियन से अधिक इंस्टॉल हैं। इसमें एक वॉलेट सुविधा, Nusuk Wallet भी शामिल है, जो सऊदी नेशनल बैंक के साथ मिलकर बनाई गई है और SAMA, सऊदी केंद्रीय बैंक द्वारा अनुमोदित है। यह लेख वॉलेट के बारे में है।
खोज
मैंने सीधे Google Play से APK सेट (संस्करण 17.4.9, versionCode 131215) खींचा और इसे अनपैक किया। इसमें कुछ भी असाधारण नहीं था: मानक Kotlin/Compose, कोई पैकर नहीं, कोई सार्थक अस्पष्टता नहीं।
ऐप के संसाधनों के अंदर, res/raw/nusuk.pfx पर, एक 2,835-बाइट PKCS#12 कंटेनर था। एक .pfx फ़ाइल एक प्रमाणपत्र और उसकी निजी कुंजी के लिए मानक बंडल प्रारूप है, जो एक पासवर्ड से एन्क्रिप्टेड है।
पासवर्ड ऐप के कोड में था, उस फ़ाइल के लोड होने के स्थान से कुछ पंक्तियाँ दूर:
1const-string v3, "2"
एक अक्षर, डीकंपाइल्ड बाइटकोड में एक शाब्दिक के रूप में बैठा हुआ। एक openssl कॉल बाद में:
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
एक बैंक द्वारा जारी एक लाइव क्लाइंट सर्टिफिकेट, एक और वर्ष के लिए वैध, TLS पर क्लाइंट को सर्वर से प्रमाणित करने के लिए अभिप्रेत है।
यह अकेला नहीं था। वही कोड पथ वॉलेट घटक है, जो com.walletstaq के रूप में पैकेज किया गया है और Staq Technologies से Trustless SDK में वायर्ड है, जो कंपनी SNB के Finto BaaS प्लेटफ़ॉर्म चलाती है। तीन और मान स्पष्ट पाठ में वहाँ बैठे थे:
- CLIENT_ID = 379cc899…
- CLIENT_SECRET = ELrfNe9w… (64 रॉ बाइट्स, base64-एन्कोडेड)
- SERVER_URL = https://api.baas.alahli.com/api/
अनुरोधित OAuth स्कोप के साथ:
1identity accounts cards verification kyc cardpay transfers
दोनों क्रेडेंशियल को एक स्ट्रिंग में जोड़ा गया और बेस URL के साथ एक डीबग लॉगर को सौंप दिया गया।
(मैं कुंजी सामग्री या पूर्ण गुप्त मान प्रकाशित नहीं कर रहा हूँ। यहाँ जो मायने रखता है वह समस्या का आकार है।)
यह बुरा क्यों है, सरल शब्दों में
बैंक के API को दो तालों वाले दरवाजे के रूप में सोचें।
पहला ताला पारस्परिक TLS है। आम तौर पर एक सर्वर एक प्रमाणपत्र के साथ आपको अपनी पहचान साबित करता है। mTLS के साथ, आपको भी एक प्रमाणपत्र के साथ सर्वर को अपनी पहचान साबित करनी होती है। वह .pfx फ़ाइल है: प्रमाणपत्र और निजी कुंजी जो साबित करती है कि आप इसके मालिक हैं। यह वह चीज़ मानी जाती है जो केवल वैध क्लाइंट सिस्टम के पास होती है।
दूसरा ताला OAuth क्लाइंट सीक्रेट है, वह पासवर्ड जो एप्लिकेशन बैंक के API से एक्सेस टोकन माँगने के लिए उपयोग करता है।
दोनों ताले Google Play पर एक मुफ्त ऐप के अंदर भेजे गए थे, और पहले वाले को रखने वाले बॉक्स की चाबी अंक 2 थी।
मैंने पुष्टि की कि लक्ष्य होस्ट वास्तव में mTLS लागू करता है: api.baas.alahli.com के साथ TLS हैंडशेक क्लाइंट सर्टिफिकेट का अनुरोध करता है (यह Acceptable client certificate CA names भेजता है), और सर्वर सर्टिफिकेट CN=*.baas.alahli.com, O=The Saudi National Bank पढ़ता है। तो यह परीक्षण वातावरण के लिए एक सजावटी प्रमाणपत्र नहीं था। यह एक उत्पादन बैंकिंग API के सामने वाले दरवाजे का क्रेडेंशियल था, जिसमें पहचान, खाते, कार्ड, KYC, कार्ड भुगतान और स्थानांतरण को कवर करने वाले स्कोप थे।
रिसाव से अलग, इसके नीचे एक डिज़ाइन समस्या है। एक क्लाइंट सर्टिफिकेट जो दस मिलियन उपकरणों पर समान रूप से भेजा जाता है, एक इंस्टॉलेशन को दूसरे से अलग नहीं कर सकता है। हर प्रति एक ही क्रेडेंशियल प्रस्तुत करती है, इसलिए सर्टिफिकेट बैंक को बताता है कि कौन सा ऐप कॉल कर रहा है और कॉल करने वाले के बारे में कुछ नहीं बताता है। इस तरह के API के लिए क्रेडेंशियल आपके अपने बैकएंड के पीछे होने चाहिए: ऐप आपके सर्वर से बात करता है, आपका सर्वर बैंक से बात करता है।
मैंने क्या नहीं किया
मैंने यह देखने के लिए टोकन एंडपॉइंट (POST /api/tppa/token) के खिलाफ ठीक एक जाँच चलाई कि क्या क्रेडेंशियल लाइव थे। इसने nginx से HTTP 403 लौटाया। बिना किसी सर्टिफिकेट के अनुरोध के साथ भी ऐसा ही हुआ, और नंगे रूट URL के साथ भी। यह API के सामने बैठा एक नेटवर्क-स्तरीय ब्लॉक है, लगभग निश्चित रूप से भौगोलिक, और यह इस बारे में कुछ नहीं कहता है कि क्रेडेंशियल काम करते हैं या नहीं।
सऊदी अरब के बाहर से, मैं यह निर्धारित नहीं कर सका कि ये क्रेडेंशियल सक्रिय थे या नहीं। मैं वहीं रुक गया। इससे आगे कुछ भी बैंक के एक्सेस कंट्रोल को बायपास करने का प्रयास होता, और खोज इस पर निर्भर नहीं करती है: एक निजी कुंजी और ट्रांसफर स्कोप वाला एक बैंकिंग OAuth सीक्रेट सार्वजनिक रूप से डाउनलोड करने योग्य आर्टिफैक्ट में बैठा है, यही खोज है, भले ही मैं व्यक्तिगत रूप से एंडपॉइंट तक पहुँच सकूं या नहीं।
रिपोर्ट करने का प्रयास
यह वह हिस्सा है जिसने ट्वीट को वायरल बना दिया, और यह अधिक दिलचस्प आधा है।
मैंने जिम्मेदारी से इसकी रिपोर्ट करने का एक तरीका खोजा। जो मौजूद है:
चैनल
परिणाम
nusuk.sa, haj.gov.sa, hajj.nusuk.sa पर security.txt
मौजूद नहीं है
सऊदी CERT (cert.gov.sa) भेद्यता रिपोर्टिंग पृष्ठ
NCA पर रीडायरेक्ट करता है; अपना रिपोर्टिंग पृष्ठ बंद कर दिया
NCA भेद्यता फ़ॉर्म (haseen.gov.sa)
जर्मनी से पहुँच योग्य नहीं: टाइमआउट, जियो-ब्लॉक किया गया
bugbounty.sa
बंद प्रोग्राम, बाहर से HTTP 403
HackerOne / Bugcrowd
Nusuk, मंत्रालय या Elm के लिए कोई प्रोग्राम नहीं
ऐप स्टोर लिस्टिंग संपर्क
सहायता पते, कोई सुरक्षा जनादेश नहीं
इनमें से किसी ने भी मुझे सुरक्षा जनादेश वाला एक चैनल नहीं छोड़ा जिस तक मैं वास्तव में पहुँच सकता था। मैंने फिर भी ईमेल किया। Haseen Support से जवाब:
"Haseen पोर्टल तक पहुँच सऊदी अरब साम्राज्य के भीतर के उपयोगकर्ताओं तक सीमित है। किसी भी आगे की पूछताछ के लिए आप आधिकारिक Haseen पोर्टल पर उपलब्ध 'We Care' सेवा के माध्यम से हमसे संपर्क कर सकते हैं।"
Haseen पोर्टल, जो वह चीज़ है जिस तक मैं नहीं पहुँच सकता। मैंने समझाते हुए वापस लिखा कि मैं सऊदी नहीं हूँ, कि यह एक सरकारी ऐप में उजागर एक निजी बैंक प्रमाणपत्र है, और मैं बस इसे सौंपना चाहता था। जवाब, फिर से, यह था कि फ़ॉर्म केवल KSA के नागरिकों के लिए काम करता है।
इसलिए मैंने CERT/CC को उनके VINCE प्लेटफ़ॉर्म के माध्यम से एक समन्वय मध्यस्थ के रूप में (VRF#26-08-DXMKL) एक रिपोर्ट दर्ज की, जो केवल इस खोज तक सीमित थी। जब प्रभावित पक्ष के पास अपना कोई सुलभ चैनल नहीं होता है तो आप यही रास्ता अपनाते हैं।
और फिर मैंने इसके बारे में ट्वीट किया, ज्यादातर हताशा से बाहर।
ट्वीट को 1.5M व्यूज़ मिले। कुछ ही घंटों में, Haseen Support ने मुझे उसी थ्रेड पर, बिना माँगे, ईमेल किया, जिसने मुझे दो बार बताया था कि पोर्टल मेरे लिए नहीं है:
"हमारी प्रासंगिक टीम के अनुसार, कृपया हमें सुरक्षा भेद्यता के बारे में अधिक विवरण प्रदान करें।"
मैंने पूरा विवरण भेजा। मैं प्रक्रिया के बारे में सही होने के बजाय चीज़ को ठीक करवाना पसंद करूंगा।
समाधान
अगला ऐप अपडेट Android और iOS दोनों पर आया। मैंने नया Android बिल्ड (17.5.0, versionCode 156635) सीधे Play से खींचा और इसे अपने विश्लेषण किए गए बिल्ड से अलग किया। तीन जाँचें:
- नए APK सेट में कहीं भी कोई सर्टिफिकेट कंटेनर नहीं: .pfx, .p12, .pkcs12, .jks, .bks, .pem, या .key एक्सटेंशन वाली कोई फ़ाइल नहीं। मैंने पुराने nusuk.pfx को हैश भी किया और इसकी तुलना नए बिल्ड में समान आकार की हर फ़ाइल से बाइट दर बाइट की, यदि इसे केवल नाम बदल दिया गया हो। कोई मेल नहीं।
- कोई भी ज्ञात क्रेडेंशियल नहीं। मैंने सभी DEX फ़ाइलों, नेटिव लाइब्रेरीज़, एसेट्स, XML, JSON और रॉ संसाधनों में बेस URL, स्कोप, क्लाइंट ID और क्लाइंट सीक्रेट के सटीक पुराने मान खोजे। चारों के लिए शून्य हिट।
- कोई कोड पथ नहीं। संस्करण 17.4.9 में
com.walletstaqऔरcom.trustlessपैकेजों के तहत 4,571 फ़ाइलें थीं; 17.5.0 में शून्य हैं। मार्करbaas.alahli,tppa/token,nusuk.pfx, सर्टिफिकेट विषय और जारीकर्ता का नाम डीकोडेड बिल्ड में शून्य हिट लौटाते हैं।
पूरे वॉलेट और BaaS एकीकरण को हटा दिया गया था। चरण 1 में नाम बदलने की जाँच यह नियम बनाती है कि मान बस पैकेज में कहीं और स्थानांतरित नहीं हुए हैं।
वह हिस्सा जिसे बाहर कोई सत्यापित नहीं कर सकता
किसी ऐप से एक गुप्त जानकारी हटाने से वह गुप्त जानकारी अमान्य नहीं हो जाती। पुरानी APK प्रतियां हमेशा उपलब्ध रहती हैं, और सर्टिफिकेट अप्रैल 2027 तक वैध था। तो खुला प्रश्न यह है कि क्या इसे रद्द कर दिया गया था और क्या OAuth सीक्रेट को बदल दिया गया था।
मैं स्वतंत्र रूप से इसकी जाँच करने का एक तरीका खोज रहा था। कोई नहीं है, और इसका कारण अपने आप में एक खोज है।
सर्टिफिकेट में कोई crlDistributionPoints एक्सटेंशन नहीं है, इसलिए किसी रद्दीकरण सूची का कोई संदर्भ नहीं दिया गया है। इसका एकमात्र रद्दीकरण एंडपॉइंट है:
1OCSP - URI: http://finto-ocsp-responder.prod.svc.cluster.local:8080/api/v1/ocsp
.cluster.local एक Kubernetes क्लस्टर का आंतरिक DNS प्रत्यय है। यह परिभाषा के अनुसार सार्वजनिक इंटरनेट पर रूट करने योग्य नहीं है, और यह उस क्लस्टर के बाहर कहीं से भी NXDOMAIN को हल करता है, प्लेन HTTP पर पोर्ट 8080 पर।
इसलिए इस सर्टिफिकेट की रद्दीकरण स्थिति को बैंक के बुनियादी ढांचे के बाहर से जाँचा नहीं जा सकता है, क्योंकि यहाँ पूछताछ करने के लिए कुछ भी नहीं है। उस एक क्लस्टर के बाहर किसी भी आश्रित पक्ष के लिए, इस जारीकर्ता के सर्टिफिकेट प्रभावी रूप से अप्रतिसंहरणीय हैं, जो किसी के आर्किटेक्चर समीक्षा में अपने आप में एक पैराग्राफ का हकदार है।
उसी फ़ील्ड ने दस मिलियन लोगों को वितरित एक ऐप में एक बैंक के BaaS प्लेटफ़ॉर्म के उत्पादन PKI के क्लस्टर नाम, नेमस्पेस, सेवा नाम और पोर्ट को भी प्रकाशित किया।
केवल SNB, Finto, या Staq ही रोटेशन की पुष्टि कर सकते हैं। विक्रेता पक्ष का कहना है कि क्रेडेंशियल बदल दिए गए हैं। मेरे पास स्वतंत्र रूप से इसे सत्यापित करने का कोई तरीका नहीं है।
समयरेखा
दिनांक (2026)
घटना
अगस्त 29
APK का विश्लेषण किया गया, खोज की स्थानीय रूप से पुष्टि की गई
अगस्त 29
रिपोर्ट ईमेल की गई; Haseen ने उत्तर दिया कि पोर्टल KSA के अंदर के उपयोगकर्ताओं तक सीमित है
अगस्त 31
बार-बार प्रयास, वही उत्तर। मैंने इसके बारे में ट्वीट किया; ~1.5M व्यूज़
सितंबर 1
Haseen ने बिना माँगे थ्रेड को फिर से खोला, विवरण मांगा। विवरण भेजा गया
सितंबर 1
रिपोर्ट CERT/CC VINCE (VRF#26-08-DXMKL) को मध्यस्थ के रूप में भी दर्ज की गई
सितंबर 1
संस्करण 17.5.0 Google Play पर प्रकाशित हुआ
सितंबर 3
अपरिवर्तित 17.5.0 APK सेट पर पुनः परीक्षण ने पूर्ण निष्कासन की पुष्टि की
सितंबर 8
यह लेख
मैं यह साबित नहीं कर सकता कि अपडेट मेरी रिपोर्ट के कारण हुआ था। 17.5.0 पहले से ही पाइपलाइन में हो सकता है। मैं यह दिखा सकता हूँ कि सामग्री 17.4.9 में थी और 17.5.0 में नहीं है।
मैं इससे क्या लूंगा
- ऐप में कुछ छिपाना कोई सुरक्षा सीमा नहीं है। संसाधनों में नहीं, नेटिव .so में नहीं, अस्पष्टता या उसी बाइनरी में संग्रहीत पासवर्ड के पीछे नहीं। यदि ऐप इसे पढ़ सकता है, तो ऐप इंस्टॉल करने वाला हर कोई भी इसे पढ़ सकता है। कई टीमें हर साल सार्वजनिक रूप से यह सीखती हैं।
- एक साझा क्लाइंट सर्टिफिकेट प्रमाणीकरण नहीं है। यदि दस मिलियन उपकरण एक ही सर्टिफिकेट प्रस्तुत करते हैं, तो यह बताता है कि कौन सा ऐप कॉल कर रहा है और कॉल करने वाले के बारे में कुछ नहीं बताता है, और ऐप एक फ़ाइल है जिसे कोई भी डाउनलोड कर सकता है। तीसरे पक्ष के API, सबसे ऊपर एक बैंक के, के लिए क्रेडेंशियल आपके द्वारा नियंत्रित सर्वर पर होने चाहिए।
- अपने भेद्यता प्रकटीकरण चैनल को जियोफेंस करना अपने आप में एक भेद्यता है। हमलावर फ़ॉर्म नहीं भरते हैं। यदि दस मिलियन लोगों को वैश्विक रूप से प्रकाशित ऐप में किसी दोष की रिपोर्ट करने का एकमात्र तरीका शारीरिक रूप से एक देश के अंदर होना है, तो जो लोग फ़ॉर्म तक नहीं पहुँच सकते हैं, वे ठीक वही लोग हैं जिनसे आप सबसे अधिक सुनना चाहते हैं। एक वायरल ट्वीट ने एक चैनल खोलने के लिए लिया जो एक security.txt फ़ाइल मुफ्त में खोल देती।
मैंने सार्वजनिक रूप से उपलब्ध Play Store APK सेट का विश्लेषण गेस्ट मोड में किया, बिना किसी खाते और बिना किसी वास्तविक व्यक्तिगत डेटा के। मैंने बैंकिंग API के सामने नेटवर्क-स्तरीय ब्लॉक को बायपास करने का कोई प्रयास नहीं किया। इस लेख में प्रत्येक मान या तो संरचनात्मक (पथ, वर्ग नाम, सर्टिफिकेट मेटाडेटा) है या संपादित किया गया है; कोई निजी कुंजी सामग्री और कोई पूर्ण गुप्त जानकारी प्रकाशित नहीं की गई है।





