
CVE-2023-45777 के लिए राइटअप और एक्सप्लॉइट, Android 13 पर AccountManagerService के अंदर Intent सत्यापन को बायपास करना, "Lazy Bundle" शमन के बावजूद
इस बार शुरुआत करते हैं उस पैच से जो Android Security Bulletin में CVE-2023-45777 के फिक्स के रूप में सामने आया था:```diff diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 7a19d034c2c8..5238595fe2a2 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -4923,7 +4923,7 @@ public class AccountManagerService p.setDataPosition(0); Bundle simulateBundle = p.readBundle(); p.recycle();
Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT);
Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT, Intent.class);
if (intent != null && intent.getClass() != Intent.class) {
return false;
}
कुछ लोग इससे इतने उलझन में थे कि उन्होंने मुझसे पूछा, पहले मैंने उन्हें कुछ संकेत दिए थे और अब मैं इस मुद्दे के लिए पूरा विवरण प्रकाशित कर रहा हूँ
लेकिन पहले आइए कुछ संदर्भ प्रदान करें कि इस पैच में क्या हो रहा है
यह [`checkKeyIntent()` विधि](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/java/com/android/server/accounts/AccountManagerService.java;l=4938-4954;drc=47de64a38aa1799cb41f41b2ea0c539ee61de64d) में बदलाव है। यह विधि कई जाँचें करती है ताकि यह सुनिश्चित किया जा सके कि एप्लिकेशन द्वारा प्रदान किया गया `Intent` सिस्टम के लिए (सिस्टम के विशेषाधिकारों का उपयोग करके) लॉन्च करने के लिए सुरक्षित है
सबसे पहले, यह विधि `checkKeyIntentParceledCorrectly()` का उपयोग करती है जो उस `Bundle` को फिर से सीरियलाइज़ और डीसीरियलाइज़ करती है जिसे हम जाँच रहे हैं और यह जाँचती है कि उससे पहले `Bundle` से लिया गया `Intent` ऐसे चक्र के बाद `Bundle` से प्राप्त `Intent` से मेल खाता है या नहीं। चूँकि `Intent` का लॉन्च उस प्रक्रिया के अलावा अन्य सिस्टम ऐप प्रक्रियाओं में होता है जो सत्यापन करती है, इसलिए पहले [ऐसे `Bundle`-s का निर्माण संभव था जो `AccountManagerService` के अंदर सत्यापन के दौरान सुरक्षित दिखते थे, लेकिन अगली प्रक्रिया में भेजे जाने के बाद उनमें अलग `Intent` होता था](https://github.com/michalbednarski/IntentsLab/issues/2#issuecomment-344365482)। यह ऐसी स्थितियों का पता लगाने के लिए `Bundle` को अगली प्रक्रिया में भेजने का अनुकरण करता है
`checkKeyIntentParceledCorrectly()` के बाद हमारे पास `bundle.getParcelable()` कॉल है, जिसे यह पैच पुराने संस्करण से बदल देता है जो किसी भी ऑब्जेक्ट का निर्माण कर सकता था, उस संस्करण से जो यह सत्यापित करता है कि जो ऑब्जेक्ट डीसीरियलाइज़ होने वाला है वह उस प्रकार का है जो दूसरे पैरामीटर में निर्दिष्ट किया गया था
टाइप पैरामीटर वाला वह संस्करण Android 13 में, बड़े `Parcel`/`Bundle` सख्तीकरण के हिस्से के रूप में पेश किया गया था। विशेष रूप से, Android 13 से पहले जब `Bundle` प्रक्रियाओं के बीच भेजा जाता था, तो यह पूरे सीरियलाइज़्ड डेटा की कच्ची प्रति तब तक रखता था जब तक कि किसी आइटम तक पहुँच नहीं होती थी, जिस बिंदु पर हर मान डीसीरियलाइज़ हो जाता था। अब जब `Bundle` प्राप्त होने के बाद किसी मान तक पहली बार पहुँचा जाता है, तो केवल `String` कुंजियाँ और आदिम प्रकारों के मान डीसीरियलाइज़ होते हैं, जबकि गैर-आदिम मान `LazyValue`-s के रूप में छोड़ दिए जाते हैं, जिनकी लंबाई सीरियलाइज़्ड डेटा के हिस्से के रूप में संग्रहीत होती है ताकि यह सुनिश्चित किया जा सके कि भले ही सीरियलाइज़ेशन/डीसीरियलाइज़ेशन तर्क मेल न खाता हो, ऐसी असंगतियाँ अन्य प्रविष्टियों को प्रभावित न करें
गहराई में जाने से पहले, आइए `LazyValue` पर एक नज़र डालें: इसके स्रोत कोड में [हमारे पास एक अच्छी टिप्पणी है जो इसकी डेटा संरचना की व्याख्या करती है](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Parcel.java;l=4392-4399;drc=03c34f57c05feecfb090de3917787f049cb5f804)```
| 4B | 4B |
mSource = Parcel{... | type | length | object | ...}
a b c d
length = d - c
mPosition = a
mLength = d - a
mPosition और mLength मूल Parcel में पूरे LazyValue डेटा के स्थान का वर्णन करते हैं, जिसमें type और length शामिल हैं। "length" (शुरुआत में "m" के बिना) Parcel में लिखे गए length मान को संदर्भित करता है और हेडर (type और length) को बाहर रखता है
यदि LazyValue युक्त Bundle को किसी अन्य प्रक्रिया में अग्रेषित किया जा रहा है, तो type और length फ़ील्ड सहित पूरा LazyValue Bundle.mParcelledData से गंतव्य Parcel में शब्दशः कॉपी किया जाता है
जब LazyValue द्वारा प्रदर्शित Bundle आइटम तक पहुँचा जाता है, तो Parcel को mPosition पर रिवाइंड किया जाता है और readValue() को कॉल किया जाता है। यदि bundle.getParcelable() को type तर्क पारित किया जाता है, तो इसे readValue() तक प्रचारित किया जाता है जो यह सुनिश्चित करेगा कि अनपार्सल होने वाला type अपेक्षित है, साथ ही अनपार्सल करने के बाद सत्यापित करेगा कि अनपार्सल किया गया मान type अपेक्षित है। अनपार्सल करने के बाद LazyValue को बदल दिया जाता है ताकि अगली बार जब Bundle को Parcel में लिखा जाए, तो मान को writeValue() के माध्यम से फिर से क्रमबद्ध किया जाए
टाइप किए गए Bundle.get*()/Parcel.read*() पैरामीटर का उपयोग मुख्य रूप से Parcel.readParcelableList() जैसी विधियों के लिए प्रासंगिक है, जो ArrayList लौटाता है और Java Type Erasure के कारण भले ही आपने List<SomeParcelableType> field = parcel.readParcelableList(); जैसा कुछ किया हो, <SomeParcelableType> भाग रनटाइम पर लागू नहीं किया गया था और ऐसी List में सिस्टम में उपलब्ध कोई भी Parcelable क्लास हो सकती थी, और इसलिए सिस्टम में उपलब्ध सभी createFromParcel/writeToParcel ऐसी List वाले type के क्रमबद्धीकरण/विक्रमबद्धीकरण के भाग के रूप में उपयोग किए जा सकते थे
आप Android Security and Privacy टीम की इन तंत्रों की शुरुआत के बारे में प्रस्तुति (स्लाइड्स, वीडियो) भी देख सकते हैं
हालाँकि यहाँ टाइप किए गए संस्करण का उपयोग अनावश्यक प्रतीत होता है, क्योंकि हम लौटाई गई वस्तु के type की भी स्पष्ट रूप से जाँच करते हैं। तो यहाँ क्या हो रहा है और यहाँ किस कमजोरी को ठीक किया जा रहा है?
शुरुआत से पैच को फिर से देखें
"intent" कुंजी के अंतर्गत विपार्सल किया गया मान एक Intent है
Intent ऑब्जेक्ट से बेमेल ट्रिगर किया जा सकता है तो हमारे पास बहुत बड़ी समस्या होगीIntent नहीं है
Bundle के अंदर एक Intent रखने की आवश्यकता होगी, लेकिन Parcelable का type किसी भी संभावित बेमेल से पहले के ऑफसेट पर सहेजा जाता है और LazyValue की length-उपसर्ग प्रणाली हमें writeToParcel/createFromParcel बेमेल के मामले में अगले कुंजी-मान जोड़े को संशोधित करने से रोकती हैतो, type तर्क के बिना bundle.getParcelable(AccountManager.KEY_INTENT) का कॉल यहाँ क्या खतरनाक काम कर सकता है?
[उत्तर अगले पैराग्राफ में है, पढ़ने से पहले अनुमान लगाने का प्रयास करें। यदि मेरे पास एक फ़र्सोना होता तो यह कुछ कला के लिए स्थान होता]