एक ओझा को बुलाओ! मेरे रोबोट के पास!

अवलोकन

डेवलपर्स की मदद करने के हमारे निरंतर लक्ष्य के रूप में व्यवसायों और उपभोक्ताओं के लिए सुरक्षित उत्पाद प्रदान करते हैं, हमने हाल ही में जांच की गई McAfee Advanced Threat Research (ATR) में यहां विषयों, रोबोमी ग्लोबल लिमिटेड द्वारा निर्मित एक टेलीकांफ्रेंस रोबोट, हमारे शोध ने हमें टेमी रोबोट में चार अलग-अलग कमजोरियों की खोज करने के लिए प्रेरित किया, जिसका वर्णन यह पेपर महान विस्तार से करेगा। इसमें शामिल है:

  1. CVE-2020-16170 – हार्ड-कोडेड क्रेडेंशियल्स का उपयोग
  2. CVE-2020-16168 – मूल मान्यता त्रुटि
  3. CVE-2020-16167 – महत्वपूर्ण समारोह के लिए गुम प्रमाणीकरण
  4. CVE-2020-16169 – चैनल के एक वैकल्पिक पथ का उपयोग करके प्रमाणीकरण बाईपास

साथ में, इन कमजोरियों का उपयोग एक दुर्भावनापूर्ण अभिनेता द्वारा टेमी की वीडियो कॉल पर जासूसी करने के लिए किया जा सकता है, एक अन्य उपयोगकर्ता के लिए इंटरसेप्ट कॉल और यहां तक ​​कि दूरस्थ रूप से संचालित टेमी – सभी शून्य प्रमाणीकरण के साथ।

McAfee की भेद्यता प्रकटीकरण नीति के अनुसार, हमने 5 मार्च, 2020 को रोबोटी ग्लोबल लिमिटेड को हमारे निष्कर्षों की सूचना दी। इसके तुरंत बाद, उन्होंने जवाब दिया और एटीआर के साथ एक सतत बातचीत शुरू की, जबकि उन्होंने हमारे प्रकटीकरण रिपोर्ट में उल्लिखित मितव्यों को अपनाने का काम किया। 15 जुलाई, 2020 तक, इन भेद्यताओं को सफलतापूर्वक समाप्त कर दिया गया – टेमी के रोबॉक्स ओएस के संस्करण 120 में और तिमिर एंड्रॉइड ऐप के 1.3.7931 के बाद के सभी संस्करणों में शमन किया गया। हम उनकी त्वरित प्रतिक्रिया और इस पूरी प्रक्रिया में सहयोग करने की इच्छा के लिए रोबोमी की सराहना करते हैं। हम अभी तक यह कहते हैं कि यह सबसे संवेदनशील, सक्रिय और कुशल विक्रेताओं में से एक है, McAfee के साथ काम करने की खुशी मिली है।

इस पत्र का उद्देश्य भेद्यता की खोज प्रक्रिया के एक लंबे समय के तकनीकी विश्लेषण के रूप में किया जाता है, वल्नों द्वारा संभव किए गए कारनामों और इस तरह के कारनामों का संभावित प्रभाव हो सकता है। इन निष्कर्षों के एक उच्च-स्तरीय, कम तकनीकी अवलोकन में रुचि रखने वालों को हमारे सारांश ब्लॉग पोस्ट का उल्लेख करना चाहिए।

अंतर्वस्तु

अवलोकन
अंतर्वस्तु
टेमी क्या है
टेम्परी का सामान्य संचालन
प्रारंभिक रेककन
पोर्ट स्कैनिंग
आवागमन पर कब्जा
एक शैल हो रही है
मंदिर का कोड ढूंढना
वीडियो कॉल के लिए कोड उलट
Brute-Forcing चैनल का नाम अटैक वेक्टर के रूप में
MQTT हमला क्षेत्रों की खोज
अवलोकन
टेमी फोन ऐप को संशोधित करना
रोबोट आईडी और MQTT विषयों के बीच संबंध
MQTT कॉल आमंत्रण संदेश कैसे प्रकाशित होते हैं?
अवरोधक कॉल
समस्या: टेमी ने मेरी कॉल का जवाब नहीं दिया
समाधान: एक मालिक बनें
एक OWNER जोड़ना: फोन ऐप का परिप्रेक्ष्य
एक मालिक जोड़ना: टेमी का परिप्रेक्ष्य
पता लगाएँ: चुपके से ओण्डी की संपर्क सूची
OWNER विशेषाधिकार प्राप्त करना
शोषण को परिष्कृत करना
प्रभाव
निष्कर्ष

टेमी क्या है?

रोबोट।

अंतिम सीमा रेखा।

एंड्रॉइड टैबलेट ’ब्रेन’ के लिए 4 फुट लंबे रोबोट के ऊपर बैठकर, टेमी बहुत सारे सेंसरों को एक छोटे फॉर्म फैक्टर में पैक करती है। इनमें 360 ° LIDAR, तीन अलग-अलग कैमरे, पांच निकटता सेंसर और यहां तक ​​कि एक जड़त्वीय मापन इकाई (IMU) सेंसर शामिल है, जो कि एक तरह का एक्सीलेरोमीटर + गायरोस्कोप + मैग्नेटोमीटर ऑल-इन-वन है। ये सभी किसी भी बाधाओं से बचने के दौरान एक स्थान के माध्यम से स्वायत्त रूप से स्थानांतरित करने की क्षमता के करीब कुछ देने के लिए एक साथ काम करते हैं। यदि यह सीढ़ियों और वक्रों की नापाक ताकतों के लिए नहीं है, तो मंदिर अजेय होगा।

रोबॉटेमी अपने रोबोट को मुख्य रूप से टेलीकांफ्रेंसिंग के लिए इस्तेमाल किया जाता है। टेमी वेबसाइट से जुड़े लेख विभिन्न उद्योगों में रोबोट के अनुप्रयोगों का वर्णन करते हैं: कनेक्टेड लिविंग ने हाल ही में बड़ी देखभाल में उपयोग के लिए टेमी के साथ भागीदारी की, एनवाईसी में केलॉग के कैफे ने “खुदरा अनुभव को बढ़ाने” के लिए टेमी को अपनाया, और कॉर्पोरेट स्टाफिंग कोलाबेरा टेमी को “का उपयोग करता है” क्रॉस-ऑफ़िस संचार में सुधार करें। ” “व्यक्तिगत रोबोट” के नारे के बावजूद, ऐसा प्रतीत होता है कि टेमी को उपभोक्ता के लिए डिज़ाइन किया गया है तथा एंटरप्राइज़ एप्लिकेशन, और यह बाद का है जो वास्तव में हमें McAfee एडवांस्ड थ्रेट रिसर्च में मिला है, जो इसमें रुचि रखते हैं एक शोध लक्ष्य के रूप में। मेडिकल स्पेस में इसकी बढ़ती मौजूदगी, जिसे टेमी के रचनाकारों ने एक महीने में 1,000 यूनिट तक उत्पादन करके समायोजित किया है, विशेष रूप से दिलचस्प है जिसे दूरदराज के डॉक्टर की यात्राओं की बहुत बढ़ी हुई मांग को देखते हुए। अपने उपयोगकर्ताओं के लिए एक समझौता किए गए टेम्परी का क्या मतलब होगा, चाहे वह व्यवसाय पर माता हो, या रोगी को रोबोटिक प्रॉक्सी के माध्यम से निदान किया जाए? हमने अपना प्रीऑर्डर रखा और पता लगाने के लिए सेट किया।

मंदिर का सामान्य संचालन

एक बार जब यह अंत में आ गया, तो हम इसे किसी भी उपयोगकर्ता की तरह स्थापित करने के लिए तैयार हो गए: हमने इसे अनबॉक्स किया, इसकी चार्जिंग डॉक में प्लग किया और इसे वाईफाई से जोड़ा। टेमी रोबोट का सामान्य संचालन उसके स्मार्ट फोन ऐप के उपयोग के माध्यम से किया जाता है, और पहले स्टार्टअप टेमी ने हमें ऐप के साथ एक क्यूआर कोड स्कैन करने के लिए प्रेरित किया। क्यूआर कोड को स्कैन करने के लिए उपयोग किया जाने वाला फोन टेमी का “एडमिन” बन जाता है, जिससे आप बस इसे कॉल करके रोबोट को रिमोट से नियंत्रित कर सकते हैं। टेमी के अपने विलक्षण व्यवस्थापक के बाहर कई संपर्क हो सकते हैं, और संपर्क बनना काफी सीधा है। जब भी आप टेमी फोन ऐप लॉन्च करते हैं, तो यह आपके फोन के कॉन्टैक्ट्स को स्कैन करता है और स्वचालित रूप से उन सभी नंबरों को जोड़ देता है, जो टेमी ऐप के संपर्क सूची में पंजीकृत हो गए हैं। यदि उन संपर्कों में से कोई भी एक टेमी व्यवस्थापक नहीं होता है, तो आप उस संपर्क को क्लिक करके अपने टेमी को कॉल कर सकते हैं, जैसा कि दिखाया गया है आकृति 1

चित्र 1: टेमी फोन ऐप से संपर्क का चयन करना

इस तरह, टेमी के एडमिन के अलावा फोन ऐप के उपयोगकर्ता अभी भी इस पद्धति का उपयोग करके टेमी रोबोट को कॉल कर सकते हैं। चूंकि एक टेमी के साथ कॉल शुरू करने से आप इसे अपने कैमरे के माध्यम से देखने और इसके माइक्रोफोन के माध्यम से सुनने के अलावा इसे संचालित कर सकते हैं, जिससे किसी को भी आपकी टेमी कॉल करने की क्षमता साबित हो सकती है … समस्याग्रस्त। टेमी के रचनाकार अपने FAQ पृष्ठ पर इस सटीक मुद्दे को संबोधित करते हैं, जैसा कि इसमें दिखाया गया है चित्र 2

चित्र 2: “क्या कोई मेरे मंदिर से जुड़ सकता है?”, मंदिर के FAQ पृष्ठ से

यहां पहली पंक्ति थोड़ी भ्रामक है, क्योंकि फोन के संपर्क के रूप में एक टेमी के एडमिन को जोड़ने के लिए पर्याप्त लगता है कि उस टेमी को कॉल करने के लिए – व्यवस्थापक को आपको काम करने के लिए फोन संपर्क के रूप में भी जोड़ने की आवश्यकता नहीं है। ऐसा कहा जा रहा है, इसका मतलब यह नहीं है कि कोई भी आपके मंदिर को नियंत्रित करना शुरू कर सकता है; “रोबोट की ओर से भौतिक अनुमति” के लिए आवश्यक है कि उपयोगकर्ताओं द्वारा किए गए कॉल के लिए, जो कि टेमी के व्यवस्थापक या स्पष्ट रूप से अधिकृत द्वारा अधिकृत न हों। व्यवहार में, यह टेमी स्क्रीन पर एक कॉल अधिसूचना से मेल खाती है जिसका उपयोगकर्ता या तो जवाब दे सकता है या अस्वीकार कर सकता है। दूसरे शब्दों में, यह आपके सेल फोन पर कोल्ड कॉल प्राप्त करने की तुलना में कम या ज्यादा “सुरक्षित” नहीं है।

