還記得我之前說的PHP Hash Collisions Ddos漏洞吧? 最初的時候,開發組給出的修復方案,采用的是如果超過max_input_vars,就報錯(E_ERROR),繼而導致PHP出錯結束,而后來,為了更加輕量級的解決這個問題,我們又改善了一下,變成了如果超過max_input_vars,就發出警告(E_WARNING),并且不再往目的數組添加,但是流程繼續,然后我們發布了5.3.9.
這個新的修復方法初衷是好的,但是卻帶來一個嚴重的問題(5.3.10中已經修復),這個問題最初是由Stefan Esser發現的,請看之前(5.3.9)最終的修復方案(php_register_variable_ex),代碼如下:
- while (1) {
- if (zend_symtable_find(symtable1, escaped_index, index_len + 1, (void **) &gpc_element_p) == FAILURE
- || Z_TYPE_PP(gpc_element_p) != IS_ARRAY) { //(3)
- if (zend_hash_num_elements(symtable1) <= PG(max_input_vars)) { // (4)
- if (zend_hash_num_elements(symtable1) == PG(max_input_vars)) {
- php_error_docref(NULL TSRMLS_CC, E_WARNING, "Input variables exceeded %ld. ...", PG(max_input_vars)); // (1)
- }
- MAKE_STD_ZVAL(gpc_element);
- array_init(gpc_element);
- zend_symtable_update(symtable1, escaped_index, index_len + 1, &gpc_element, sizeof(zval *), (void **) &gpc_element_p);
- }
- //......
- }
- //.....
- symtable1 = Z_ARRVAL_PP(gpc_element_p); // (2)
- //開源代碼Vevb.com
- goto plain;
- }< li>
注意到,如果此時注冊一個數組變量(在GET中類似于:a[]=2),并且此時這個變量剛好是第max_input_vars個變量的時候,會觸發一個警告(1),此時一切正常.
但是,如果此時還是注冊一個數組變量,但是這個變量已經是第max_input_vars + 1個變量的時候,那么此時gpc_element_p將成為一個未初始化的指針,而因為現在邏輯會繼續走, 也就會走到(2)號位置, 導致解引用了一個未初始化的指針,于是,Boomb~
那么,到目前位置,我們就可以使用這樣的特性來對5.3.9做Ddos了,如果Server開啟了Core Dump的話,這個效果會非常明顯.
然而,這個問題還會導致一個更嚴重的問題:
還是上面的代碼,在最外層有一個循環,這個循環起作用的時刻在注冊類似于a[b]=2的pair對的時候,循環將會執行倆次,第一次插入a[],第二次往a[]中插入b.然后再讓我們注意下(3),如果在目的數組中找不到一個想要的元素,**或者這個元素不為數組**,則也會直接導致流程留到(2),于是問題就出現了.
1=1&1=2&..........&999=1&x="我是惡意的string"&x[0]=
會發生什么事情呢?讓我來一步一步描述下:
1.從1到999沒什么問題, 都被正常插入
2.x是1000個元素, 所以觸發警告, 也沒有問題, x被插入
3.x[0]插入的時候,(3)號語句判斷發現不是Arrary于是進入if體,但是此時(4)號語句失敗, 于是流程最終流到了(2)
4.此時,gpc_element_p指向x,也就是那個我們偽造的字符串….現在讓我們看看關鍵的數據結構,zval,代碼如下:
- struct _zval_struct {
- /* Variable information */
- zvalue_value value; /* value */
- zend_uint refcount__gc;
- zend_uchar type; /* active type */
- zend_uchar is_ref__gc;
- };< li>
然后看zvalue_value,代碼如下:
- typedef union _zvalue_value {
- long lval; /* long value */
- double dval; /* double value */
- struct {
- char *val;
- int len;
- } str;
- HashTable *ht; /* hash table value */
- zend_object_value obj;
- } zvalue_value;< li>
zvalue_value是一個聯合體,于是我們構造的字符串區域的內存,就會被當做一個Hashtable結構體,代碼如下:
- typedef struct _hashtable {
- uint nTableSize;
- uint nTableMask;
- uint nNumOfElements;
- ulong nNextFreeElement;
- Bucket *pInternalPointer; /* Used for element traversal */
- Bucket *pListHead;
- Bucket *pListTail;
- Bucket **arBuckets;
- dtor_func_t pDestructor; //注意這個
- zend_bool persistent;
- unsigned char nApplyCount;
- zend_bool bApplyProtection;
- #if ZEND_DEBUG
- int inconsistent;
- #endif
- } HashTable;< li>
在Hashtable結構體中,有一個pDestructor,這個指針指向一個函數,當這個Hashtable中有元素要被清除的時候,就會調用它…
也就是說,你可以隨心所欲的設置一個地址(pDestructor),然后讓PHP去調用它(誘使一個元素被刪除).
新聞熱點
疑難解答