zoukankan      html  css  js  c++  java
  • PHP Multipart/form-data remote dos Vulnerability

    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-mastermain fc1867.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] != '' )
        {
            /*
            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 '
    ', not '
    '. 
            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] = '';
    
                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

  • 相关阅读:
    C#缓存
    js 日期相减
    无法加载图片信息处理
    一些经营性的文章,写得不蛮不错
    集线器、交换机、路由器的区别
    存储过程使用建议
    存储过程优点和缺点
    杀人游戏规则介绍(1.0~5.0版本)
    C#网络编程(1) 来源出网络的资源收集
    谈谈外包软件项目的经验
  • 原文地址:https://www.cnblogs.com/LittleHann/p/5044140.html
Copyright © 2011-2022 走看看