अंतिम पंक्ति के रूप में, जो कुछ उपयोगकर्ताओं को रोबोट में “हॉप” करने के लिए विकल्प देने के लिए एक व्यवस्थापक की क्षमता को संदर्भित करता है, यह भी फोन ऐप के माध्यम से टेमी के संपर्क प्रविष्टि से “नए सदस्य को आमंत्रित करें” विकल्प का चयन करके किया जाता है। एप्लिकेशन में, और फिर ऐप की संपर्क सूची से “आमंत्रित” करने के लिए एक या अधिक उपयोगकर्ताओं का चयन, जैसा कि दिखाया गया है चित्र तीन

चित्र 3: फोन ऐप से उपयोगकर्ताओं को विशेष अनुमति कैसे दें

एक बार रोबोट और एक फ़ोन उपयोगकर्ता के बीच एक कॉल स्थापित हो जाने के बाद, जिसे या तो पार्टी शुरू कर सकती है, फ़ोन उपयोगकर्ता मैन्युअल रूप से रोबोट को ड्राइव करने, जोड़ने, हटाने और सहेजे गए स्थानों पर नेविगेट करने, सहेजे गए स्थानों के बीच गश्त, नियंत्रण करने जैसी चीजों को कर सकता है। मात्रा, और अधिक।

चित्र 4: फोन ऐप का उपयोग करके टेम्परी को चलाएं

टेमी के कॉल करने वालों के नियंत्रण के स्तर के कारण, टेमी रोबोट की हमारी जाँच के दौरान कॉलिंग की कार्यक्षमता तेज़ी से प्राथमिकता बन गई।

प्रारंभिक रेककन

पोर्ट स्कैनिंग

हालांकि एक रोबोट निश्चित रूप से हमारे विशिष्ट लक्ष्यों से थोड़ा अलग था, हमने अपनी टोही विधियों के साथ शुरू किया जो समय की कसौटी पर खड़े हुए हैं: पोर्ट स्कैन, पैकेट कैप्चर और एक स्थानीय शेल।

चित्र 5: तिमि पर चल रहा है

Nmap स्कैन में केवल एक ओपन पोर्ट का पता चला: TCP पोर्ट 4443। तुरंत हमें पता चला कि इस पोर्ट के Nmap का वर्गीकरण “आपके व्यवसाय के लिए सुरक्षित कार्यालय मुद्रण समाधान” फरो के लिए इस्तेमाल किया जा रहा है, यह लगभग निश्चित रूप से गलत था। इसके बजाय, यह संभावना थी कि यह बंदरगाह टीएलएस संचार के लिए मानक 443 के विकल्प के रूप में इस्तेमाल किया जा रहा था। जबकि यह जानना अच्छा है, इसने हमें इस बंदरगाह पर यातायात के प्रकार के बारे में बहुत कुछ नहीं बताया, या यहां तक ​​कि क्या सेवा ( s) उस ट्रैफ़िक को संभाल रहा था, इसलिए हम आगे बढ़ गए।

आवागमन पर कब्जा

हमारे चेकलिस्ट पर अगला था रोबोट के नेटवर्क ट्रैफ़िक के कुछ कैप्चर प्राप्त करना, एक अपडेट के दौरान, बूट के दौरान और वीडियो कॉल के दौरान उत्पन्न ट्रैफ़िक पर ध्यान केंद्रित करना।

जब टेंडी बूटिंग कर रहा था तब ट्रैफ़िक कैप्चर किया गया था, लेकिन अन्यथा आइडलिंग ने तीन अनन्य बाहरी IP पते दिखाए हैं: 98.137.246.7, 54.85.186.18, और 34.206.180.208।

इन IP पर nslookup चलाने से पता चला है कि पहले कुछ Yahoo! मीडिया सर्वर, संभवतः इसके समाचार ऐप के लिए उपयोग किया जाता है, जबकि अन्य दो एडब्ल्यूएस उदाहरण हैं।

चित्र 6: टेमी द्वारा एक्सेस किए गए IP पर nslookup चल रहा है

भेजे गए / प्राप्त किए जा रहे डेटा के लिए, देखने के लिए बहुत कुछ नहीं था। याहू! पता HTTP (पोर्ट 80) के माध्यम से एक्सेस किया जा रहा था, लेकिन टीसीपी पैकेट में कोई पेलोड नहीं था। AWS पतों के लिए, इन्हें TLS (पोर्ट 443) के माध्यम से एक्सेस किया जा रहा था और डेटा एन्क्रिप्ट किया गया था।

अपडेट के लिए, temi ने “temi-ota-updates.s3-accelerate.amazonaws.com” का उपयोग किया, जो लगभग निश्चित रूप से रोबोटीमी में लोगों द्वारा स्थापित एक कस्टम AWS उदाहरण था। AWS के अन्य ट्रैफ़िक की तरह, अपडेट को एन्क्रिप्ट किया जा रहा था।

एक शैल हो रही है

गहराई तक पहुंचाने के लिए, हमें अपने मंदिर पर एक स्थानीय शेल की आवश्यकता थी। सौभाग्य से, एंड्रॉइड डिबग ब्रिज या एडीबी के माध्यम से वायरलेस कनेक्शन, अपने टचस्क्रीन से टेमी की सेटिंग्स के माध्यम से सक्षम किया जा सकता है। बेहतर अभी भी, एडीबी के माध्यम से प्रदान किए गए शेल में रूट विशेषाधिकार थे, हालांकि डिवाइस स्वयं “जड़” नहीं था। यह संभवत: “टेमी डेवलपर्स” की सुविधा के लिए किया गया था, क्योंकि टेमी वेबसाइट में उपयोगकर्ताओं को अपने रोबोट के लिए कस्टम ऐप विकसित करने में मदद करने के लिए एक संपूर्ण पोर्टल है।

दुर्भाग्य से, एडीबी के माध्यम से दी गई डिफ़ॉल्ट शेल को काफी कम कर दिया गया था, और प्रत्येक रिबूट का मतलब होगा कि फिर से कनेक्ट करने में सक्षम होने से पहले अपने टचस्क्रीन से मैन्युअल रूप से टेमी के एडीबी पोर्ट को फिर से खोलना। इसके अलावा, इस पद्धति के लिए हमारे शेल तक पहुँचने के लिए टेमी सॉफ्टवेयर की आवश्यकता होती है, जो हमें बिना किसी सहारे के छोड़ देता अगर हमारा सॉफ्टवेयर किसी भी तरह से उस सॉफ्टवेयर को चला देता। यह आदर्श से बहुत दूर था।

जबकि एडीबी पुश जैसे कमांडों ने कस्टम एआरएम बायनेरीज़ को बैश, बिजीबॉक्स के लिए आगे बढ़ाया और टेम्परी पर काफी तुच्छ, बूट पर चलने के लिए कुछ भी प्राप्त करना अधिक चुनौतीपूर्ण साबित हुआ। शुरुआत के लिए, temi में मानक /etc/init.d/ निर्देशिका नहीं थी। इसके अतिरिक्त, प्राथमिक स्क्रिप्ट जो बूट पर चलेगी, जैसे /init.rc, बूट छवि से सीधे लोड की जाएगी, जिसका अर्थ है कि उनके द्वारा किया गया कोई भी संपादन रिबूट के पार नहीं रहेगा। यह एंड्रॉइड के SELinux सुरक्षा मॉडल के भाग के रूप में डिज़ाइन के द्वारा संभव था – अगर हमें टेम्पो को बूट पर SSH सर्वर शुरू करने के लिए राजी करना है तो हमें थोड़ा और रचनात्मक होना पड़ेगा।

/Init.rc की सामग्री के माध्यम से खुदाई, हालांकि, हमें एक सुराग दिया:

चित्र 7: /init.rc में दिलचस्प प्रविष्टि

Android की Init भाषा से अपरिचित लोगों के लिए,

“Init भाषा का उपयोग सादे पाठ फ़ाइलों में किया जाता है जो .rc फ़ाइल एक्सटेंशन लेती हैं। आमतौर पर सिस्टम में इनमें से कई स्थानों पर, नीचे वर्णित हैं।
/init.rc प्राथमिक .rc फ़ाइल है और इसके निष्पादन की शुरुआत में init निष्पादन योग्य द्वारा लोड की जाती है। यह सिस्टम के शुरुआती सेट के लिए जिम्मेदार है। ” – Android डॉक्स।

में प्रवेश दिखाया गया चित्र 7, टेम्पली के init.rc में शामिल सैकड़ों में से एक, सिस्टम को “फ्लैश_रेकवरी” को बूट के दौरान “/system/bin/install-recovery.sh” तर्क के साथ लॉन्च करने के लिए कहता है। “क्लास मेन” सिर्फ एक लेबल है जिसका उपयोग समूह प्रविष्टियों को एक साथ करने के लिए किया जाता है, और “ऑनशॉट” का अर्थ है “सेवा से बाहर निकलने पर इसे फिर से शुरू न करना।” यह प्रविष्टि हमारे लिए खड़ी थी क्योंकि यह / प्रणाली / बिन / में स्थित एक स्क्रिप्ट का आह्वान कर रही थी, जो कि थी नहीं बूट छवि से लोड किया गया है, जिसका अर्थ है कि कोई भी परिवर्तन चिपकना चाहिए जबकि / सिस्टम विभाजन केवल डिफ़ॉल्ट रूप से पढ़ा जाता है, हमारे रूट विशेषाधिकारों ने इसे अस्थायी रूप से रीमाउंट / सिस्टम के लिए तुच्छ बना दिया है ताकि निम्नलिखित पंक्ति को अंत तक जोड़ने के लिए रीड-राइट के रूप में लिखा जा सके: “/ system / bin / sshd”। इसके साथ, हमारे पास आगे की खोज करने के लिए हमारे मंदिर पर एक जड़ खोल पाने का एक विश्वसनीय साधन था।

मंदिर का कोड ढूंढना

हमने अपनी नई स्वतंत्रता के साथ जो पहली चीज की थी, वह थी नेटस्टैट:

चित्र 8: नेटस्टैट चलाना

ऐसा प्रतीत हुआ कि अधिकांश नेटवर्किंग, जिसमें ओपन पोर्ट 4443 शामिल था, जिसे हमने पहले नैंप का उपयोग करते हुए पाया था, “com.roboteam.teamy.usa” द्वारा नियंत्रित किया जा रहा था। अकेले नाम के आधार पर, यह संभवत: रोबोट पर चलने वाला मुख्य टेमी सॉफ्टवेयर था। इसके अतिरिक्त, यह नाम एक देशी बाइनरी की तुलना में एंड्रॉइड पैकेज के नाम की तरह दिखता था। हमने इसे चलाकर पुष्टि की:

चित्र 9: com /roboteam.teamy.usa के लिए बाइनरी को खोजने की कोशिश / में

app_process32 (या app_process64, वास्तुकला पर निर्भर करता है), एंड्रॉइड द्वारा एंड्रॉइड ऐप चलाने के लिए उपयोग किया जाने वाला बाइनरी है, जिसका अर्थ है कि हम एक एपीएफ नहीं बल्कि एक एपीके की तलाश में थे। इस धारणा के तहत, हमने इसके बजाय एंड्रॉइड के pm कमांड का उपयोग करके इस प्रक्रिया के लिए कोड खोजने की कोशिश की:

चित्र 10: “com.roboteam.teamy.usa” के लिए एपीके की तलाश

निश्चित रूप से, हमारे पास हमारा एपीके था।

आमतौर पर, एंड्रॉइड पर प्रत्येक इंस्टॉल किए गए ऐप में एक समान डेटा निर्देशिका होती है, और हम इस पैकेज के लिए /data/data/com.roboteam.teamy.usa के तहत एक खोजने में सक्षम थे:

