
Magento अनधिकृत रिमोट कोड निष्पादन (CVE-2016-4010)
0x00 परिचय
17 मई को, विदेशी सुरक्षा शोधकर्ता Netanel Rubin ने Magento में एक अनधिकृत रिमोट कोड निष्पादन भेद्यता (CVE-2016-4010) का खुलासा किया। यह भेद्यता वास्तव में कई छोटी भेद्यताओं को समाहित करती है और हमलावर को कमजोर Magento सर्वर पर अनधिकृत रूप से PHP कोड निष्पादित करने की अनुमति देती है। Magento एक अत्यंत लोकप्रिय ई-कॉमर्स प्लेटफ़ॉर्म है, जिसे 2011 में eBay द्वारा अधिग्रहित किया गया था। कुछ प्रसिद्ध कंपनियाँ, जैसे: Samsung, Nikon, Lenovo, तथा कई छोटे ई-कॉमर्स स्टोर इसे उपयोग कर रहे हैं। सूत्रों के अनुसार, Magento का उपयोग 250,000 ऑनलाइन स्टोर करते हैं, जिनमें प्रतिवर्ष लगभग 60 बिलियन अमेरिकी डॉलर के लेन-देन होते हैं।
0x01 विश्लेषण
इस भेद्यता के शोषण की शर्तें:
Magento का वेब API RPC के 2 अलग-अलग तरीकों की अनुमति देता है, जिन्हें REST RPC और SOAP API कहा जाता है। ये दोनों तरीके समान कार्यक्षमता प्रदान करते हैं, एकमात्र अंतर यह है कि पहला इनपुट संचारित करने के लिए JSON और HTTP अनुरोधों का उपयोग करता है, जबकि दूसरा XML का उपयोग करता है।
केवल कुछ मॉड्यूलों के API को सार्वजनिक करने के लिए, Magento डेवलपर्स को एक सुविधाजनक तरीका प्रदान करता है, जो "webapi.xml" फ़ाइल में केवल उन्हीं मॉड्यूलों के API को घोषित करना है जिन तक वे पहुँच प्रदान करना चाहते हैं। webapi.xml फ़ाइल में उन सभी वेब API की कक्षाएं (classes) और विधियाँ (methods) शामिल होती हैं जिन्हें सार्वजनिक करने की आवश्यकता होती है, तथा प्रत्येक विधि अपनी आवश्यक विशिष्ट अनुमतियाँ भी निर्दिष्ट करती है। इन अनुमतियों में शामिल हैं:
निःसंदेह, यह तरीका जो डेवलपर्स को webapi.xml फ़ाइल के माध्यम से सिस्टम के फ्रंट-एंड तथा बैक-एंड (वेब API) के बीच संचार करने की अनुमति देता है, वास्तव में मॉड्यूल के कोर में सीधे प्रवेश का एक बैकडोर भी खोल देता है।
इसके अतिरिक्त, भले ही हमारे पास "anonymous" अनुमति हो, फिर भी हमें गतिशील रूप से मान पारित करने का एक तरीका चाहिए। यहाँ तात्पर्य सिस्टम में उपलब्ध विभिन्न ऑब्जेक्ट्स से है, उदाहरण के लिए: "CustomerRepositoryInterface::save()" API फ़ंक्शन हमें "$customer" चर में "CustomerInterface" ऑब्जेक्ट का उपयोग करने की अनुमति देता है, कोड प्रोटोटाइप निम्नलिखित है:
interface CustomerRepositoryInterface
{
/**
* Create customer.
*/
public function save(\Magento\Customer\Api\Data\CustomerInterface $customer);
}
तो RPC इंटरफ़ेस का उपयोग करके ऑब्जेक्ट कैसे बनाएं? वास्तव में, इस प्रश्न का उत्तर इस बात में निहित है कि Magento SOAP सर्वर को कैसे कॉन्फ़िगर करता है।
Magento SOAP सर्वर का उपयोग करता है जो डिफ़ॉल्ट रूप से PHP के "SoapServer" के साथ बंडल होता है। सही ढंग से कॉन्फ़िगरेशन के लिए, "SoapServer" को एक WSDL फ़ाइल की आवश्यकता होती है, जिसमें सभी विधियाँ, पैरामीटर, तथा वास्तविक RPC अनुरोधों में उपयोग किए जाने वाले कस्टम प्रकार परिभाषित होते हैं। Magento XMLRPC कार्यक्षमता का समर्थन करने वाले प्रत्येक मॉड्यूल के लिए अलग-अलग WSDL फ़ाइलें उत्पन्न करता है, तथा मॉड्यूल की webapi.xml फ़ाइल से मान सीधे निर्धारित करता है।
जब कोई RPC अनुरोध सर्वर द्वारा पार्स किया जाता है, तो सर्वर WSDL फ़ाइल में उपलब्ध डेटा का उपयोग यह निर्धारित करने के लिए करता है कि अनुरोध वैध है या नहीं, तथा अनुरोध की विधि, पैरामीटर और प्रकारों की जाँच करता है। यदि अनुरोध वैध है, तो पार्स किए गए अनुरोध ऑब्जेक्ट को आगे की प्रक्रिया के लिए Magento को भेज दिया जाता है। एक अत्यंत महत्वपूर्ण बिंदु यह है कि "SoapServer" किसी भी तरह से Magento के साथ इंटरैक्ट नहीं करता है; मॉड्यूल की विधियों और पैरामीटरों के बारे में सारी जानकारी WSDL फ़ाइल से आती है। इस बिंदु पर, भेजा गया अनुरोध अभी भी नेस्टेड ऐरे से बना होता है, और SoapServer की पार्सिंग अवस्था में कोई ऑब्जेक्ट नहीं बनाया जाता है। आवश्यक ऑब्जेक्ट बनाने के लिए, Magento इनपुट को स्वयं संसाधित करना जारी रखता है।
पैरामीटर नाम और डेटा प्रकार निकालने के लिए, Magento अनुरोध की विधि से प्रोटोटाइप प्राप्त करता है (पूर्ववर्ती कोड देखें)। कुछ बुनियादी डेटा प्रकारों, जैसे स्ट्रिंग, ऐरे, बूलियन आदि के लिए, सिस्टम इनपुट को संबंधित प्रकार से मैप करेगा। लेकिन ऑब्जेक्ट प्रकारों के लिए, समाधान की प्रक्रिया अधिक जटिल है।
यदि पैरामीटर का डेटा प्रकार किसी वर्ग का उदाहरण (instance) है, तो Magento प्रदान किए गए इनपुट का उपयोग करके उदाहरण बनाने का प्रयास करेगा। ध्यान रखें, इस समय इनपुट केवल एक डिक्शनरी है, जिसकी key गुण (property) का नाम है तथा value गुण का मान है।
सबसे पहले, Magento आवश्यक वर्ग का एक नया उदाहरण बनाएगा। फिर, यह निम्नलिखित विधि का उपयोग करके इसे भरने का प्रयास करेगा:
Magento इस विधि के अनुसार उन सभी गुणों को संसाधित करेगा जिन्हें उपयोगकर्ता सेट करने का प्रयास कर रहा है। जब सभी गुणों की जाँच हो जाती है, तो Magento यह मान लेगा कि उदाहरण सेट हो चुका है और अगले पैरामीटर को संसाधित करेगा। जब सभी पैरामीटर इस प्रकार संसाधित हो जाते हैं, तो Magento अंततः उस API विधि को निष्पादित करेगा।
संक्षेप में, Magento आपको एक ऑब्जेक्ट बनाने, उसके सार्वजनिक गुणों को सेट करने, और अंततः उसके RPC के माध्यम से "Set" से शुरू होने वाली किसी भी विधि को निष्पादित करने की अनुमति देता है। और यही व्यवहार Magento की भेद्यता को जन्म देता है।
शोध से पता चला कि कुछ API कॉल शॉपिंग कार्ट में विशिष्ट जानकारी सेट करने की अनुमति देते हैं, यह जानकारी हमारा डाक पता, उत्पाद, या यहाँ तक कि हमारी भुगतान विधि भी हो सकती है।
जब Magento शॉपिंग कार्ट उदाहरण में हमारी जानकारी सेट करता है, तो यह नए जोड़े गए डेटा को डेटाबेस में संग्रहीत करने के लिए उदाहरण की "save" विधि का उपयोग करता है।
आइए देखें कि "save" विधि कैसे काम करती है!
/**
* Save object data
*/
public function save(\Magento\Framework\Model\AbstractModel $object)
{
...
// If the object is valid and can be saved
if ($object->isSaveAllowed()) {
// Serialize whatever fields need serializing
$this->_serializeFields($object);
...
// If the object already exists in the DB, update it
if ($this->isObjectNotNew($object)) {
$this->updateObject($object);
// Otherwise, create a new record
} else {
$this->saveNewObject($object);
}
// Unserialize the fields we serialized
$this->unserializeFields($object);
}
...
return $this;
}
// AbstractDb::save()
Magento सुनिश्चित करता है कि हमारे ऑब्जेक्ट मान्य हैं, फिर उन सभी भागों को सीरियलाइज़ करता है जिन्हें सीरियलाइज़ किया जाना चाहिए और उन्हें डेटाबेस में संग्रहीत करता है, तथा अंत में पहले सीरियलाइज़ किए गए भागों को अनसीरियलाइज़ करता है।
सरल लगता है, है ना? वास्तव में ऐसा नहीं है, आइए आगे देखें कि Magento कैसे निर्धारित करता है कि किन भागों को सीरियलाइज़ किया जाना चाहिए।
/**
* Serialize serializable fields of the object
*/
protected function _serializeFields(\Magento\Framework\Model\AbstractModel $object)
{
// Loops through the '_serializableFields' property
// (containing hardcoded fields that should be serialized)
foreach ($this->_serializableFields as $field => $parameters) {
// Get the field's value
$value = $object->getData($field);
// If it's an array or an object, serialize it
if (is_array($value) || is_object($value)) {
$object->setData($field, serialize($value));
}
}
}
// AbstractDb::_serializeFields()
जैसा कि हम देखते हैं, केवल वे भाग जो हार्डकोडेड डिक्शनरी "_serializableFields" में मौजूद हैं, सीरियलाइज़ किए जा सकते हैं। सबसे महत्वपूर्ण बात यह है कि यह विधि केवल यह सुनिश्चित करने के बाद ही सीरियलाइज़ेशन जारी रखती है कि field का मान एक ऐरे या ऑब्जेक्ट है।