PHP Multipart/form-data remote dos Vulnerability

Andrew.Hann發表於2015-12-14

catalog

1. Description
2. Analysis

 

1. Description

PHP is vulnerable to a remote denial of service, caused by repeatedly allocate memory、concatenate string、copy string and free memory when PHP parses header areas of body part of HTTP request with multipart/form-data. By sending multiple HTTP multipart requests to an affected application containing malicious header area of body part, a remote attacker could exploit this vulnerability to cause the consumption of CPU resources.
該漏洞利用PHP多次合併boundary裡的引數,從而造成多次記憶體分配和拷貝,從而搶佔CPU資源,造成效能下降。攻擊者利用併發多包的方式,可以達到使目標系統拒絕服務的目的
HTTP協議中,multipart/form-data中可以包含多個報文,它們被合併在一個複雜報文中傳送,每一個部分都是獨立的,以':'分隔各自的引數和值,不同部分的報文通過分界字串(boundary)連線在一起
PHP中實現瞭解析multipart/form-data協議的功能,在解析時,當出現一個不包含':'的行,且之前有一個有效鍵值對,則說明該行是上一個鍵值對裡的值,PHP會將值拼接到上一個鍵值對裡。在拼接的過程裡,PHP進行了一次記憶體分配,兩次記憶體複製,以及一次記憶體釋放。當出現多個不包含':'的行時,PHP就會進行大量記憶體分配釋放的操作,從而導致消耗CPU資源效能下降。短時間多次傳送這類畸形請求將導致目標伺服器DoS

Relevant Link:

http://www.chinaz.com/news/2015/0520/407861.shtml
https://portal.nsfocus.com/vulnerability/list/

 

2. Analysis

The vulnerable function is multipart_buffer_headers that is called internally by the function SAPI_POST_HANDLER_FUNC in main/rfc1867.c. SAPI_POST_HANDLER_FUNC is the entry-point function which parses body parts of HTTP request with multipart/form-data.
\php-src-master\main\rfc1867.c

SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler) /* {{{ */
{
    ..
    while (!multipart_buffer_eof(mbuff))
    {
        char buff[FILLUNIT];
        char *cd = NULL, *param = NULL, *filename = NULL, *tmp = NULL;
        size_t blen = 0, wlen = 0;
        zend_off_t offset;

        zend_llist_clean(&header);

        if (!multipart_buffer_headers(mbuff, &header)) {
            goto fileupload_done;
        }
        ..

multipart_buffer_headers

/* parse headers */
static int multipart_buffer_headers(multipart_buffer *self, zend_llist *header)
{
    char *line;
    mime_header_entry prev_entry = {0}, entry;
    int prev_len, cur_len;

    /* didn't find boundary, abort */
    if (!find_boundary(self, self->boundary)) 
    {
        return 0;
    }

    /* get lines of text, or CRLF_CRLF */
    //1. Step 1. The multipart_buffer_headers executes while loop cycle to parse current body part headers, if the boundary string was found. 
    while( (line = get_line(self)) && line[0] != '\0' )
    {
        /*
        2. When parseing current body part headers which is represented as (header, value), 
        the multipart_buffer_headers function firstly call get_line function to read a line of characters, but get_line return a line when it meets character '\n', not '\r\n'. 
        After getting a line which is stored in the variable 'line', the multipart_buffer_headers function parses the variable line. 
        /* add header to table 
        PHP每次讀取HTTP body header中的一行
        */
        char *key = line;
        char *value = NULL;

        if (php_rfc1867_encoding_translation()) 
        {
            self->input_encoding = zend_multibyte_encoding_detector((const unsigned char *) line, strlen(line), self->detect_order, self->detect_order_size);
        }

        /* space in the beginning means same header */
        if (!isspace(line[0])) 
        {
            //尋找":"作為key-value的分界符
            value = strchr(line, ':');
        }

        if (value) 
        {
            *value = 0;
            do { value++; } while(isspace(*value)); 
            entry.value = estrdup(value);
            entry.key = estrdup(key); 
        } 
        //3. And then, it calls zend_llist_add_element function to store entry
        else if (zend_llist_count(header)) 
        { 
            /* If no ':' on the line, add to previous line */ 
            /*
            4. In this step, the multipart_buffer_headers function thinks current line is not a new header, 
            and current line should be append to value of prev_entry. Thus, prev_entry and current line merge into a new entry by executing the following codes: 
            */
            prev_len = (int)strlen(prev_entry.value);
            cur_len = (int)strlen(line);

            entry.value = emalloc(prev_len + cur_len + 1);
            memcpy(entry.value, prev_entry.value, prev_len);
            memcpy(entry.value + prev_len, line, cur_len);
            entry.value[cur_len + prev_len] = '\0';

            entry.key = estrdup(prev_entry.key);

            //// free memory 
            zend_llist_remove_tail(header);
        } else {
            continue;
        }

        zend_llist_add_element(header, &entry);
        prev_entry = entry;
    }

    return 1;
}

0x1: The Remote Denial of Service Vulnerability

1. If value of body part header consists of n lines
2. and first character of each line is not blank character
3. and each line did constains character ':'

當滿足以上條件時,multipart_buffer_headers函式會不斷進行記憶體申請、引數解析嘗試、並嘗試將當前行解析出的引數歸併入前一個引數鍵、並釋放當前申請記憶體塊

1. executes string copy operation twice, frees memory once.
2. Each time mergence of entry.value increase length of body part header's value. 每次申請的記憶體在不斷擴大
3. thus string copy operations will cause the consumption of CPU resources, and then the service is not available.
//If n is the length of body part header's value, and copying one byte is the unit time complexity,the time complexity of multipart_buffer_headers function is O(n*m) 

0x2: example

------WebKitFormBoundarypE33TmSNWwsMphqz 
Content-Disposition: form-data; name="file"; filename="s 
a 
a 
a 
a" 
Content-Type: application/octet-stream 

<?php phpinfo();?> 
------WebKitFormBoundarypE33TmSNWwsMphqz 

The value of Content-Disposition consists of 5 lines, and the length of the value of Content-Disposition is 5. The multipart_buffer_headers function executes Step 2.3(記憶體申請、字串複製、記憶體釋放) 4 times

1. The first time execution copys 2 bytes
2. The second execution copys 3 bytes
3. The third time execution copys 4 bytes
4. The fourth time execution copys 5 bytes
5. Thus, the multipart_buffer_headers function executes 14 times byte copy operation. 

Default maximum size of body part is 2097152 bytes (2M), It is enough to cause the consumption of CPU resources by sending multiple HTTP multipart requests to an affected application containing malicious header area of body part.

Relevant Link:

https://bugs.php.net/bug.php?id=69364

 

Copyright (c) 2015 LittleHann All rights reserved

 

相關文章