चित्र 11: “com.roboteam.teamy.usa” के लिए डेटा निर्देशिका

पैकेज द्वारा उपयोग किए गए मूल / निर्देशिका में मूल कोड शामिल हैं:

चित्र 12: “com.roboteam.teamy.usa” द्वारा प्रयुक्त मूल कोड

इनमें से कई पुस्तकालयों में देखने के बाद, libagora-rtc-sdk-jni.so विशेष रूप से हमारे सामने खड़ा है क्योंकि यह Agora का हिस्सा था, एक ऐसा मंच जो आवाज, वीडियो और रियल-टाइम-मैसेजिंग (RTM) सेवाओं के लिए SDK प्रदान करता है । यह संभावना थी कि टेमी अपनी वीडियो कॉलिंग कार्यक्षमता को लागू करने के लिए अगरोरा का उपयोग कर रही थी – जिस कार्यक्षमता में हम सबसे अधिक रुचि रखते थे।

इस बाइनरी स्ट्रिंग्स को देखकर, हम यह भी निर्धारित करने में सक्षम थे कि टेमी अगोरा वीडियो एसडीके के 2.3.1 संस्करण का उपयोग कर रहे थे:

चित्र 13: स्ट्रिंग्स का उपयोग करके एगोरा एसडीके संस्करण का पता लगाना

बेशक, हम समान रूप से टेमी फोन ऐप के लिए कोड में रुचि रखते थे, जिसे हमने एपीके एक्सट्रैक्टर और एडीबी के उपयोग के माध्यम से प्राप्त किया था। हम यह भी देखने के लिए उत्सुक थे कि क्या एंड्रॉइड फोन ऐप में एगोरा एसडीके के समान संस्करण का उपयोग किया गया था, जिसे हमने उनके एमडी 5 हैश की तुलना करके जांचा था:

चित्र 14: टेमी रोबोट और टेमी फोन ऐप के बीच कामेच्छा के लिए एमडी 5 हैश की तुलना करना

निश्चित रूप से, वे समान थे।

वीडियो कॉल के लिए कोड उलट

अगला कदम था कि बेहतर तरीके से समझने के लिए कि उन्होंने कैसे काम किया। हमने फोन ऐप के कोड को देखकर शुरू करने का फैसला किया, क्योंकि रोबोट की तुलना में फोन ऐप के व्यवहार का परीक्षण करना अधिक आसान था।

एपीके को विघटित करने और उसका विश्लेषण करने के लिए, हमने JADX का उपयोग किया। हालाँकि, कई जावा डीकॉम्पिलर हैं जो एंड्रॉइड कोड पर काम करते हैं, JADX अपनी विभिन्न विशेषताओं के कारण हमारे लिए खड़ा है:

  • यह सीधे एपीके फाइलें खोल सकता है (.dex को .jar में कनवर्ट करेगा और एकाधिक .dex फ़ाइलों को स्वचालित रूप से मर्ज करेगा)
  • यह आधुनिक जावा सुविधाओं को संभालता है जैसे कि नेस्टेड कक्षाएं और इनलाइन लैम्ब्डा अधिकांश अन्य डिकंपाइलर्स से बेहतर है
  • देखे जा रहे किसी भी वर्ग के लिए, आप “smali” टैब पर क्लिक करके smali कोड देख सकते हैं
  • यह बाइटकोड में संबंधित .line निर्देशों के साथ सिंक्रनाइज़ किए गए लाइन नंबर को प्रदर्शित करता है, जिससे मूल कोड के साथ विघटित जावा कोड को मैप करना आसान हो जाता है।
  • आप इसके उपयोग या घोषणा को देखने के लिए किसी भी विधि, सदस्य या वर्ग पर राइट-क्लिक कर सकते हैं

सबसे गैर-तुच्छ एंड्रॉइड ऐप की तरह टेमी एंड्रॉइड फोन ऐप बड़े पैमाने पर था। अंधेरे में टटोलने और कुछ दिलचस्प कोड पर ठोकर खाने की उम्मीद के बजाय, हमने अधिक लक्षित दृष्टिकोण लेने का फैसला किया। शुरू से ही, हम जानते थे कि हमारा ध्यान मंदिर की कॉलिंग कार्यक्षमता पर था क्योंकि यह किसी भी हमलावर के लिए सबसे बड़ा प्रभाव प्रदान करेगा। हम अब यह भी जानते हैं कि टेमी इस कॉलिंग कार्यक्षमता को लागू करने के लिए एगोरा एसडीके का उपयोग कर रहे थे। एंड्रॉइड के लिए Agora के जावा एपीआई संदर्भ (v2.3.1) को देखने के बाद, हमने फैसला किया कि हमारी जांच शुरू करने के लिए फ़ंक्शन जॉचनेल () एक अच्छी जगह होगी:

चित्र 15: जॉचनेल () के लिए अगोरा का प्रलेखन

आईडीए में libagora-rtc-sdk-jni.so खोलकर और इसके निर्यात को देखते हुए, हम ऑफसेट 0xD4484 पर nativeJoinChannel () नामक एक फ़ंक्शन खोजने में सक्षम थे:

चित्र 16: लिबागोरा के कुछ निर्यात

JADX की खोज सुविधा का उपयोग करते हुए, हमने RtcEngineImpl में लाइन 960 पर स्थित विघटित कोड में समान नाम वाला एक फ़ंक्शन पाया:

चित्र 17: RtcEngineImpl में नेटिव अगोरा विधियाँ

यहां से, हमने वीडियो कॉल के लिए कॉल एंट्री का पता लगाने के लिए पीछे की ओर काम करने की थकाऊ प्रक्रिया शुरू की। हमने ऐसा करने के लिए खोज की विधि है जो nativeJoinChannel () को आमंत्रित किया है, फिर उस विधि की तलाश में है जो उस विधि को लागू करता है, और इसी तरह। हमारे अथक प्रयासों के लिए निवेश पर वापसी निम्नलिखित थी:

चित्र 18: टेमी फोन ऐप का उपयोग करके वीडियो कॉल करने के विभिन्न तरीकों के लिए कोड प्रवाह आरेख

उच्च स्तर पर, आउटगोइंग कॉल के लिए कोड में चार एंट्री-पॉइंट होते हैं, और ये मैप 1-टू -1 टेम्परी फोन ऐप से कॉल शुरू करने के चार तरीकों से होता है:

  1. एक संपर्क का चयन करें और “कॉल” मारा। यह उस संपर्क के फोन को सीधे कॉल करेगा, न कि उनके टेमी को। यह ग्राफ (हरे क्षेत्र) में उल्लिखित “संपर्क विवरण → संपर्क कॉल” कोड पथ से मेल खाती है।
  2. एक संपर्क का चयन करें और, अगर उनके पास एक टेमी रोबोट है, तो “कनेक्ट” दबाएं। यह उनके टेमी को बुलाएगा और ग्राफ (नारंगी क्षेत्र) में उल्लिखित “संपर्क विवरण → रोबोट कॉल” कोड पथ से मेल खाती है।
  3. ऐप के “रीसेंट” टैब पर जाएं और उन संपर्कों / रोबोटों में से एक का चयन करें जिन्हें आपने हाल ही में कॉल किया है या आपको बुलाया है। यह उस संपर्क या रोबोट को कॉल करेगा और ग्राफ (नीले क्षेत्र) में उल्लिखित “हाल के कॉल → कॉल” कोड पथ से मेल खाता है।
  4. यदि आप एक व्यवस्थापक हैं, तो “संपर्क” टैब पर “मेरा टेमी” शीर्षक के नीचे से अपनी टेम्पल चुनें और निम्न स्क्रीन से “कनेक्ट” बटन दबाएं, जिसे रोबोट विवरण स्क्रीन भी कहा जाता है। यह आपके टेमी को कॉल करेगा और ग्राफ (लाल क्षेत्र) में उल्लिखित “रोबोट विवरण → कॉल” कोड पथ से मेल खाता है।

रंगीन क्षेत्रों के भीतर निहित कक्षाएं और विधियाँ उन स्क्रीनों को प्रस्तुत करना संभालती हैं जिनसे कॉल शुरू की जा सकती हैं और इन बटनों को उस कोड से बाँधना है जो वास्तव में कॉलिंग को संभालती हैं। कोई बात नहीं प्रवेश बिंदु, सभी आउटगोइंग कॉल TelepresenceService.initiateCall () पर अभिसरण करते हैं, इसलिए यह इस पद्धति पर एक करीब से नज़र रखने के लायक है:

चित्र 19: TelepresenceService.initiateCall ()

और यहां चार तरीके दिए गए हैं:

संपर्क विवरण → संपर्क कॉल

चित्र 20: संपर्क विवरण से संपर्क आरंभ करें →

संपर्क विवरण → रोबोट कॉल

चित्र 21: संपर्क विवरण → रोबोट कॉल से आरंभ () आरंभ करना

हाल की कॉल → कॉल

चित्र 22: हाल की कॉल → कॉल से आरंभ () आरंभ करना

रोबोट विवरण → कॉल

चित्र 23: रोबोट विवरणों से आरंभ करने वाले आरंभ () कॉल →

पहला पैरामीटर एक आईडी है जिसका उपयोग कैली की पहचान करने के लिए किया जाता है। टेमी रोबोट के लिए, यह कुछ ऐसा प्रतीत होता है जिसे “रोबोट आईडी” कहा जाता है, जबकि संपर्कों के लिए इसे एक कॉल के माध्यम से प्राप्त किया जाता है getContactId ()। दिलचस्प है, हाल ही में कॉल के लिए, जो या तो संपर्क हो सकता है या एक रोबोट, ID को कॉल के माध्यम से प्राप्त किया जाता है, जिसे MM5PhoneNumber () कहा जाता है, जो कि थोड़ा अजीब है क्योंकि टमी एक फ़ोन नंबर नहीं है (जहाँ तक हम बता सकते हैं)। हम बाद में इस पर विस्तार करेंगे।

दूसरा पैरामीटर कॉलर के प्रकार को दर्शाते हुए एक स्ट्रिंग है। चूँकि हम यहाँ विशेष रूप से फ़ोन ऐप कोड देख रहे थे, इसलिए हमने मान लिया कि “टाइप यूजर” फोन ऐप का उपयोग करके एक कॉलर को दर्शाता है।

तीसरा पैरामीटर कैलली का प्रदर्शन नाम है – सीधे पर्याप्त।

चौथा और अंतिम पैरामीटर एक कस्टम एनम प्रकार का है जो कि दर्शाता है कॉल प्राप्त करने वाला का प्रकार। यदि कोई टेमी बुला रहा है, तो इसका प्रकार CallContactType.TEMI_CALL है; अन्यथा, यह CallContactType.CONTACT_CALL है। ध्यान दें कि हाल के कॉल प्रविष्टि बिंदु के लिए, यह मान गतिशील रूप से प्राप्त किया जाता है, जो समझ में आता है क्योंकि यह पथ संपर्क और मंदिर दोनों को कॉल हैंडल करता है।

यह भी ध्यान देने योग्य बात है कि आरंभ () आमंत्रण ऑब्जेक्ट का निर्माण करने के लिए InvitationManager.createInvitation () को आमंत्रित करता है, जो “कॉल आमंत्रण” का प्रतिनिधित्व करता है जिसे कैली को भेजा जाता है:

चित्र 24: InvitationManager.createInvitation ()

वहां से, जैसा कि रेखा 223 पर देखा गया है चित्र 19, आरंभ करता है () Utils.getRandomSessionId () के परिणाम के रूप में गुजरता है पहला पैरामीटर बनाने के लिए Invitation (), जो सेशन बन जाता है:

