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] != '