mirror of
https://github.com/owasp-modsecurity/ModSecurity.git
synced 2025-08-15 23:55:03 +03:00
261 lines
7.1 KiB
C++
261 lines
7.1 KiB
C++
/*
|
|
* ModSecurity, http://www.modsecurity.org/
|
|
* Copyright (c) 2015 Trustwave Holdings, Inc. (http://www.trustwave.com/)
|
|
*
|
|
* You may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* If any of the files related to licensing are missing or if you have any
|
|
* other questions related to licensing please contact Trustwave Holdings, Inc.
|
|
* directly using the email address security@modsecurity.org.
|
|
*
|
|
*/
|
|
|
|
#include "request_body_processor/multipart.h"
|
|
|
|
#include <list>
|
|
#include <iostream>
|
|
#include <string>
|
|
|
|
#include "request_body_processor/multipart_blob.h"
|
|
|
|
namespace ModSecurity {
|
|
namespace RequestBodyProcessor {
|
|
|
|
Multipart::Multipart(std:: string header)
|
|
: crlf(false),
|
|
containsDataAfter(false),
|
|
containsDataBefore(false),
|
|
lf(false),
|
|
missingSemicolon(false),
|
|
invalidQuote(false),
|
|
boundaryStartsWithWhiteSpace(false),
|
|
boundaryIsQuoted(false),
|
|
m_header(header) {
|
|
}
|
|
|
|
|
|
bool Multipart::init() {
|
|
if (m_header.length() > 1024) {
|
|
debug(4, "Multipart: Invalid boundary in Content-Type (length).");
|
|
return false;
|
|
}
|
|
|
|
std::size_t boundary_pos = m_header.find("boundary");
|
|
if (boundary_pos < 0) {
|
|
if (boundary_pos > 0 && m_header.find("boundary", boundary_pos) > 0) {
|
|
debug(4, "Multipart: Multiple boundary parameters in " \
|
|
"Content-Type.");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
std::string boundary = m_header.c_str() + boundary_pos;
|
|
std::size_t semicolon_pos = boundary.find(";");
|
|
if (semicolon_pos != std::string::npos
|
|
&& boundary.find(";", semicolon_pos + 1) != std::string::npos) {
|
|
debug(4, "Multipart: Invalid boundary in Content-Type. (malformed). " \
|
|
"Too many semicolons.");
|
|
return false;
|
|
}
|
|
|
|
if (semicolon_pos == std::string::npos) {
|
|
debug(4, "Multipart: Missing semicolon.");
|
|
this->missingSemicolon = true;
|
|
}
|
|
|
|
if (boundary.at(8) != '=') {
|
|
debug(4, "Multipart: Invalid boundary in Content-Type. (malformed). " \
|
|
"Missing equals.");
|
|
return false;
|
|
}
|
|
|
|
if (boundary.at(8 + 1) == ' ') {
|
|
boundaryStartsWithWhiteSpace = true;
|
|
debug(4, "Multipart: Boundary starts with a white space");
|
|
}
|
|
|
|
if ((boundaryStartsWithWhiteSpace && boundary.at(8 + 2) == '"') ||
|
|
(!boundaryStartsWithWhiteSpace && boundary.at(8 + 1) == '"')) {
|
|
boundaryIsQuoted = true;
|
|
debug(4, "Multipart: Boundary inside quotes");
|
|
}
|
|
|
|
if (boundaryIsQuoted && boundary.at(boundary.length()-1) != '"') {
|
|
debug(4, "Multipart: Invalid boundary in Content-type (quote).");
|
|
return false;
|
|
}
|
|
|
|
#if 0
|
|
Not checking
|
|
/* Case-insensitive test for the string "boundary" in the boundary. */
|
|
if (count_boundary_params(msr->mp, msr->mpd->boundary) != 0) {
|
|
msr->mpd->flag_error = 1;
|
|
*error_msg = apr_psprintf(msr->mp, "Multipart: Invalid boundary " \
|
|
"in C-T (content).");
|
|
return -1;
|
|
}
|
|
#endif // if 0
|
|
|
|
int real_boundary_pos = 9;
|
|
|
|
if (boundaryStartsWithWhiteSpace) {
|
|
real_boundary_pos++;
|
|
}
|
|
|
|
if (boundaryIsQuoted) {
|
|
real_boundary_pos++;
|
|
}
|
|
|
|
m_boundary = boundary.c_str() + real_boundary_pos;
|
|
|
|
if (boundaryIsQuoted) {
|
|
m_boundary.pop_back();
|
|
}
|
|
|
|
if (boundaryContainsOnlyValidCharacters() == false) {
|
|
debug(4, "Multipart: Invalid boundary in Content-type " \
|
|
"(invalid characters).");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool Multipart::boundaryContainsOnlyValidCharacters() {
|
|
if (m_boundary.empty()) {
|
|
return false;
|
|
}
|
|
|
|
for (int i = 0; i < m_boundary.length(); i++) {
|
|
int c = m_boundary.at(i);
|
|
|
|
/* Control characters and space not allowed. */
|
|
/* Non-ASCII characters not allowed. */
|
|
if (c < 32 || c > 126) {
|
|
return false;
|
|
}
|
|
|
|
switch (c) {
|
|
/* Special characters not allowed. */
|
|
case '(' :
|
|
case ')' :
|
|
case '<' :
|
|
case '>' :
|
|
case '@' :
|
|
case ',' :
|
|
case ';' :
|
|
case ':' :
|
|
case '\\' :
|
|
case '"' :
|
|
case '/' :
|
|
case '[' :
|
|
case ']' :
|
|
case '?' :
|
|
case '=' :
|
|
return false;
|
|
break;
|
|
default :
|
|
break;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Multipart::checkForCrlfLf(const std::string &data) {
|
|
size_t start = 0;
|
|
size_t pos = 0;
|
|
|
|
pos = data.find("\n", start);
|
|
while (pos != std::string::npos) {
|
|
if (pos > 1 && data.at(pos-1) == '\r') {
|
|
this->crlf = true;
|
|
} else {
|
|
this->lf = true;
|
|
}
|
|
pos = data.find("\n", pos + 1);
|
|
}
|
|
}
|
|
|
|
bool Multipart::process(std::string data) {
|
|
std::list<std::string> blobs;
|
|
size_t start = data.find(m_boundary);
|
|
size_t endl = 1;
|
|
size_t lastValidBoundary = 0;
|
|
size_t firstValidBoundary = start;
|
|
double files_size = 0;
|
|
|
|
if (start != 0) {
|
|
debug(4, "Multipart: Boundary was not the first thing.");
|
|
this->containsDataBefore = true;
|
|
}
|
|
|
|
while (start != std::string::npos) {
|
|
size_t end = data.find(m_boundary, start + m_boundary.length());
|
|
if (end == std::string::npos) {
|
|
start = end;
|
|
continue;
|
|
}
|
|
std::string block = std::string(data, start + m_boundary.length() +
|
|
+ endl, end - (start + m_boundary.length() + endl) - endl);
|
|
|
|
checkForCrlfLf(block);
|
|
|
|
blobs.push_back(block);
|
|
lastValidBoundary = end;
|
|
start = end;
|
|
}
|
|
|
|
size_t lastPiece = m_boundary.length() + lastValidBoundary \
|
|
+ firstValidBoundary + 2;
|
|
if (this->crlf) {
|
|
lastPiece = lastPiece + 2;
|
|
} else {
|
|
lastPiece = lastPiece + 1;
|
|
}
|
|
|
|
if (data.length() > lastPiece) {
|
|
this->containsDataAfter = true;
|
|
}
|
|
|
|
std::string filename("");
|
|
std::string name("");
|
|
for (std::string x : blobs) {
|
|
MultipartBlob m(x, this);
|
|
|
|
if (m.name.empty() == false) {
|
|
name = m.name;
|
|
}
|
|
if (m.filename.empty() == false) {
|
|
filename = m.filename;
|
|
variables.emplace("FILES:" + m.name, m.filename);
|
|
variables.emplace("FILES_NAMES:" + m.name, m.name);
|
|
variables.emplace("FILES_SIZES:" + m.name,
|
|
std::to_string(m.content.size()));
|
|
variables.emplace("FILES_TMP_CONTENT:" + m.name, m.content);
|
|
files_size = files_size + m.content.size();
|
|
}
|
|
if (m.invalidQuote) {
|
|
debug(4, "Multipart: Found invalid quoting.");
|
|
this->invalidQuote = true;
|
|
}
|
|
}
|
|
if (filename.empty() == false) {
|
|
variables.emplace("MULTIPART_FILENAME", filename);
|
|
}
|
|
if (name.empty() == false) {
|
|
variables.emplace("MULTIPART_NAME", name);
|
|
}
|
|
variables.emplace("FILES_COMBINED_SIZE", std::to_string(files_size));
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
} // namespace RequestBodyProcessor
|
|
} // namespace ModSecurity
|