चित्र 25: Utils.getRandomSessionId ()

यह सब कर रहा है, यादृच्छिक रूप से 100,000 और 999,999 के बीच एक पूर्णांक उत्पन्न कर रहा है, समावेशी। दूसरे शब्दों में, sessionId हमेशा एक सकारात्मक, छह अंकों की दशमलव संख्या होती है।

इस मूल्य को एगोरा की नेटिवजेनचैननेल () को कॉल श्रृंखला के नीचे सभी तरह से ट्रेस करके, हमने पाया कि यह सेशनआईड “चैनलनेम” पैरामीटर में वर्णित है। चित्र 15 – उस विशेष कॉल के लिए चैटरूम के लिए विशिष्ट पहचानकर्ता। इस चैनल नाम के प्रसार को देखा जा सकता है चित्र 18; बोल्ड प्रत्येक विधि कॉल में पैरामीटर में चैनल का नाम होता है।

Brute-Forcing चैनल का नाम अटैक वेक्टर के रूप में

तो हमें अगोरा चैनल के नाम में इतनी दिलचस्पी क्यों थी? खैर, अगर हम पीछे मुड़कर देखें चित्र 15, हम देख सकते हैं कि यह चैनल में शामिल होने के लिए आवश्यक केवल दो क्षेत्रों में से एक है क्योंकि तीसरे और चौथे क्षेत्र को वैकल्पिक रूप से लेबल किया गया है। केवल अन्य आवश्यक फ़ील्ड “टोकन” है, लेकिन दस्तावेज़ को करीब से देखने से पता चलता है कि “अधिकांश परिस्थितियों में, स्थिर ऐप आईडी पर्याप्त है”, और उन मामलों में टोकन “वैकल्पिक है और इसे अशक्त के रूप में सेट किया जा सकता है”:

चित्र 26: जॉचनेल के “टोकन” पैरामीटर के लिए अगोरा प्रलेखन

हमारा अगला सवाल था, क्या टेंडी एक टोकन का उपयोग करता है या यह एक स्थिर ऐप आईडी का उपयोग करता है? हमने जल्दी से उस सवाल का जवाब दिया कि यह देखकर कि टेमी फोन ऐप joinChannel () को कैसे कॉल कर रहा है:

चित्र 27: अगोरा की जॉइनचैनेल () एपीआई फंक्शन को एगोरांगाइन से बुलाया जा रहा है। जॉइनचैनेल ()

निश्चित रूप से, टोकन पैरामीटर को शून्य करने के लिए सेट किया जा रहा है।

यह आशाजनक लगने लगा था – अगर स्टैटिक ऐप आईडी और चैनल का नाम सभी को एक एगोरा वीडियो कॉल में शामिल होने की आवश्यकता है और हम पहले से ही जानते थे कि टेमी के चैनल के नाम छह अंकों के मूल्यों तक सीमित हैं, तो यह मौजूदा टेमी में शामिल होने के लिए प्रशंसनीय हो सकता है जानवर बल के माध्यम से कॉल का मतलब है। हम सभी की जरूरत है यह “ऐप आईडी” है।

अगोरा डॉक्स को देखते हुए, हमने देखा कि कौन से एपीआई फ़ंक्शन वास्तव में इस ऐप आईडी का उपयोग करते हैं। जैसा कि यह निकला, केवल एक ही था: RtcEngine.create ()।

चित्रा 28: RtcEngine.create के लिए Agora प्रलेखन ()

संक्षेप में कहें, ऐप आईडी डेवलपर्स के लिए जारी किया गया एक स्थिर मूल्य है जो प्रत्येक परियोजना के लिए अद्वितीय है और अगोरा के सर्वर के विभिन्न उपयोगकर्ताओं को अलग करते हुए एक प्रकार के नामस्थान के रूप में उपयोग किया जाता है। यह सुनिश्चित करता है कि अन्य उपयोगकर्ताओं को कॉल करने के लिए टेमी उपयोगकर्ता केवल Agora का उपयोग कर सकते हैं। चूंकि कोई भी टेमी फोन ऐप उपयोगकर्ता किसी भी टेमी (या अन्य उपयोगकर्ता) को कॉल करने में सक्षम होना चाहिए, इसलिए सभी टेमी रोबोटों द्वारा साझा एक एकल ऐप आईडी होना चाहिए। हमने यह देखने का निर्णय लिया कि टेम्परी का कोड RtcEngine.create () को कैसे देख रहा है, यह देखने के लिए कि क्या हम ऐप आईडी को ट्रैक कर सकते हैं:

चित्र 29: AgoraEngine.ensureRtcEngineReadyLock ()

खैर, यह अच्छा नहीं है।

हमारे विचार में, यह पहले से ही भेद्यता थी, जिसे CVE-2020-16170 द्वारा चिह्नित किया गया था और इसका CVSS स्कोर 8.2 था। एक समर्पित हमलावर को सभी 900,000 संभावित चैनल नामों पर ध्यान देने में कोई समस्या नहीं होगी। इससे भी बुरी बात यह है कि ऐसा करने से हमलावर “स्प्रे और प्रार्थना” करते हैं, जिससे उन्हें अपने पीड़ितों के बारे में कुछ भी जानने की आवश्यकता के बिना किसी भी चल रहे टेंमी कॉल से कनेक्ट करने की अनुमति मिलती है।

हालांकि हम निश्चित रूप से उत्पादन सर्वर के खिलाफ इस तरह के कारनामे का परीक्षण नहीं कर सकते हैं, हमने यह परीक्षण करने का फैसला किया है कि क्या कोई हमलावर केवल ऐप आईडी और चैनल का नाम जानते हुए एक मौजूदा टेमी कॉल में शामिल हो सकता है। इसे सुविधाजनक बनाने के लिए, हमने अपने टेमी को कॉल करने के लिए एक एंड्रॉइड फोन का उपयोग किया, जिससे कॉल शुरू होने से पहले फोन पर लॉगकैट चलाना सुनिश्चित हो सके। ऐसा करने पर, हम आमंत्रण संदेश को चैनल के नाम (“सेशनआईड” लेबल) के साथ कैप्चर करने में सक्षम थे, क्योंकि यह OkHttp क्लाइंट को भेजा जा रहा था, जिसने तब इसे लॉग इन किया था:

चित्र 30: लॉगकैट का उपयोग करके कॉल के लिए चैनल का नाम खोजना

हार्डकोड ऐप आईडी और लॉग्स से प्राप्त चैनल नाम का उपयोग करके हम सफलतापूर्वक एक चालू कॉल में शामिल होने में सक्षम थे और ऑडियो और वीडियो दोनों को कम से कम एक कॉल करने वाले से प्राप्त कर सकते हैं, यह साबित करते हुए कि यह एक व्यवहार्य हमला वेक्टर है।

यद्यपि इस भेद्यता का दोहन Agora के वीडियो कॉलिंग API का उपयोग करता है, लेकिन इसका अस्तित्व SDK के विशिष्ट कार्यान्वयन के परिणामस्वरूप है। जबकि फोन ऐप में टेमी की एजोरा ऐप आईडी को हार्डकोड करने का निर्णय इस भेद्यता का मूल कारण है, इसका प्रभाव टोकन के उपयोग या चैनल नामों की व्यापक श्रेणी की अनुमति देकर काफी हद तक कम किया जा सकता है। यदि असंभव नहीं है, तो इनमें से किसी ने जानवर बल के हमले के वेक्टर को अविश्वसनीय रूप से कठिन बना दिया है।

MQTT हमला क्षेत्रों की खोज

अवलोकन

प्रत्येक संभावित चैनल आईडी की कोशिश करके मौजूदा टेमी कॉल्स में शामिल होने वाली एक ब्रूट-फोर्स पद्धति के बाहर, अधिक लक्षित वेक्टर वेक्टर होना उपयोगी होगा। इसके अलावा, ब्रूट-फोर्स मेथड का उपयोग करते हुए एक कॉल में शामिल होने से उपयोगकर्ताओं पर एक हमलावर जासूसी कर सकता है, यह उन्हें स्वयं रोबोट पर नियंत्रण प्रदान नहीं करेगा – हम एक ऐसा तरीका चाहते थे जो हमें दोनों करने दे। सौभाग्य से, इस स्तर का नियंत्रण टेम्परी रोबोट + फोन ऐप के सामान्य संचालन के दौरान पहले से ही उपलब्ध है।

यद्यपि टेमी वीडियो कॉल की सुविधा के लिए एगोरा का उपयोग करता है, रिंगिंग या अन्यथा के माध्यम से इनकमिंग कॉल के उपयोगकर्ताओं को सूचित करता है, यह एगोरा द्वारा लागू की गई विशेषता नहीं है। टेमी के मामले में, यह कार्यक्षमता MQTT का उपयोग करके कार्यान्वित की जाती है। MQTT “मशीन-टू-मशीन (M2M) / इंटरनेट ऑफ थिंग्स” संचार के लिए बनाया गया एक प्रकाशित / सदस्यता (पब / उप) कनेक्टिविटी प्रोटोकॉल है। इसमें, संचार को “विषयों” में वर्गीकृत किया जाता है और ग्राहक अपने सभी संबंधित संदेशों को प्राप्त करने के लिए इन विषयों को “सदस्यता” दे सकते हैं, इन विषयों के लिए अपने स्वयं के संदेश प्रकाशित कर सकते हैं, या दोनों। इन विषयों को एक पदानुक्रम में संरचित किया गया है जो कि यूनीक्स-शैली की फाइलसिस्टम नामकरण योजनाओं के समान है, जिसमें एक अलग स्लैश (“/”) है जो अलग-अलग विषय “स्तरों” को अलग करता है। क्लाइंट के बीच संचार को एक “ब्रोकर” के रूप में जाना जाता है, जिसे आमतौर पर एक दूरस्थ सर्वर के रूप में लागू किया जाता है, जो एक कनेक्शन और संदेश, संदेश वितरण, और क्लाइंट प्रमाणीकरण को स्थापित करने और बंद करने का काम करता है।

दुर्भाग्य से, कई बाधाएँ थीं जिन्हें दूर करने के लिए हमें अनधिकृत “हमलावरों” के रूप में मंदिर की कॉलिंग कार्यक्षमता का लाभ उठाने की आवश्यकता होगी।

सबसे पहले, एक लक्षित हमले विधि की आवश्यकता होती है, कम से कम, लक्ष्य की विशिष्ट पहचान का एक विश्वसनीय तरीका। दूसरे शब्दों में, यदि आप बॉब की टेमी को हैक करने में रुचि रखते हैं, तो आपको बॉब के टेमी को हर दूसरे टेमी से बाहर निकालने का एक तरीका चाहिए। एक नरम लेकिन समान रूप से महत्वपूर्ण आवश्यकता यह है कि इस पहचानकर्ता को एक ऐसा होना चाहिए जो एक हमलावर को प्राप्त कर सकता है। पहचानकर्ता को प्राप्त करना जितना कठिन होता है, उतना ही अधिक विवादित और अवास्तविक कोई भी हमला करने वाले वैक्टर जो इस पहचानकर्ता पर निर्भर होते हैं। एक उदाहरण के रूप में, एक नाम या फोन नंबर एक प्रशंसनीय पहचानकर्ता हो सकता है; एक सामाजिक सुरक्षा संख्या, कम।

