When processing a multi-part/form-data HTTP request with multiple Content-Disposition headers in the same request, a use-after-free condition can occur while cleaning up the heap structures used for storing the different parts of the request.
canProceed = filterChunkData(wp); // 处理分块的数据? if (!canProceed || wp->finalized) { return canProceed; } #if ME_GOAHEAD_UPLOAD if (wp->flags & WEBS_UPLOAD) { // 如果请求的类型是 multipart/form-data canProceed = websProcessUploadData(wp); // 根据披露信息来看,可能的漏洞入口 if (!canProceed || wp->finalized) { return canProceed; } } #endif #if !ME_ROM if (wp->putfd >= 0) { canProceed = websProcessPutData(wp); if (!canProceed || wp->finalized) { return canProceed; } } #endif #if ME_GOAHEAD_CGI if (wp->cgifd >= 0) { canProceed = websProcessCgiData(wp); if (!canProceed || wp->finalized) { return canProceed; } } #endif if (wp->eof) { wp->state = WEBS_READY; /* Prevent reading content from the next request The handler may not have been created if all the content was read in the initial read. No matter. */ socketDeleteHandler(wp->sid); } return canProceed; }
line = 0; canProceed = 1; while (canProceed && !wp->finalized && wp->uploadState != UPLOAD_CONTENT_END) { if (wp->uploadState == UPLOAD_BOUNDARY || wp->uploadState == UPLOAD_CONTENT_HEADER) { /* Parse the next input line */ line = wp->input.servp; if ((nextTok = memchr(line, '\n', bufLen(&wp->input))) == 0) { // 找到分割边界这一行的结尾换行符 /* Incomplete line */ canProceed = 0; // 找不到的话说明这个请求不对劲 break; } *nextTok++ = '\0'; // 换行符变成 \x00 nbytes = nextTok - line; // 计算分割行的长度 assert(nbytes > 0); // 必须大于 0 websConsumeInput(wp, nbytes); strim(line, "\r", WEBS_TRIM_END); // 从字符串中去掉某个字符? } switch (wp->uploadState) { case0: initUpload(wp); break;
case UPLOAD_BOUNDARY: processContentBoundary(wp, line); break;
case UPLOAD_CONTENT_HEADER: // 分割行的下一行(数据内容头部) processUploadHeader(wp, line); break;
case UPLOAD_CONTENT_DATA: canProceed = processContentData(wp); if (bufLen(&wp->input) < wp->boundaryLen) { /* Incomplete boundary - return to get more data */ canProceed = 0; } break;
case UPLOAD_CONTENT_END: break; } } bufCompact(&wp->input); return canProceed; }
此函数位于 upload.c 中,第 106 行。通读下来,函数主要的功能是定位 banner 以及(如上所述)处理 data 等。
if (scaselesscmp(headerTok, "Content-Disposition") == 0) { /* The content disposition header describes either a form variable or an uploaded file. 头部的 Content-Disposition 字段表明了这块数据到底是表单变量,还是上传的文件 Content-Disposition: form-data; name="field1" >>blank line Field Data ---boundary Content-Disposition: form-data; name="field1" filename="user.file" >>blank line File data ---boundary */ key = rest; wfree(wp->uploadVar); wfree(wp->clientFilename); wp->uploadVar = wp->clientFilename = 0; while (key && stok(key, ";\r\n", &nextPair)) {
size = bufLen(content); if (size < wp->boundaryLen) { /* Incomplete boundary. Return and get more data */ return0; } if ((bp = getBoundary(wp, content->servp, size)) == 0) { // 寻找分割行 trace(7, "uploadFilter: Got boundary filename %x", wp->clientFilename); if (wp->clientFilename) { /* No signature found yet. probably more data to come. Must handle split boundaries. */ data = content->servp; nbytes = ((int) (content->endp - data)) - (wp->boundaryLen - 1); if (writeToFile(wp, content->servp, nbytes) < 0) { /* Proceed to handle error */ return1; } websConsumeInput(wp, nbytes); /* Get more data */ return0; } } data = content->servp; nbytes = (bp) ? (bp - data) : bufLen(content);
if (nbytes > 0) { /* This is the CRLF before the boundary */ len = nbytes; if (len >= 2 && data[len - 2] == '\r' && data[len - 1] == '\n') { len -= 2; } if (wp->clientFilename) { /* Write the last bit of file data and add to the list of files and define environment variables */ if (writeToFile(wp, data, len) < 0) { // 写入文件错误 /* Proceed to handle error */ websConsumeInput(wp, nbytes); return1; } hashEnter(wp->files, wp->uploadVar, valueSymbol(file), 0); // 关键点2 defineUploadVars(wp);
} elseif (wp->uploadVar) { /* Normal string form data variables */ data[len] = '\0'; trace(5, "uploadFilter: form[%s] = %s", wp->uploadVar, data); websDecodeUrl(wp->uploadVar, wp->uploadVar, -1); websDecodeUrl(data, data, -1); websSetVar(wp, wp->uploadVar, data); } websConsumeInput(wp, nbytes); } if (wp->clientFilename) { /* Now have all the data (we've seen the boundary) */ close(wp->upfd); wp->upfd = -1; wfree(wp->clientFilename); wp->clientFilename = 0; wfree(wp->uploadTmp); wp->uploadTmp = 0; } wp->uploadState = UPLOAD_BOUNDARY; return1; }