अपने फोन ऐप कोड में टेमी के एमक्यूटीटी कार्यान्वयन को उलटने से एक संभावित संभावित पहचानकर्ता का पता चला था। हमने पाया कि प्रत्येक रोबोट के अपने MQTT विषय होते हैं जिन्हें वह सुनता है, जिन्हें उसके MQTT क्लाइंट आईडी का उपयोग करके पहचाना जाता है, अन्यथा इसे उसकी रोबोट आईडी के रूप में जाना जाता है। एक टेमी के लिए रोबोट आईडी को आसानी से फोन संपर्क के रूप में अपने उपयोगकर्ताओं में से एक को जोड़कर फोन ऐप के सामान्य संचालन के माध्यम से आसानी से प्राप्त किया जा सकता है, एक विधि जो हमने पहले वर्णित की थी। ऐसा इसलिए है क्योंकि टेमी फोन ऐप उपयोगकर्ता की संपर्क सूची में फोन नंबर पर पंजीकृत किसी भी टेमी को कॉल करने की अनुमति देता है, जिसके लिए उस टेमी की रोबोट आईडी की आवश्यकता होती है। यदि फोन ऐप में पहले से ही स्थानीय रूप से इस जानकारी तक पहुंच है, तो ऐप से इस जानकारी को खींचने के लिए काल्पनिक रूप से संभव होना चाहिए।

दूसरा, हमें टेम्परी के साथ संवाद करने का एक तरीका चाहिए था। यदि कॉलिंग को कुछ विषयों के लिए MQTT संदेशों के प्रकाशन के माध्यम से सुविधा प्रदान की जाती है, तो हमें इन समान विषयों के लिए अपने स्वयं के संदेशों को प्रकाशित करने का एक तरीका खोजने की आवश्यकता है। उस नोट पर, यदि सभी MQTT संदेशों को पहले ब्रोकर के पास से गुजरना चाहिए, तो हमें ब्रोकर को यह सोचने के लिए एक तरीका चाहिए कि हम एक विश्वसनीय MQTT क्लाइंट थे और किसी भी प्रमाणीकरण को बायपास करें जो कि हो सकता है।

इस बाधा को दूर करने के लिए एक तरीका यह होगा कि मौजूदा टेम्परी फोन ऐप को बदल दिया जाए। तृतीय पक्ष एंड्रॉइड एप्लिकेशन को संशोधित करना एक प्रसिद्ध प्रक्रिया है और इससे हमें इस कोड को खरोंच से लिखने के बजाय MQTT संदेश भेजने के लिए पहले से मौजूद कोड का लाभ मिलेगा। इसके अलावा, चूंकि फोन ऐप को कॉल करने के लिए टेमी के उपयोगकर्ताओं में से किसी एक के फोन नंबर से अधिक कुछ भी नहीं चाहिए (और इस प्रकार, उसके विषयों पर एमक्यूटीटी संदेश प्रकाशित करें), इसका मतलब है कि फोन ऐप में ब्रोकर के साथ प्रमाणीकरण का तरीका होना चाहिए। । यदि हम “विश्वसनीय” फ़ोन ऐप के संदर्भ में मनमाने विषयों पर कस्टम MQTT संदेश भेज सकते हैं, तो हमें प्रमाणीकरण के बारे में चिंता करने की आवश्यकता नहीं होगी।

इसने हमें हमारी तीसरी और अंतिम बाधा के साथ छोड़ दिया: विशेषाधिकार वृद्धि। यद्यपि कोल्ड रोबोट को कॉल करना मुश्किल नहीं है, लेकिन यह हमें अपने आप ही रोबोट को दूर से संचालित करने की क्षमता प्रदान नहीं करता है। ऐसा इसलिए है क्योंकि इस तरह से टेमी को कॉल करने से यह बजता है और इसके टचस्क्रीन के माध्यम से टेमी के अंत में कॉल को स्वीकार करने की आवश्यकता होती है। केवल टेमी का व्यवस्थापक, जो उपयोगकर्ता इसे क्यूआर कोड स्कैन के माध्यम से पंजीकृत करता है, और इस व्यवस्थापक द्वारा मैन्युअल रूप से चुने गए विशेषाधिकार प्राप्त उपयोगकर्ता सीधे दूसरे छोर पर किसी भी उपयोगकर्ता के संपर्क के बिना टेमी को नियंत्रित कर सकते हैं। इस प्रकार, हमें अपने विशेषाधिकार को बढ़ाने के लिए एक रास्ता खोजने की आवश्यकता थी, ताकि तिमिर हमारे कॉल को स्वचालित रूप से उठा ले।

यदि MQTT टेमी और उसके फोन ऐप उपयोगकर्ताओं के बीच संचार का प्राथमिक साधन है, और व्यवस्थापक सीधे फ़ोन ऐप से उपयोगकर्ताओं के विशेषाधिकार स्तर का प्रबंधन कर सकते हैं, तो इसका कारण यह है कि विशेषाधिकार प्रबंधन संभवतः MQTT के माध्यम से किया जाता है। इस प्रकार, अगर हम एक विशेषाधिकार वृद्धि MQTT संदेश को बिगाड़ने के लिए फोन ऐप को बदल सकते हैं, तो हम इस बाधा को भी दूर कर सकते हैं।

टेमी फोन ऐप को संशोधित करना

चूँकि हमारी पूरी योजना हम पर निर्भर थी, इसलिए हम बिना ऐप को तोड़े या बिना किसी संपर्क के विभिन्न रिमोट सर्वर से इसे प्रमाणित करने से रोकने के लिए फोन ऐप के बायोटेक को बदल सकते थे, हमने पहले पुष्टि करने का फैसला किया कि यह संभव था। इसे पूरा करने के लिए, हमने ADB, Apktool, Keytool और Jarsigner के संयोजन का उपयोग किया।

हमने Apktool का उपयोग करके टेम्परी फोन ऐप के लिए एपीके फ़ाइल को अनपैक करके शुरू किया:

चित्र 31: टेमी फोन ऐप को अनपैक करना

अगला, हमने उस फ़ाइल या कोड के लिए अनपैक्ड एपीके को खोजा जिसे हम संशोधित करना चाहते थे। हमारी प्रूफ-ऑफ-कॉन्सेप्ट के लिए, हमने “कॉल” बटन के लिए केवल लेबल को बदलने का फैसला किया, क्योंकि यह तुरंत स्पष्ट हो जाएगा कि यह काम किया है या नहीं। कई एंड्रॉइड ऐप में, बटन के लिए उपयोग किए जाने वाले तार आमतौर पर हार्डकोड नहीं होते हैं और इसके बजाय एक संसाधन फ़ाइल से लोड किए जाते हैं। इस स्थिति में, उन्हें फ़ाइल रेस / मान / स्ट्रिंग्स.xml से लोड किया गया था:

चित्र 32: strings.xml में कॉल बटन लेबल की खोज

ऐसा लग रहा था कि लाइन 108 में वह लेबल था जो हम बदलना चाहते थे। हमने बस “कॉल” को “PWN” से बदल दिया और हमारे परिवर्तनों को बचाया।

नोट: कम तुच्छ संशोधनों के लिए, जिन्हें बाद में हमें बनाने की आवश्यकता होगी, यह प्रक्रिया स्वाभाविक रूप से अधिक शामिल होगी। चूँकि यहां तक ​​कि सबसे परिष्कृत जावा डिकंपाइलर कोड का उत्पादन करने में असमर्थ हैं जो वास्तव में गैर-तुच्छ एप्लिकेशन के लिए संकलन करेंगे, सार्थक संशोधनों का मतलब आमतौर पर पढ़ना और संशोधित करना है। smaliAndroid के Dalvik bytecode के लिए कोडांतरक। कहा जा रहा है, हमने पाया कि टेमी जैसे जटिल ऐप के लिए सार्थक बदलाव करने का सबसे अच्छा तरीका है जावा पढ़ें, स्माली लिखें। By this, we mean that it’s better to do your reversing on decompiled Java code and only look at the nigh-hieroglyphic smali code once you know exactly what changes you want to make and what class to make it in.

Once our modification had been made, we used Apktool again to repack the APK:

Figure 33: Repacking our modified app

Our next step was to sign the modified APK. This is because Android refuses to install any unsigned APKs, even through ADB. First, we generated a key using Keytool:

Figure 34: Generating the key we will use to sign the modified app

and then we used our key to sign the APK using Jarsigner:

Figure 35: Signing the modified app

Finally, we installed the modified APK onto an Android phone using ADB:

Figure 36: Installing the modified app

After launching the app and selecting one of our contacts, we were greeted with a pleasant sight:

Figure 37: Testing the modified app

Furthermore, the change we made did not seem to impact the app’s functionality; we could still make and receive calls, add contacts, etc.

In our view, this was a vulnerability, later denoted by CVE-2020-16168 and having a CVSS score of 6.5. An altered and potentially malicious app could still access the various cloud resources and user information made available to the temi app because no integrity checks were performed by either the app or the remote servers to ensure that the app had not been modified. As we’ll demonstrate later, the presence of this vulnerability made various other attack vectors possible.

The Relationship Between Robot IDs and MQTT Topics

In the overview section, we made the claim that “each robot has its own MQTT topics that it listens on, which are identified using temi’s MQTT client ID, otherwise known as its robot ID.” Here we will outline how we came to this conclusion and the specific format of a few of the topics that temi and its phone app subscribe/publish to.

Since we knew that temi uses MQTT to handle calling notifications, a natural place to start our investigation was the code used by the phone app to initiate calls. Moving down the call chain, we saw that the robot ID was being passed to the sendInitInvitation() method of the TelepresenceService class:

Figure 38: InvitationManagerImpl.sendInitInvitation()

Here, the robot ID is used in two different places. On line 82, an MqttDelegateApi.Topic object is created with the name “users//status”, implying that each robot has its own MQTT topic category for its status and the robot ID itself is used to uniquely identify these topics। Next, on line 87, we see one of many RxJava functions (the others have been omitted) with the robot ID passed as a parameter to it. This function’s only purpose is to call InvitationManagerImpl.sendInviteMsg(), passing along the Invitation and the robot ID as its arguments:

Figure 39: InvitationManagerImpl.sendInviteMsg()

This function is of particular interest because we see the construction of another MQTT topic name on line 332, this time taking the form “client//invite”. Presumably, this is the topic format used when publishing call invitations to specific temi robots (and, likely, phone contacts).

Additionally, the anonymous function executed via doOnSuccess() uses MqttManager.publish() (line 359) to actually publish the call invitation on the callee’s call invite topic. This information would become useful when we later tried to send custom MQTT messages to our temi in order to facilitate privilege escalation.

How are MQTT Call Invite Messages Published?

If we were to publish our own MQTT messages, we would need a robust understanding of the code used by the temi phone app to publish arbitrary MQTT messages, which appears to be this MqttManagerImpl.publish() method.

To determine what was actually being passed to publish(), we needed to go through what each of the RxJava functions in InvitationManagerImpl.sendInviteMsg() was doing. Referring back to Figure 39:

  • On lines 331-339, Single.zip() is called, which simply creates a Pair from the MQTT topic string (“client//invite”) and the Invitation object.
  • On lines 340-355, Single.flatMap() is called, which adds a timestamp to the Invitation object inside the Pair.
  • Presuming successful execution of the previous flatMap() call, Single.doOnSuccess() is called on lines 356-372. As mentioned previously, this is where the call to MqttManagerImpl.publish() occurs. Since this doOnSuccess() operates on the value returned by the previous call to flatMap(), the arguments being passed to publish() are:
Argument विवरण
(String) pair.first The MQTT topic string
new Gson().toJson((Invitation) pair.second) The Invitation object as JSON
0 The integer “0”
असत्य The boolean “false”

While it was obvious that the first argument is the MQTT topic the message is being published on and the second argument is the message itself, it was not immediately obvious what the third and fourth arguments were for. Digging down into the source code of the MQTT package being used (org.eclipse.paho.client.mqttv3) revealed their purpose:

Figure 40: MqttAsyncClient.publish()

After passing through a couple MqttManagerImpl methods, the four arguments listed above become the first four arguments passed to this internal publish() method. The JSON string (second argument), is converted from a string to a byte array in the interim; the rest of the arguments are unchanged.

Knowing this, it was clear that the second argument is indeed the MQTT message, the third argument is the QoS, and the fourth argument is a flag that specifies whether or not the message should be retained. A QoS value of “0” means that the message will be delivered to clients subscribed to the topic at most once. A retained flag of “false” means that new subscribers to the topic will not receive the most recent published message upon subscription.

Intercepting Calls

As we’ve already established, every temi robot has a unique MQTT client ID and many of the topics it subscribes to contain this ID to indicate that they are intended for that specific robot. If users of the temi phone app can receive calls in addition to making them, it stands to reason that they must also have a unique MQTT client ID – a robot ID equivalent. If there was an easy way to discover the client ID of another phone app user, it might be possible to subscribe to their topics and thus receive calls intended for them, making it worthy of investigation.

If we refer back to Figure 22, we saw that if a call is initiated from the Recent Calls screen, a method called getMd5PhoneNumber() is used to obtain the client ID of the callee. While a temi doesn’t have a phone number to speak of, we began to suspect that the client ID for users of the temi phone app might just be an MD5 hash of their phone number.

Although we could have followed the code in order to track down exactly where this value comes from, we thought it might be easier to simply verify our suspicions. To do this, we first took the MD5 hash of the temi admin’s phone number and then performed a string search for this hash in every temi-related file we had access to.

Figure 41: The Google Voice number used to register with temi

Figure 42: Taking the MD5 hash of the phone number

Sure enough, this hash appeared in two places. First, it appeared in the primary SQLite 3 database file for the temi app running on the robot itself. More specifically, it appears in the “RecentCallModel” table under the “userId” column for the table’s only entry. Based on the table’s name and sole entry, this is almost certainly used to store temi’s recent calls, as its admin was the only user it had called at this point.

Figure 43: The matching string in temi’s RecentCallModel table, part of its SQLite 3 Database

The second match was in the log output we saved after running logcat on our temi earlier, the same log output seen in Figure 30:

Figure 44: Matching strings in the logcat output recorded earlier

This log output appears to conclusively confirm our suspicions. With what we knew now, these log messages appeared to be JSON data being sent to/received from the MQTT broker. Moreover, the “topic” string in the first result exactly matched the MQTT topic format string found on line 82 of Figure 38। The only difference is that in that case, the string between “users/” and “/status” was the robot ID; here, it was an MD5 hash of the user’s phone number.

Since we now knew that

  1. temi robots and phone app users received calls on the topic “client//invite”, where “” is the MQTT client ID of the callee,
  2. the MQTT client ID for phone app users was simply an MD5 hash of the phone number used to register with the app, and
  3. we could alter the existing phone app,

it stood to reason that we could modify the app to subscribe to another user’s call invite topic in order to intercept calls intended for that user as long as we knew the user’s phone number. In theory, this would allow an attacker to effectively impersonate another user and spy on them by intercepting their calls. The question then became: What code needs to be modified in order to get this to happen? Well, we’re looking at a situation where temi initiates a call with its admin and an attacker attempts to intercept that call. In the case of calls initiated by the phone app, we discovered that call invitations are sent via InvitationManagerImpl.sendInviteMsg(), which publishes the call invite message on the topic “client//invite”. We suspected a similar approach was being used when a call is initiated from a temi to a phone user and decided to investigate to confirm.

Luckily for us, the exact same InvitationManagerImpl.sendInviteMsg() method could be found in the temi robot’s code, and it even seemed to function identically. Thus, it was probably safe to assume that the robot initiates calls with phone users in the same way: by publishing a call invitation to the topic “client//invite”, CLIENT_ID being the MQTT client ID of the callee.

If the caller publishes their call invites to a certain MQTT topic, it follows that the callee must subscribe to that same topic to receive the invite. Now that we knew the format of the call invite topic, the next step was to track down the code used by the Android app to subscribe to this topic so we could alter it to subscribe to the temi admin’s topic instead.

Performing a string search for this pattern in the decompiled phone app’s code produced three unique results:

Figure 45: Searching for “client/.+/invite”

The second result we recognized as being part of the code used to generate an outgoing call invitation. Thus, we were only interested in the first and third results.

We began by looking at the first result, found on line 170 of InvitationManagerImpl.java:

Figure 46: InvitationManagerImpl.sendInviteAbortMsg()

This reference is part of the method InvitationManagerImpl.sendInvitationAbortMsg(). Since we were interested in the code that subscribes to the call invite topic and not the code that publishes messages to it, we moved on.

The third result was found on line 523 of MqttManagerImpl.java:

Figure 47: MqttManagerImpl.buildInviteTopic()

This didn’t tell us anything about how the generated topic is used, so we took a step back and looked at what code invokes this method:

Figure 48: MqttManagerImpl.lambda$initMqttClient$13()

The call to buildInviteTopic() can be seen on line 408. There’s a lot going on in this method, but at a high level it appears that it is responsible for setting the callback functions for the MqttManager, which are being defined inline. More specifically, the invite topic string generated by buildInviteTopic() is used in the connectComplete() callback function, where it is passed as the first parameter to MqttManagerImpl.subscribe().

As expected, MqttManager’s subscribe() method is used to a subscribe to a particular MQTT topic, with the first parameter being the topic string:

Figure 49: MqttMangerImpl.subscribe()

Thus, it appeared that we had found the code being used to subscribe to the call invite MQTT topic. Based on these findings, we decided the simplest approach would be to change the call to MqttManagerImpl.subscribe() on line 408 of Figure 48 – instead of passing it the topic string returned by MqttManagerImpl.buildInviteTopic(), we would instead hard-code it to pass the temi admin’s call invite MQTT topic string.

Using this approach, we were able to construct a modified temi phone app that would receive all calls intended for another user, as shown in the following video:

Here, the vulnerability was the lack of any authentication when publishing or subscribing to arbitrary topics, denoted by CVE-2020-16167 and having a CVSS score of 8.6. At a minimum, a check should have been made to ensure that clients cannot subscribe to another client’s topics.

Problem: temi Won’t Answer My Calls

Our next goal was to gain the ability initiate a call with a temi and have it automatically answer. As mentioned previously, this capability is typically reserved for the temi’s admin and users explicitly authorized by the admin. Thus, if an attacker wishes to spy on a temi user, they would need to trick temi into thinking the call is originating from one of these authorized users.

With no other leads, we began searching through the robot’s codebase for keywords related to user permissions, ownership, admin, etc. until we found a very promising enum class:

Figure 50: Class used for delineating the various roles held by temi’s users

This enum is used in com.roboteam.teamy.users.User, the class used to store information about individual temi users, with User.role being an object of the Role enum class:

Figure 51: Class used to describe individual temi users/contacts

Going back to Figure 50, if we assume that Role.ADMIN refers to the user that originally registered temi and that Role.CONTACT refers to a normal user, then Role.OWNER likely refers to a user that has been authorized to “hop in” to the robot by the admin. To verify this, we looked for places in the code where this enum is used. There are 59 unique references to the Role class, but we’ll only be looking at the pertinent ones here.

The first reference we’ll be looking at appears on lines 351 and 357 of ConferencePresenter.handleViewForCallTypes():

Figure 52: ConferencePresenter.handleViewForCallTypes()

On line 351, a string comparison is performed between this.resourcesLoader.getString(R.string.privacy_support_support_caller_name) and this.callerDisplayName; if the two match, a new User is created with a role of CONTACT. So just what is this string that the ‘if’ statement is checking against? We decided to take a look at where this constant is defined:

Figure 53: Searching for “privacy_support_support_caller_name”

Taken together, this means that if the caller’s display name is exactly “Support”, a new User is created with CONTACT privileges. This check likely exists in the event that a member of temi’s support staff calls a user. While this was certainly interesting, it is a niche scenario.

What happens if the caller’s name is not “Support”? In that case, the else statement on line 352 is hit, which simply sets user to the result of getUserbyPeerId():

Figure 54: ConferencePresenter.getUserByPeerId()

This method tries to obtain the User associated with the current caller by performing a lookup in temi’s UsersRepository using the caller’s MQTT client ID. If the lookup succeeds, the found User is returned; if it fails, a new User is created with Role.CONTACT privileges.

As mentioned previously, Figure 52 contains two references to the Role class. Let’s now look at the second reference, found on line 357. Here, the role of the user is checked. If they are either an ADMIN or an OWNER, temi:

  • waits 1.5 seconds (line 362)
  • checks if the call hasn’t already ended (line 365)
  • if it hasn’t, answers the incoming call (line 370)

Otherwise, the function returns after setting the app’s view for an incoming call.

Solution: Become an OWNER

To recap what we’ve learned thus far:

  • The role of ADMIN appears to be reserved for the user that originally registers temi via QR code scan.
  • If the user calling temi is not recognized, a new User object is created for them with CONTACT privileges.
  • If the user calling temi है recognized, their role is checked:
    • If they are a CONTACT, temi waits for the call to be answered via touchscreen.
    • If they are either an ADMIN or an OWNER, temi answers the call automatically। This is the behavior we want.

Scanning the temi’s QR code for the purposes of registration is only possible when temi is first powered on or after performing a factory reset. Thus, the attacker cannot realistically make themselves an ADMIN. And since CONTACT privileges are insufficient to get temi to pick up automatically, our best bet was to figure out how to obtain the role of OWNER.

Adding an OWNER: Phone App’s Perspective

Although we knew that it was possible to promote a user to an OWNER using the phone app and we suspected that was achieved via MQTT, we still weren’t sure of what goes on “under the hood” to make that happen.

After some searching, we found that AddOwnersPresenter.addOwners() is called whenever an admin selects one or more users to be granted OWNER privileges:

Figure 55: AddOwnersPresenter.addOwners(), trimmed

Here, selectedIds refers to the MQTT client IDs of the users selected to be promoted and robotId refers to the MQTT client ID of the temi robot this method is granting permissions for.

The majority of this method’s body has been trimmed because we’re really only concerned with what’s happening on lines 104-106, which seems to handle sending the request to add an OWNER – the rest of the method is dedicated to logging and updating local databases to reflect the new OWNERs.

This request is sent by first fetching the unique private key used to identify this app’s instance (line 101), and then creating a new AddRemoveOwnersRequest using the selectedIds, robotId, and this private key:

Figure 56: AddRemoveOwnersRequest

The constructor for AddRemoveOwnersRequest creates a timestamp for the request, then creates a new AddOwnersRequestRequest, which contains the body of the request, and finally uses the passed in privateKey in order generate a signature for the AddOwnersRequestRequest. In other words, AddRemoveOwnersRequest is nothing more than a wrapper for the real request and is used to store its signature.

We decided to look at this AddOwnersRequestRequest object next. While the ownerIds, robotId, and timestamp members were mostly self-explanatory, source and type were less so. Looking back at line 8, we saw that source was being set to a hardcoded value of “ADMIN”, which seemed to imply that this was the origin of the request. Looking back at line 6, we saw that type is simply the passed in OwnersRequestType enum (in our case, OWNERS_ADD_TYPE) converted to a string. This enum, defined near the bottom of the class, can take on two values: OWNERS_ADD_TYPE and OWNERS_REMOVE_TYPE. This implied that this same structure was recycled for requests meant to demote OWNERs back to CONTACTs.

Thus, we determined that AddRemoveOwnersRequests had the following structure:

Figure 57: Anatomy of an AddRemoveOwnersRequest

Now that we knew the structure of these requests, we next wanted to know how they were being sent and where. To this end, we decided to look at OwnersApi.addOwners(), which, according to Figure 55, is actually sending the AddRemoveOwnersRequest.

Figure 58: OwnersApi.addOwners()

The body of this method didn’t tell us much, but the imports for the OwnersApi class gave us a clue: this was using the Retrofit 2 HTTP Client for Android, which is typically used to send HTTP requests to a REST API. According to this Retrofit tutorial, the @POST annotation “indicates that we want to execute a POST request when this method is called” and “the argument value for the @POST annotation is the endpoint.” The @Body annotation, on the other hand, indicates that we want the AddRemoveOwnersRequest to serve as the body of the POST request.

Okay, so this method is simply sending the request to add an OWNER via POST to some REST server at the endpoint “ownership/admin/add/owners”. Our next question became: Where is this REST server located?

Part 5 of that same Retrofit tutorial told us that the getClient() method is typically used to obtain/create a Retrofit instance, and the only argument it takes is a string containing the URL for the REST server. Searching for “getClient()” in the phone app’s code led us to ApiClient.getClient():

Figure 59: ApiClient.getClient()

Working backwards from this method, we were able to track down the server’s URL:

Figure 60: URLs for the MQTT broker and REST server

This URL confirmed that the recipient of this request was नहीं the temi robot and it was नहीं being sent via MQTT, contrary to our initial assumptions. This begged the question: If the phone app wasn’t sending these requests to temi, how was temi being notified of any updates to the privileges of its users? We hypothesized that this REST server was simply a middleman whose job was to authenticate all requests to add/remove OWNERs by checking the request’s signature against the admin’s public key that was saved during temi’s initial registration process. This extra level of authentication made sense since privilege management was a particularly sensitive functionality. Presumably, this same REST server would then forward the request to the robot if it determined that the request’s signature was valid.

We took some time trying to send these requests from a user that wasn’t temi’s admin, but they failed, lending some credence to our theory. This was looking like a dead end.

Adding an OWNER: temi’s Perspective

Well, if we couldn’t successfully spoof the request the phone app sends to the REST server, perhaps we could instead spoof the request the server sends to temi, bypassing the authentication mechanism altogether. We started looking for the code temi used to handle these requests.

Searching specifically for “Role.OWNER” in the temi’s decompiled code led us to OwnersController$getUsersRepository$2.apply():

Figure 61: OwnersController$getUsersRepository$2.apply()

Starting with OwnersController$getUsersRepository$2, we moved up the call chain in an attempt to discover how temi processes requests to add OWNERs. More concretely, this was accomplished through a liberal use of the “Find Usage” feature in JADX, which can be done by simply right-clicking virtually any symbol. Although convenient, “Find Usage” would often fail when the method in question was not invoked directly, such as when an implementation of an abstract class/interface served as the middleman. In such cases, we would perform a string search for instances where the method was invoked in the smali code. To help separate invocations from declarations, we took advantage of the smali syntax for method calls, which consisted of the name of the class the method belonged to, followed by a right arrow (->), followed by the method’s signature.

As an example, “Find Usage” failed for the accept() method of the class UsersAdminTopicListener$listenToUserAdminTopic$1, so to find where it’s invoked, we ran:

Figure 62: Searching for UsersAdminTopicListener$listenToUserAdminTopic$1.accept() in the smali code

For especially indirect invocations, like dependency injection, more creative means had to be used, but a combination of “Find Usage” and string searches such as these got us 99% of the way there.

Using this approach, we found that the mechanism begins with the UsersAdminTopicListener class, which, as the name suggests, handles listening on temi’s “users admin” topic. Moving down the call chain from here, we found out how messages received on this topic are processed by temi and ultimately used to alter the Role, or privilege level, of certain contacts. Based on our earlier analysis, it was likely that the REST server would forward the request sent by the phone app to this MQTT topic.

We found that the bulk of the work is performed by a method called listenToUserAdminTopic():

Figure 63: UsersAdminTopicListener.listenToUserAdminTopic()

This function does several things. First, on line 50, it creates a Flowable object by calling UsersAdminTopicListener.getOwnerRequestFlowable(). Next, on lines 51-56, it subscribes to this Flowable and for each emitted OwnersRequest, it calls OwnersController.handle$app_usaDemoRelease() upon success or simply logs upon failure.

We decided to first look at the code for getOwnerRequestFlowable():

Figure 64: UsersAdminTopicListener.getOwnerRequestFlowable()

  • It begins by first converting an Observable obtained via mqttPipeline.observe() into a Flowable.
  • Next, it throws out all emitted MqttMessages whose topic doesn’t match the regex “users/.+/admin” via RxJava’s filter() method.
  • RxJava’s map() method is then used convert the body of the MqttMessage from JSON to an object of the OwnersMessage
  • Finally, map() is used a second time to extract and return the OwnersRequest from each OwnersMessage.

At this point, we decided it would be useful to understand the structure of OwnersRequests and OwnersMessages, since these seem to be key to temi’s privilege management mechanisms:

Figure 65: Anatomy of an OwnersMessage

Put briefly, each OwnersMessage is nothing more than a wrapper for an OwnersRequest, which consists of ownerIds, a list of the MQTT client IDs of the users whose privileges are being modified, and type, which indicates whether the request is trying to promote a CONTACT to an OWNER (OWNERS_ADD) or demote an OWNER back to a CONTACT (OWNERS_REMOVE).

Comparing this to Figure 57, an OwnersMessage appears to be an AddRemoveOwnersRequest without the signature. Similarly, an OwnersRequest appears to be a stripped-down AddOwnersRequestRequest, with the robotId, source, and timestamp omitted. This meshes well with our earlier hypothesis that the REST server’s job is to authenticate and forward AddRemoveOwnersRequests to the temi robot. The signature, timestamp, and source would be omitted since they’ve already been verified by the server; the robotId, while needed by the server to know where to forward the request, becomes redundant once the request reaches temi.

Our next step was to figure out what handle$app_usaDemoRelease() does. Since it’s quite the rabbit hole, however, we will summarize its effects in lieu of venturing down it:

  1. It queries the Users table in temi’s local database for all users with IDs matching any of the ones in the OwnersRequest’s ownerIds
  2. It replaces the Role for each of these users with one corresponding to the OwnersRequest’s type: OWNERS_ADD→ OWNER, OWNERS_REMOVE → CONTACT.
  3. It updates temi’s Users table with the updated user information.

This was promising, since it meant that we could potentially trick temi into promoting an arbitrary user/contact to an OWNER simply by crafting a custom OwnersRequest and publishing it on temi’s “users//admin” topic, thereby bypassing the authentication server entirely.

Unfortunately, part 1 above reveals a potential obstacle for this plan: since temi will only process OwnersRequests for users already present in its local Users table, we must first add ourselves to this table for this strategy to succeed. Recalling our earlier analysis of how temi handles unrecognized callers, one way of accomplishing this was to simply cold call temi, which would cause it to automatically add the caller to its contacts list. This was far from ideal, however, since cold calling  temi without already having the role of OWNER or ADMIN would cause it to ring and display the caller’s username on its screen, potentially alerting temi’s users that something weird is going on.

Detour: Sneaking Onto temi’s Contact List

Before continuing on, we decided to take a brief detour to find a better way for an attacker to add themselves to temi’s contact list.

From our prior investigation into how temi implements its various privilege levels through the use of Roles, we discovered that temi uses the User class to define its various users. Thus, it follows that any code used to add a new user to temi’s local contact list would first create a new User object, so that’s exactly what we searched for.

Figure 66: Searching temi’s code for “new User(“, trimmed

Figure 66 shows the result that we followed up on, the others have been omitted. The name of the containing class, SyncContactsController, sounded promising on its own since syncing contacts from temi’s ADMIN would likely involve adding new contacts without needing to start a call, which was exactly what we were trying to do.

Using largely the same strategy we employed for tracing the code flow for adding OWNERs (JADX’s “Find Usage” feature + grepping the smali code), we were able to trace the code flow all the way back to the app’s entry point. With a more holistic understanding of the mechanism used to sync contacts, we realized that the process is ultimately kicked off by SyncContactsTopicListener.subscribeMqttPipeline():

Figure 67: SyncContactsTopicListener.subscribeMqttPipeline()

The first thing this method does is take this.mqttPipeline and turns it first into an Observable (using MqttPipeline.observe()) and then into a Flowable (using RxJavaInterop.toV2Flowable()), as seen on lines 29 and 30.

Essentially, this.mqttPipeline acts as a relay. Incoming MQTT messages are pushed to the relay using its push() method, which it then relays to all its observers.

The output of this relay is then filtered on lines 31-38 to only return MQTT messages received on topics matching the regex “synccontacts/.+”. Based on the other MQTT topic strings we’ve seen up to this point –

  • “users//status”
  • “users//admin”
  • “client//invite”

– we were fairly certain temi’s client ID goes after the forward slash. Thus, temi appeared to be listening on the MQTT topic “synccontacts/” for messages regarding contact synchronization.

On lines 39-43, the now-filtered MQTT messages emitted by the relay are passed to a call to RxJava’s map() function, which converts each incoming MQTT message from JSON to an object of the SyncContactsMessage class. We quickly determined that SyncContactsMessages had the following structure:


Figure 68: Anatomy of a SyncContactsMessage

Put briefly, each SyncContactsMessage consisted of a senderClientId, a string holding the MQTT client ID of the request’s sender, and contacts, a list of ContactEntry objects. Each ContactEntry object in the list corresponded to a contact to be synced to temi’s contact list, containing both their clientId and their name.

Finally, on lines 45-49, SyncContactsController.save() would be called on each SyncContactsMessage spit out by the prior call to map():

Figure 69: SyncContactsController.save()

This method is doing a lot, but we’ll focus on only the most pertinent side-effects:

  • On lines 53-62, all messages where the senderClientId does not match the temi admin’s client ID are discarded। This will become important later.
  • On lines 63-75, the list of contacts is extracted from the SyncContactsMessage and is used to build a list of User objects – one per contact. The Users produced by this method are initialized in the following manner:
Member Assigned Value
User.id ContactEntry.clientId
User.name ContactEntry.name
User.picUrl “”
User.role Role.CONTACT
User.userId SyncContactsMessage.senderClientId
  • On lines 81-85, our newly-minted list of User objects is passed to insertOrUpdateContact(). This method writes the list of Users to the Users table in temi’s SQLite 3 database, completely overwriting temi’s old contacts list in the process.

So now that we knew how an ADMIN’s contacts are synced to their temi, how could we leverage that knowledge to add ourselves to temi’s contact list in a discrete fashion? Well, if we could publish a message to temi’s synccontacts MQTT topic, we could add ourselves as a contact. Although temi does perform a check to make sure that the sender of the message is its ADMIN, it makes the mistake of trusting that the contents of the message accurately reflect the actual sender. In theory, there’s nothing stopping us from publishing a SyncContactsMessage from one client ID and setting the senderClientId field in the message to a completely different ID – the ADMIN’s client ID, for example.

Based on our understanding of how the temi robot parses MQTT requests to sync contacts, we crafted a JSON message that should decode into a valid SyncContactsMessage object and would add our “attack” phone to temi’s contacts:

Figure 70: Our custom SyncContactsMessage in JSON format

This message was crafted using the following rationale:

  1. Objects in JSON are indicated via curly braces ({})। Since the entire message contents are being converted into a single SyncContactsMessageobject, it follows that the contents should be contained within a pair of curly braces, representing the SyncContactsMessage object itself.
  2. A SyncContactsMessagecontains a senderClientId, a string indicating the client ID of the “sender” of the message. Thus, we added an element to our object with the key “senderClientId” and the string “060f62296e1ab8d0272b623f2f08f915” – the client ID of the temi’s admin – as its value.
  3. A SyncContactsMessagealso contains contacts, a list of ContactEntry Thus, we added an element with the key “contacts” and a new list as its value. In JSON, lists are indicated via square brackets ([])।
  4. In our case, the contacts list contains only a single ContactEntry – one corresponding to the “attack” phone, so we added a single pair of curly braces to the list to indicate the contents of this single ContactEntry.
  5. Each ContactEntry contains a clientId. Thus, we added an element to the object with the key “clientId” and the string “fe5d7af42433f0b6fb6875b6d640931b” – the client ID of the “attack” phone – as its value.
  6. Each ContactEntry also contains a name. Thus, we added a second element to the object with the key “name” and the string “Test” as its value. This simply represents the display name for the contact, so we could set it to basically whatever we liked.
  7. As for spacing and newlines, we referred to the Gson User Guide, since that’s the library temi was using to perform JSON serialization/deserialization. The guide states:

“The default JSON output that is provided by Gson is a compact JSON format. This means that there will not be any whitespace in the output JSON structure. Therefore, there will be no whitespace between field names and its value, object fields, and objects within arrays in the JSON output.”

As a result, whitespace and newlines have been omitted.

Now that we understood where to publish the request and what the request should look like, the next step was figuring out how to alter the temi phone app to send this request. Since we were aiming for a simple proof-of-concept, we prioritized ease and speed of implementation over robustness or elegance when considering which code to change. To this end, it made sense to leverage the code already present in the app that was dedicated to publishing MQTT messages since it would minimize the amount of code we needed to change and thus reduce the risk of introducing unintended bugs into our altered app – an ever-present risk when directly modifying an app’s bytecode. While the phone app publishes many MQTT messages to various topics during its runtime, we decided to try using the phone app’s video calling functionality. This had the obvious advantage of giving us clear control over when and how often the MQTT message is published, since calls are always initiated by hitting the “Connect” or “Call” buttons. Ultimately, we decided to leverage InvitationManagerImpl.sendInviteMsg() for this purpose. If we refer back to Figure 39 and our section “How are MQTT Call Invite Messages Published?”, the reason for this becomes apparent: sendInviteMsg() has a direct interface to MqttManagerImpl.publish(), the underlying method temi uses to publish arbitrary MQTT messages, while still being specific to the call chain for outgoing video calls. This meant that it would only get executed unless when we manually initiated a call from the app.

Running our altered app resulted in our attack phone being added to temi’s contact list, as shown in the following video:

Gaining OWNER Privileges

All that was left was for us to craft and publish a custom OwnersMessage in order to gain OWNER privileges on our temi. As before, we began by crafting the JSON for the message itself:

Figure 71: Our custom OwnersMessage in JSON format

This message was crafted using the following rationale:

  1. Since the entire message contents are being converted into a single OwnersMessage object, it follows that the contents should be contained within a pair of curly braces, representing the OwnersMessage object itself.
  2. An OwnersMessage contains a request, an object of the OwnersRequest class, and nothing else. Thus, we added an element with the key “request” and a new object as its value. The contents of this inner object will become our OwnersRequest.
  3. An OwnersRequest contains a type, an enum specifying the type of request. In our case we want to add an OWNER, so we added an element with the key “type” and the string “OWNERS_ADD” as its value. As for why we’re expressing this enum value as a string, it’s because this article shows that Gson’s default behavior is to express enum values as strings corresponding to the name of the enum value.
  4. An OwnersRequest also contains ownerIds, a list of strings enumerating the temi contacts the request should apply to. In our case, the list contains only a single ID – the ID of the “attack” phone, which is just the MD5 hash of its phone number.
  5. As before, spaces and newlines have been omitted, per the Gson User Guide.

Fortunately, we were able to leverage the code modifications we used to publish our SyncContactsMessage, since the only things changing were the MQTT topic we’re publishing to and the contents of the message.

Our full test consisted of running our first modified app in order to add ourselves to temi’s contact list, followed by our second modified app in order to perform privilege escalation, as shown in the following video:

The call appeared to be fully functional, and we were able to drive temi around, navigate to its saved locations, and receive its audio/video feeds. All an attacker needed to make this possible was the phone number of one of temi’s contacts.

This authentication bypass was the final and most impactful vulnerability we discovered in the temi robot, denoted by CVE-2020-16169 and having a CVSS score of 9.4. To better understand how this an auth bypass, let’s compare temi’s privilege management during normal operation:

Figure 72: temi’s privilege management during normal operation

to how it looks using our modified app:

Figure 73: temi’s privilege management with auth bypass

As you can see, although authentication है in place for adding OWNERs, it can be circumvented entirely by simply spoofing the MQTT message temi expects to receive from the REST server post-authentication.

Refining the Exploits

Once our exploits had the capabilities we initially set out to obtain, we got to work refining them. We created a single modified app that combined all of the MQTT attack vectors we previously outlined: it could intercept calls and send both MQTT messages necessary to obtain OWNER privileges, all with a single button press. Furthermore, we added some additional logic to automatically extract the client IDs required by our custom MQTT messages from the app’s contact list, meaning our app would work on any temi robot. To see all these features in action, please refer to the video below:

Impact

At this point it would be prudent to take a step back, review the combined capabilities of the exploits we’ve outlined, and consider what impact these might have in a real-world setting.

At the time of discovery, the vulnerabilities in the temi robot meant that an attacker could join any ongoing temi call simply by using a custom Agora app initialized with temi’s hardcoded App ID and iterating over all 900,000 possible channel names – certainly feasible with modern computing power. This becomes more plausible if one considers that our testing revealed that joining a temi call this way does not notify either of the existing call participants that another user is present, since the apps were only designed with 1-on-1 calling in mind. The fact that the attacker needs no information on the victims makes this attack vector worrisome, but it also means that the attacker has no control over who he or she spies on using this method. Realistically, a malicious actor might use this exploit as a means of reconnaissance – by collecting data on arbitrary temi users, they could follow up on the more “promising” ones later with one of the targeted attack vectors. Given temi’s limited adoption by consumers but ramping adoption in industries such as healthcare, the odds are in the attacker’s favor that they stumble upon sensitive information.

Furthermore, if an attacker has a specific victim in mind, they could leverage the lack of per-topic authentication in temi’s MQTT implementation in order to intercept calls between a user and their temi. The plausibility of this particular attack vector is difficult to evaluate. On the one hand, the only information the attacker needs is the victim’s phone number, and telemarketers consistently remind us that this is a very low bar. On the other hand, this exploit’s behavior during our testing proved somewhat inconsistent. At times, the user running the exploit could listen in on the call as a third party with the call participants being none the wiser. At other times, the user running the exploit would disconnect the original caller and take their place. It is easy to imagine scenarios where both of these outcomes are desirable to a malicious actor, and neither bodes well for the privacy of the user being targeted. It is also important to note that although we focused on using this vulnerability to intercept calls, it can also be used to intercept all sorts of notifications intended for another user: contacts being added, when temi goes online/offline, its current location, etc. While less flashy than intercepting a call, this approach is more discreet and might allow a dedicated attacker to learn a user’s routine, only showing their hand to listen in when it would be most impactful.

Of the possible attack vectors, we believe that the ability to call and control a temi remotely, leveraging the authentication bypass present in temi’s privilege management mechanism, is the most impactful. The bar for this attack vector is arguably even lower than the previous, as the attacker would only need the phone number of कोई भी of temi’s contacts – it need not be its admin. In our testing, none of the steps involved in leveraging this exploit notify temi’s admin in any way that something is amiss; they are not notified that the attacker has added themselves to the robot’s contact list nor that they have gained raised privileges. Since this method does not cause temi to ring, an observer would have to see the attacker move temi or have a good look at its touchscreen during the attack to know something nefarious was going on. This level of control and subtlety becomes especially problematic when we consider some of temi’s applications in industry. In April of this year, Robotemi Global Ltd. stepped up production to 1,000 units per month in response to Israel’s Ministries of Defense and Health choosing temi to assist with patients in their COVID-19 wards. In South Korea, temi sees use in both public places and nursing homes, helping to facilitate social distancing. Besides the obvious impact of compromising patients’ sensitive medical information during remote doctor’s visits, the ability to have eyes and ears into a hospital is worrying in its own right. It isn’t difficult to imagine what a malicious agent might do with an overheard network password, access code to a sensitive area, or the location and condition of a person of interest.

निष्कर्ष

The findings outlined in this paper highlight the importance of secure coding practices and security auditing for cutting edge technologies, particularly in the IoT space. When an IoT camera evolves into one that can also drive around your home or business and even assist medical patients, the need to properly secure who can access it only becomes more important. While history has demonstrated that any sufficiently complex software is bound to have vulnerabilities, there are many steps we can take to substantially raise the bar for bad actors. In our view, these responsibilities are shared between vendors, consumers, and the security industry as a whole.

First and foremost, vendors can ensure they are employing proper security hygiene when designing products. Often, best practices consist not of novel, out-of-the-box thinking, but rather adopting time-tested guidelines like the principle of least privilege। In the case of temi, application of this principle likely would have addressed CVE-2020-16167, which allows clients to subscribe/publish to topics they have no business accessing. Considerations for security should also extend beyond the development phase, with third-party security auditing being a powerful tool that allows vendors to discover and patch vulnerabilities before the bad guys get ahold of them. Taking vulnerability disclosure seriously and cooperating with the bodies that report them can often net similar benefits, and this is an area Robotemi excelled in.

Consumers of these technologies, at either the individual or enterprise level, also share some of the responsibility. Companies should ideally vet all technologies before widespread adoption, particularly if these technologies have access to customer information. It goes without saying that greater risk warrants greater scrutiny. Individuals, while typically having fewer resources, can also take certain precautions. Placing IoT devices on a VLAN, a virtually segregated subnetwork, reduces the risk that a vulnerability in one device will compromise your entire network. Staying up to date on important vulnerabilities can also help inform consumers’ purchasing decisions, although the presence of vulnerabilities is often less illuminating than if/how a company chooses to address them.

Finally, we in the security industry also have an obligation towards moving the needle in the realm of security, one that we hope research such as this embodies. One of our goals here at McAfee ATR is to identify and illuminate a broad spectrum of threats in today’s complex and constantly evolving landscape. While we take seriously our obligation to inform vendors of our findings in a timely and responsible fashion, it is only through cooperation that the best results are possible. Our partnership with Robotemi on addressing these vulnerabilities was a perfect example of this. They responded quickly to our private disclosure report, outlined to us their plans for mitigations and an associated timeline, and maintained a dialogue with us throughout the whole process. We even received feedback that they have further emphasized security in their products by approaching all development discussions with a security-first mindset as a result of this disclosure. The ultimate result is a product that is more secure for all who use it.