From 2e3da7ea24b0e89ab3c69efa8822114f38bd7a01 Mon Sep 17 00:00:00 2001 From: Felipe Zimmerle Date: Wed, 8 Jun 2016 21:48:02 -0300 Subject: [PATCH] Better support for multipart ModSecurity v2.x parser was ported into 3.x branch. All the multipart related variables should be workbale. --- headers/modsecurity/rules_properties.h | 15 +- src/Makefile.am | 1 - src/parser/seclang-parser.yy | 9 + src/parser/seclang-scanner.ll | 6 +- src/request_body_processor/multipart.cc | 1509 ++++++++-- src/request_body_processor/multipart.h | 157 +- src/request_body_processor/multipart_blob.cc | 142 - src/request_body_processor/multipart_blob.h | 54 - src/rules.cc | 12 + src/transaction.cc | 66 +- src/utils.cc | 2 +- .../request-body-parser-multipart-crlf.json | 67 + .../request-body-parser-multipart.json | 2453 +++++++++++++++++ .../test-cases/regression/variable-FILES.json | 8 +- .../variable-FILES_COMBINED_SIZE.json | 12 +- .../regression/variable-FILES_NAMES.json | 8 +- .../regression/variable-FILES_SIZES.json | 8 +- .../variable-FILES_TMP_CONTENT.json | 63 - .../variable-MULTIPART_CRLF_LF_LINES.json | 16 +- .../variable-MULTIPART_FILENAME.json | 16 +- ...able-MULTIPART_INVALID_HEADER_FOLDING.json | 61 + .../regression/variable-MULTIPART_NAME.json | 16 +- .../variable-MULTIPART_STRICT_ERROR.json | 12 +- 23 files changed, 4117 insertions(+), 596 deletions(-) delete mode 100644 src/request_body_processor/multipart_blob.cc delete mode 100644 src/request_body_processor/multipart_blob.h create mode 100644 test/test-cases/regression/request-body-parser-multipart-crlf.json create mode 100644 test/test-cases/regression/request-body-parser-multipart.json delete mode 100644 test/test-cases/regression/variable-FILES_TMP_CONTENT.json create mode 100644 test/test-cases/regression/variable-MULTIPART_INVALID_HEADER_FOLDING.json diff --git a/headers/modsecurity/rules_properties.h b/headers/modsecurity/rules_properties.h index 752794a9..7e7fb10a 100644 --- a/headers/modsecurity/rules_properties.h +++ b/headers/modsecurity/rules_properties.h @@ -60,7 +60,11 @@ class RulesProperties { requestBodyLimitAction(ProcessPartialBodyLimitAction), responseBodyLimit(0), responseBodyLimitAction(ProcessPartialBodyLimitAction), - secRuleEngine(DetectionOnlyRuleEngine) { } + secRuleEngine(DetectionOnlyRuleEngine), + uploadKeepFiles(0), + uploadFileLimit(100), + uploadFileMode(0), + tmpSaveUploadedFiles(false) { } explicit RulesProperties(DebugLog *debugLog) : audit_log(NULL), @@ -76,8 +80,11 @@ class RulesProperties { requestBodyLimitAction(ProcessPartialBodyLimitAction), responseBodyLimit(0), responseBodyLimitAction(ProcessPartialBodyLimitAction), - secRuleEngine(DetectionOnlyRuleEngine) { } - + secRuleEngine(DetectionOnlyRuleEngine), + uploadKeepFiles(0), + uploadFileLimit(100), + uploadFileMode(0), + tmpSaveUploadedFiles(false) { } /* RulesProperties(const RulesProperties &other) @@ -219,6 +226,8 @@ class RulesProperties { int uploadFileLimit; int uploadFileMode; std::string uploadDirectory; + std::string uploadTmpDirectory; + bool tmpSaveUploadedFiles; audit_log::AuditLog *audit_log; diff --git a/src/Makefile.am b/src/Makefile.am index 62088939..befb2412 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -200,7 +200,6 @@ COLLECTION = \ BODY_PROCESSORS = \ request_body_processor/multipart.cc \ - request_body_processor/multipart_blob.cc \ request_body_processor/xml.cc diff --git a/src/parser/seclang-parser.yy b/src/parser/seclang-parser.yy index 980fdde5..ea977c06 100644 --- a/src/parser/seclang-parser.yy +++ b/src/parser/seclang-parser.yy @@ -211,6 +211,7 @@ using modsecurity::Variables::XML; %token CONFIG_DIR_AUDIT_TPE %token CONFIG_UPDLOAD_KEEP_FILES +%token CONFIG_UPDLOAD_SAVE_TMP_FILES %token CONFIG_UPLOAD_FILE_LIMIT %token CONFIG_UPLOAD_FILE_MODE %token CONFIG_UPLOAD_DIR @@ -403,6 +404,14 @@ audit_log: { driver.uploadDirectory = $1; } + | CONFIG_UPDLOAD_SAVE_TMP_FILES CONFIG_VALUE_ON + { + driver.tmpSaveUploadedFiles = true; + } + | CONFIG_UPDLOAD_SAVE_TMP_FILES CONFIG_VALUE_OFF + { + driver.tmpSaveUploadedFiles = false; + } ; actings: diff --git a/src/parser/seclang-scanner.ll b/src/parser/seclang-scanner.ll index dc0b852d..990f0fc9 100755 --- a/src/parser/seclang-scanner.ll +++ b/src/parser/seclang-scanner.ll @@ -99,6 +99,7 @@ CONFIG_DIR_AUDIT_TPE (?i:SecAuditLogType) CONFIG_UPLOAD_FILE_LIMIT (?i:SecUploadFileLimit) CONFIG_UPLOAD_FILE_MODE (?i:SecUploadFileMode) CONFIG_UPDLOAD_KEEP_FILES (?i:SecUploadKeepFiles) +CONFIG_UPDLOAD_SAVE_TMP_FILES (?i:SecTmpSaveUploadedFiles) CONFIG_UPLOAD_DIR (?i:SecUploadDir) @@ -125,7 +126,7 @@ OPERATOR_GEOIP (?i:@geoLookup) TRANSFORMATION t:(?i:(parityZero7bit|parityOdd7bit|parityEven7bit|sqlHexDecode|cmdLine|sha1|md5|hexEncode|lowercase|urlDecodeUni|urlDecode|none|compressWhitespace|removeWhitespace|replaceNulls|removeNulls|htmlEntityDecode|jsDecode|cssDecode|trim|normalizePathWin|normalisePathWin|normalisePath|length|utf8toUnicode|urldecode|removeCommentsChar|removeComments|replaceComments)) -VARIABLE (?i:(RESOURCE|ARGS_COMBINED_SIZE|ARGS_GET_NAMES|ARGS_POST_NAMES|FILES_COMBINED_SIZE|FULL_REQUEST_LENGTH|REQUEST_BODY_LENGTH|REQUEST_URI_RAW|UNIQUE_ID|SERVER_PORT|SERVER_ADDR|REMOTE_PORT|REMOTE_HOST|PATH_INFO|MULTIPART_CRLF_LF_LINES|MATCHED_VAR_NAME|MATCHED_VAR|INBOUND_DATA_ERROR|OUTBOUND_DATA_ERROR|FULL_REQUEST|AUTH_TYPE|ARGS_NAMES|REMOTE_ADDR|REQUEST_BASENAME|REQUEST_BODY|REQUEST_FILENAME|REQUEST_HEADERS_NAMES|REQUEST_METHOD|REQUEST_PROTOCOL|REQUEST_URI|RESPONSE_BODY|RESPONSE_CONTENT_LENGTH|RESPONSE_CONTENT_TYPE|RESPONSE_HEADERS_NAMES|RESPONSE_PROTOCOL|RESPONSE_STATUS|REQBODY_PROCESSOR|USERID|SESSIONID)) +VARIABLE (?i:(MULTIPART_DATA_AFTER|RESOURCE|ARGS_COMBINED_SIZE|ARGS_GET_NAMES|ARGS_POST_NAMES|FILES_TMPNAMES|FILES_COMBINED_SIZE|FULL_REQUEST_LENGTH|REQUEST_BODY_LENGTH|REQUEST_URI_RAW|UNIQUE_ID|SERVER_PORT|SERVER_ADDR|REMOTE_PORT|REMOTE_HOST|PATH_INFO|MULTIPART_CRLF_LF_LINES|MATCHED_VAR_NAME|MATCHED_VAR|INBOUND_DATA_ERROR|OUTBOUND_DATA_ERROR|FULL_REQUEST|AUTH_TYPE|ARGS_NAMES|REMOTE_ADDR|REQUEST_BASENAME|REQUEST_BODY|REQUEST_FILENAME|REQUEST_HEADERS_NAMES|REQUEST_METHOD|REQUEST_PROTOCOL|REQUEST_URI|RESPONSE_BODY|RESPONSE_CONTENT_LENGTH|RESPONSE_CONTENT_TYPE|RESPONSE_HEADERS_NAMES|RESPONSE_PROTOCOL|RESPONSE_STATUS|REQBODY_PROCESSOR|USERID|SESSIONID)) VARIABLE_COL (?i:(SESSION|GLOBAL|ARGS_POST|ARGS_GET|ARGS|FILES_SIZES|FILES_NAMES|FILES_TMP_CONTENT|MULTIPART_FILENAME|MULTIPART_NAME|MATCHED_VARS_NAMES|MATCHED_VARS|FILES|QUERY_STRING|REQUEST_COOKIES|REQUEST_HEADERS|RESPONSE_HEADERS|GEO|IP|REQUEST_COOKIES_NAMES)) VARIABLE_TX (?i:TX) @@ -148,7 +149,7 @@ RUN_TIME_VAR_TIME_WDAY (?i:TIME_WDAY) RUN_TIME_VAR_TIME_YEAR (?i:TIME_YEAR) RUN_TIME_VAR_XML (?i:XML) -VARIABLENOCOLON (?i:REQBODY_ERROR|REQBODY_PROCESSOR_ERROR|MULTIPART_HEADER_FOLDING|MULTIPART_INVALID_HEADER_FOLDING|MULTIPART_STRICT_ERROR|MULTIPART_UNMATCHED_BOUNDARY|REMOTE_ADDR|REQUEST_LINE) +VARIABLENOCOLON (?i:MULTIPART_FILE_LIMIT_EXCEEDED|MULTIPART_INVALID_QUOTING|REQBODY_ERROR|REQBODY_PROCESSOR_ERROR|MULTIPART_HEADER_FOLDING|MULTIPART_INVALID_HEADER_FOLDING|MULTIPART_STRICT_ERROR|MULTIPART_UNMATCHED_BOUNDARY|REMOTE_ADDR|REQUEST_LINE) CONFIG_VALUE_ON (?i:On) CONFIG_VALUE_OFF (?i:Off) @@ -230,6 +231,7 @@ CONFIG_DIR_UNICODE_MAP_FILE (?i:SecUnicodeMapFile) {CONFIG_UPLOAD_FILE_LIMIT}[ ]{CONFIG_VALUE_NUMBER} { return yy::seclang_parser::make_CONFIG_UPLOAD_FILE_LIMIT(strchr(yytext, ' ') + 1, *driver.loc.back()); } {CONFIG_UPLOAD_FILE_MODE}[ ]{CONFIG_VALUE_NUMBER} { return yy::seclang_parser::make_CONFIG_UPLOAD_FILE_MODE(strchr(yytext, ' ') + 1, *driver.loc.back()); } {CONFIG_UPDLOAD_KEEP_FILES} { return yy::seclang_parser::make_CONFIG_UPDLOAD_KEEP_FILES(yytext, *driver.loc.back()); } +{CONFIG_UPDLOAD_SAVE_TMP_FILES} { return yy::seclang_parser::make_CONFIG_UPDLOAD_SAVE_TMP_FILES(yytext, *driver.loc.back()); } {CONFIG_UPLOAD_DIR}[ ]{CONFIG_VALUE_PATH} { return yy::seclang_parser::make_CONFIG_UPLOAD_DIR(strchr(yytext, ' ') + 1, *driver.loc.back()); } %{ /* Debug log entries */ %} diff --git a/src/request_body_processor/multipart.cc b/src/request_body_processor/multipart.cc index 34aa1cb5..e099b9e9 100644 --- a/src/request_body_processor/multipart.cc +++ b/src/request_body_processor/multipart.cc @@ -15,147 +15,137 @@ #include "request_body_processor/multipart.h" +#include +#include +#include +#include +#include +#include +#include #include #include #include -#include "request_body_processor/multipart_blob.h" #include "modsecurity/collection/collections.h" +#include "src/utils.h" namespace modsecurity { namespace RequestBodyProcessor { + Multipart::Multipart(std:: string header, Transaction *transaction) - : crlf(false), - containsDataAfter(false), - containsDataBefore(false), - lf(false), - missingSemicolon(false), - invalidQuote(false), - boundaryStartsWithWhiteSpace(false), - boundaryIsQuoted(false), + : m_boundary_count(0), + m_buf_contains_line(0), + m_bufleft(0), + m_buf_offset(0), + m_bufptr(NULL), + m_flag_boundary_quoted(0), + m_flag_boundary_whitespace(0), + m_flag_crlf_line(0), + m_flag_data_after(0), + m_flag_data_before(0), + m_flag_error(0), + m_flag_file_limit_exceeded(0), + m_flag_header_folding(0), + m_flag_invalid_header_folding(0), + m_flag_invalid_part(0), + m_flag_invalid_quoting(0), + m_flag_lf_line(0), + m_flag_missing_semicolon(0), + m_flag_unmatched_boundary(0), + m_header(header), + m_is_complete(0), + m_mpp_state(0), + m_nfiles(0), + m_seen_data(0), m_transaction(transaction), - m_header(header) { -} + m_reqbody_no_files_length(0) + { } -bool Multipart::init() { - if (m_header.length() > 1024) { -#ifndef NO_LOGS - debug(4, "Multipart: Invalid boundary in Content-Type (length)."); -#endif - return false; - } +Multipart::~Multipart() { + debug(4, "Multipart: Cleanup started (remove files " \ + + std::to_string(!m_transaction->m_rules->uploadKeepFiles) \ + + ")"); - std::size_t boundary_pos = m_header.find("boundary"); - if (boundary_pos != std::string::npos && - m_header.find("boundary", boundary_pos + 1) != std::string::npos) { -#ifndef NO_LOGS - debug(4, "Multipart: Multiple boundary parameters in " \ - "Content-Type."); -#endif - return false; - } + if (m_transaction->m_rules->uploadKeepFiles == false) { + for (MultipartPart *m : m_parts) { + if (m->m_type == MULTIPART_FILE) { + if (!m->m_tmp_file_name.empty()) { + /* make sure it is closed first */ + if (m->m_tmp_file_fd > 0) { + close(m->m_tmp_file_fd); + m->m_tmp_file_fd = -1; + } - 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) { -#ifndef NO_LOGS - debug(4, "Multipart: Invalid boundary in Content-Type. (malformed). " \ - "Too many semicolons."); -#endif - return false; - } - - if (semicolon_pos == std::string::npos) { -#ifndef NO_LOGS - debug(4, "Multipart: Missing semicolon."); -#endif - this->missingSemicolon = true; - } - - if (boundary.at(8) != '=') { -#ifndef NO_LOGS - debug(4, "Multipart: Invalid boundary in Content-Type. (malformed). " \ - "Missing equals."); -#endif - return false; - } - - if (boundary.at(8 + 1) == ' ') { - boundaryStartsWithWhiteSpace = true; -#ifndef NO_LOGS - debug(4, "Multipart: Boundary starts with a white space"); -#endif - } - - if ((boundaryStartsWithWhiteSpace && boundary.at(8 + 2) == '"') || - (!boundaryStartsWithWhiteSpace && boundary.at(8 + 1) == '"')) { - boundaryIsQuoted = true; -#ifndef NO_LOGS - debug(4, "Multipart: Boundary inside quotes"); -#endif - } - - if (boundaryIsQuoted && boundary.at(boundary.length()-1) != '"') { -#ifndef NO_LOGS - debug(4, "Multipart: Invalid boundary in Content-type (quote)."); -#endif - 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; + if (unlink(m->m_tmp_file_name.c_str()) < 0) { + debug(1, "Multipart: Failed to delete file (part) \"" \ + + m->m_tmp_file_name + "\" because " \ + + std::to_string(errno) + "(" \ + + strerror(errno) + ")"); + } else { + debug(4, "Multipart: Failed to delete file (part) \"" \ + + m->m_tmp_file_name + "\""); + } + } + } } -#endif // if 0 - - int real_boundary_pos = 9; - - if (boundaryStartsWithWhiteSpace) { - real_boundary_pos++; } - if (boundaryIsQuoted) { - real_boundary_pos++; + while (!m_parts.empty()) { + m_parts.pop_front(); } - - m_boundary = boundary.c_str() + real_boundary_pos; - - if (boundaryIsQuoted) { - m_boundary.pop_back(); - } - - if (boundaryContainsOnlyValidCharacters() == false) { -#ifndef NO_LOGS - debug(4, "Multipart: Invalid boundary in Content-type " \ - "(invalid characters)."); -#endif - return false; - } - - return true; } -bool Multipart::boundaryContainsOnlyValidCharacters() { - if (m_boundary.empty()) { - return false; +int Multipart::is_token_char(unsigned char c) { + /* ENH Is the performance important at all? We could use a table instead. */ + + /* CTLs not allowed */ + if ((c <= 32) || (c >= 127)) { + return 0; } - for (int i = 0; i < m_boundary.length(); i++) { - int c = m_boundary.at(i); + switch (c) { + case '(' : + case ')' : + case '<' : + case '>' : + case '@' : + case ',' : + case ';' : + case ':' : + case '\\' : + case '"' : + case '/' : + case '[' : + case ']' : + case '?' : + case '=' : + return 0; + } + return 1; +} + + +int Multipart::boundary_characters_valid(const char *boundary) { + const unsigned char *p = (unsigned char *)boundary; + unsigned char c; + + if (p == NULL) { + return -1; + } + + while ((c = *p) != '\0') { /* Control characters and space not allowed. */ + if (c < 32) { + return 0; + } + /* Non-ASCII characters not allowed. */ - if (c < 32 || c > 126) { - return false; + if (c > 126) { + return 0; } switch (c) { @@ -175,124 +165,1237 @@ bool Multipart::boundaryContainsOnlyValidCharacters() { case ']' : case '?' : case '=' : - return false; + return 0; break; + default : + /* Do nothing. */ break; } + + p++; + } + + return 1; +} + + +void Multipart::validate_quotes(const char *data) { + int i, len; + + if (data == NULL) + return; + + len = strlen(data); + + for (i = 0; i < len; i++) { + if (data[i] == '\'') { + debug(9, "Multipart: Invalid quoting detected: " \ + + std::string(data) + " length " \ + + std::to_string(len) + " bytes"); + m_flag_invalid_quoting = 1; + } + } +} + + +int Multipart::parse_content_disposition(const char *c_d_value) { + const char *p = NULL; + + /* accept only what we understand */ + if (strncmp(c_d_value, "form-data", 9) != 0) { + return -1; + } + + /* see if there are any other parts to parse */ + + p = c_d_value + 9; + while ((*p == '\t') || (*p == ' ')) p++; + if (*p == '\0') { + return 1; /* this is OK */ + } + + if (*p != ';') { + return -2; + } + p++; + + /* parse the appended parts */ + + while (*p != '\0') { + const char *start = NULL; + std::string name; + std::string value; + + /* go over the whitespace */ + while ((*p == '\t') || (*p == ' ')) { + p++; + } + + if (*p == '\0') { + return -3; + } + + start = p; + while ((*p != '\0') && (*p != '=') && (*p != '\t') && (*p != ' ')) { + p++; + } + + if (*p == '\0') { + return -4; + } + + name = std::string(start, (p - start)); + + while ((*p == '\t') || (*p == ' ')) { + p++; + } + + if (*p == '\0') { + return -5; + } + + if (*p != '=') { + return -13; + } + + p++; + + while ((*p == '\t') || (*p == ' ')) { + p++; + } + + if (*p == '\0') { + return -6; + } + + /* Accept both quotes as some backends will accept them, but + * technically "'" is invalid and so flag_invalid_quoting is + * set so the user can deal with it in the rules if they so wish. + */ + + if ((*p == '"') || (*p == '\'')) { + /* quoted */ + char quote = *p; + + if (quote == '\'') { + m_flag_invalid_quoting = 1; + } + + p++; + if (*p == '\0') { + return -7; + } + + while (*p != '\0') { + if (*p == '\\') { + if (*(p + 1) == '\0') { + /* improper escaping */ + return -8; + } + /* only quote and \ can be escaped */ + if ((*(p + 1) == quote) || (*(p + 1) == '\\')) { + p++; + } else { + /* improper escaping */ + + /* We allow for now because IE sends + * improperly escaped content and there's + * nothing we can do about it. + * + * return -9; + */ + } + } else if (*p == quote) { + break; + } + + value.append((p++), 1); + } + + p++; /* go over the quote at the end */ + + } else { + /* not quoted */ + + start = p; + while ((*p != '\0') && (is_token_char(*p))) { + p++; + } + value.assign(start, p - start); + } + + /* evaluate part */ + if (name == "name") { + validate_quotes(value.c_str()); + + m_transaction->m_collections.storeOrUpdateFirst("MULTIPART_NAME", + value); + + if (!m_mpp->m_name.empty()) { + debug(4, "Multipart: Warning: Duplicate Content-Disposition " \ + "name: " + value + ". Previously: " + m_mpp->m_name + ""); + return -14; + } + m_mpp->m_name.assign(value); + debug(9, "Multipart: Content-Disposition name: " + value + "."); + } else if (name == "filename") { + validate_quotes(value.c_str()); + collection::Collections *c = &m_transaction->m_collections; + c->storeOrUpdateFirst("MULTIPART_FILENAME", value); + + if (!m_mpp->m_filename.empty()) { + debug(4, "Multipart: Warning: Duplicate Content-Disposition " \ + "filename: " + value + "."); + return -15; + } + m_mpp->m_filename.assign(value); + + debug(9, "Multipart: Content-Disposition filename: " \ + + value + "."); + } else { + return -11; + } + + if (*p != '\0') { + while ((*p == '\t') || (*p == ' ')) { + p++; + } + + /* the next character must be a zero or a semi-colon */ + if (*p == '\0') { + return 1; /* this is OK */ + } + if (*p != ';') { + p--; + if (*p == '\'' || *p == '\"') { + debug(9, "Multipart: Invalid quoting detected: " \ + + std::string(p) + " length " \ + + std::to_string(strlen(p)) + " bytes"); + m_flag_invalid_quoting = 1; + } + p++; + return -12; + } + p++; /* move over the semi-colon */ + } + + /* loop will stop when (*p == '\0') */ + } + + return 1; +} + + +int Multipart::tmp_file_name(std::string *filename) { + std::string path; + struct tm timeinfo; + char tstr[300]; + char *tmp; + int fd; + int mode; + time_t tt = time(NULL); + + localtime_r(&tt, &timeinfo); + + path = m_transaction->m_rules->uploadDirectory; + mode = m_transaction->m_rules->uploadFileMode; + + memset(tstr, '\0', 300); + strftime(tstr, 299, "/%Y%m%d-%H%M%S", &timeinfo); + path = path + tstr + "-" + m_transaction->m_id; + path = path + "-file-XXXXXX"; + + tmp = strdup(path.c_str()); + + fd = mkstemp(tmp); + filename->assign(tmp); + free(tmp); + if ((fd != -1) && (mode != 0)) { + if (fchmod(fd, mode) == -1) { + return -1; + } + } + + return fd; +} + + +int Multipart::process_part_data() { + char *p = m_buf + (MULTIPART_BUF_SIZE - m_bufleft); + char localreserve[2] = { '\0', '\0' }; /* initialized to quiet warning */ + int bytes_reserved = 0; + + /* Preserve some bytes for later. */ + if (((MULTIPART_BUF_SIZE - m_bufleft) >= 1) && (*(p - 1) == '\n')) { + if (((MULTIPART_BUF_SIZE - m_bufleft) >= 2) && (*(p - 2) == '\r')) { + /* Two bytes. */ + bytes_reserved = 2; + localreserve[0] = *(p - 2); + localreserve[1] = *(p - 1); + m_bufleft += 2; + *(p - 2) = 0; + } else { + /* Only one byte. */ + bytes_reserved = 1; + localreserve[0] = *(p - 1); + localreserve[1] = 0; + m_bufleft += 1; + *(p - 1) = 0; + } + } + + /* add data to the part we are building */ + if (m_mpp->m_type == MULTIPART_FILE) { + int extract = m_transaction->m_rules->uploadKeepFiles \ + || m_transaction->m_rules->tmpSaveUploadedFiles; + + /* remember where we started */ + if (m_mpp->m_length == 0) { + m_mpp->m_offset = m_buf_offset; + } + + /* check if the file limit has been reached */ + if (extract && m_transaction->m_rules->uploadFileLimit + && (m_nfiles >= m_transaction->m_rules->uploadFileLimit)) { + if (m_flag_file_limit_exceeded == 0) { + debug(1, "Multipart: Upload file limit exceeded " \ + + std::to_string(m_transaction->m_rules->uploadFileLimit) \ + + ". Use SecUploadFileLimit to change the limit."); + m_flag_file_limit_exceeded = 1; + } + extract = 0; + } + /* only store individual files on disk if we are going + * to keep them or if we need to have them approved later + */ + if (extract) { + /* first create a temporary file if we don't have it already */ + if (m_mpp->m_tmp_file_fd == 0) { + std::string path; + m_mpp->m_tmp_file_fd = tmp_file_name(&path); + + /* construct temporary file name */ + m_mpp->m_tmp_file_name = path; + + /* do we have an opened file? */ + if (m_mpp->m_tmp_file_fd < 0) { + debug(1, "Multipart: Failed to create file: " \ + + m_mpp->m_tmp_file_name); + return -1; + } + /* keep track of the files count */ + + m_nfiles++; + + debug(4, "Multipart: Created temporary file " \ + + std::to_string(m_nfiles) + " (mode 04o): " \ + + m_mpp->m_tmp_file_name); + } + + /* write the reserve first */ + if (m_reserve[0] != 0) { + if (write(m_mpp->m_tmp_file_fd, &m_reserve[1], m_reserve[0]) + != m_reserve[0]) { + debug(1, "Multipart: writing to \"" \ + + m_mpp->m_tmp_file_name + "\" failed"); + return -1; + } + + m_mpp->m_tmp_file_size += m_reserve[0]; + m_mpp->m_length += m_reserve[0]; + } + + /* write data to the file */ + + if (write(m_mpp->m_tmp_file_fd, m_buf, + MULTIPART_BUF_SIZE - m_bufleft) + != (MULTIPART_BUF_SIZE - m_bufleft)) { + debug(1, "Multipart: writing to \"" \ + + m_mpp->m_tmp_file_name + "\" failed"); + return -1; + } + + m_mpp->m_tmp_file_size += (MULTIPART_BUF_SIZE - m_bufleft); + m_mpp->m_length += (MULTIPART_BUF_SIZE - m_bufleft); + } else { + /* just keep track of the file size */ + m_mpp->m_tmp_file_size += (MULTIPART_BUF_SIZE - m_bufleft) \ + + m_reserve[0]; + m_mpp->m_length += (MULTIPART_BUF_SIZE - m_bufleft) + m_reserve[0]; + } + } else if (m_mpp->m_type == MULTIPART_FORMDATA) { + std::string d; + + /* The buffer contains data so increase the data length counter. */ + m_reqbody_no_files_length += (MULTIPART_BUF_SIZE - m_bufleft) \ + + m_reserve[0]; + + /* add this part to the list of parts */ + + /* remember where we started */ + if (m_mpp->m_length == 0) { + m_mpp->m_offset = m_buf_offset; + } + + if (m_reserve[0] != 0) { + d.assign(&(m_reserve[1]), m_reserve[0]); + d.assign(m_buf, MULTIPART_BUF_SIZE - m_bufleft); + + m_mpp->m_length += d.size(); + } else { + d.assign(m_buf, MULTIPART_BUF_SIZE - m_bufleft); + m_mpp->m_length += d.size(); + } + + m_mpp->m_value_parts.push_back(d); + + debug(9, "Multipart: Added data to variable: " + d); + } else { + debug(1, "Multipart: unknown part type: " \ + + std::to_string(m_mpp->m_type)); + return false; + } + + /* store the reserved bytes to the multipart + * context so that they don't get lost + */ + if (bytes_reserved) { + m_reserve[0] = bytes_reserved; + m_reserve[1] = localreserve[0]; + m_reserve[2] = localreserve[1]; + m_buf_offset += bytes_reserved; + } else { + m_buf_offset -= m_reserve[0]; + m_reserve[0] = 0; + } + + return true; +} + + +int Multipart::process_part_header() { + int i, len, rc; + + /* Check for nul bytes. */ + len = MULTIPART_BUF_SIZE - m_bufleft; + for (i = 0; i < len; i++) { + if (m_buf[i] == '\0') { + debug(1, "Multipart: Nul byte in part headers."); + return false; + } + } + + /* The buffer is data so increase the data length counter. */ + m_reqbody_no_files_length += (MULTIPART_BUF_SIZE - m_bufleft); + + if (len > 1) { + if (m_buf[len - 2] == '\r') { + m_flag_crlf_line = 1; + } else { + m_flag_lf_line = 1; + } + } else { + m_flag_lf_line = 1; + } + + /* Is this an empty line? */ + if (((m_buf[0] == '\r') && (m_buf[1] == '\n') && (m_buf[2] == '\0')) + || ((m_buf[0] == '\n') && (m_buf[1] == '\0'))) { /* Empty line. */ + std::string header_value(""); + + if (m_mpp->m_headers.count("Content-Disposition") == 0) { + debug(1, "Multipart: Part missing Content-Disposition header."); + + return false; + } + header_value = m_mpp->m_headers.at("Content-Disposition"); + + rc = parse_content_disposition(header_value.c_str()); + if (rc < 0) { + debug(1, "Multipart: Invalid Content-Disposition header (" + + std::to_string(rc) + "): " + header_value); + + return false; + } + + if (m_mpp->m_name.empty()) { + debug(1, "Multipart: Content-Disposition header missing " \ + "name field."); + + return false; + } + + if (!m_mpp->m_filename.empty()) { + /* Some parsers use crude methods to extract the name and filename + * values from the C-D header. We need to check for the case where they + * didn't understand C-D but we did. + */ + if (strstr(header_value.c_str(), "filename=") == NULL) { + debug(1, "Multipart: Invalid Content-Disposition " \ + "header (filename)."); + return false; + } + + m_mpp->m_type = MULTIPART_FILE; + } else { + m_mpp->m_type = MULTIPART_FORMDATA; + } + + m_mpp_state = 1; + m_mpp->m_last_header_name.assign(""); + } else { /* Header line. */ + if (isspace(m_buf[0])) { + std::string header_value; + char *data; + std::string new_value; + + /* header folding, add data to the header we are building */ + m_flag_header_folding = 1; + + /* RFC-2557 states header folding is SP / HTAB, but PHP and + * perhaps others will take any whitespace. So, we accept, + * but with a flag set. + */ + if ((m_buf[0] != '\t') && (m_buf[0] != ' ')) { + m_flag_invalid_header_folding = 1; + } + + if (m_mpp->m_last_header_name.empty()) { + /* we are not building a header at this moment */ + debug(1, "Multipart: Invalid part header (folding error)."); + return false; + } + + /* locate the beginning of data */ + data = m_buf; + while (isspace(*data)) { + /* Flag invalid header folding if an invalid RFC-2557 + * character is used anywhere in the folding prefix. + */ + if ((*data != '\t') && (*data != ' ')) { + m_flag_invalid_header_folding = 1; + } + data++; + } + + new_value = std::string(data); + chomp(&new_value); + + /* update the header value in the table */ + header_value = m_mpp->m_headers.at(m_mpp->m_last_header_name); + new_value = header_value + " " + new_value; + m_mpp->m_headers.at(m_mpp->m_last_header_name) = new_value; + + debug(9, "Multipart: Continued folder header \"" \ + + m_mpp->m_last_header_name + "\" with \"" \ + + std::string(data) + "\""); + + if (new_value.size() > MULTIPART_BUF_SIZE) { + debug(1, "Multipart: Part header too long."); + return false; + } + } else { + char *data; + std::string header_value; + std::string header_name; + /* new header */ + + data = m_buf; + while ((*data != ':') && (*data != '\0')) { + data++; + } + if (*data == '\0') { + debug(1, "Multipart: Invalid part header (colon missing): " \ + + std::string(m_buf)); + return false; + } + + /* extract header name */ + header_name = std::string(m_buf, data - m_buf); + if (data == m_buf) { + debug(1, "Multipart: Invalid part header " \ + "(header name missing)."); + return false; + } + + /* extract the value value */ + data++; + while ((*data == '\t') || (*data == ' ')) { + data++; + } + header_value = std::string(data); + chomp(&header_value); + + /* error if the name already exists */ + if (m_mpp->m_headers.count(header_name) > 0) { + debug(1, "Multipart: Duplicate part header: " \ + + header_name + "."); + return false; + } + + m_mpp->m_headers.insert({header_name, header_value}); + m_mpp->m_last_header_name.assign(header_name); + + debug(9, "Multipart: Added part header \"" + header_name \ + + "\" \"" + header_value + "\"."); + } } 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; +int Multipart::process_boundary(int last_part) { + /* if there was a part being built finish it */ + if (m_mpp != NULL) { + /* close the temp file */ + if ((m_mpp->m_type == MULTIPART_FILE) + && (!m_mpp->m_tmp_file_name.empty()) + && (m_mpp->m_tmp_file_fd != 0)) { + close(m_mpp->m_tmp_file_fd); + m_mpp->m_tmp_file_fd = -1; } - pos = data.find("\n", pos + 1); + + if (m_mpp->m_type != MULTIPART_FILE) { + /* now construct a single string out of the parts */ + for (std::string &i : m_mpp->m_value_parts) { + m_mpp->m_value.append(i); + } + } + + if (m_mpp->m_name.empty() == false) { + /* add the part to the list of parts */ + m_parts.push_back(m_mpp); + if (m_mpp->m_type == MULTIPART_FILE) { + debug(9, "Multipart: Added file part to the list: name \"" \ + + m_mpp->m_name + "\" " + "file name \"" + m_mpp->m_filename + "\" (offset " \ + + std::to_string(m_mpp->m_offset) + + ", length " + std::to_string(m_mpp->m_length) + ")"); + } else { + debug(9, "Multipart: Added part to the list: name \"" \ + + m_mpp->m_name + "\" " + "(offset " + std::to_string(m_mpp->m_offset) \ + + ", length " + std::to_string(m_mpp->m_length) + ")"); + } + } else { + m_flag_invalid_part = true; + debug(3, "Multipart: Skipping invalid part (part name missing): " + "(offset " + std::to_string(m_mpp->m_offset) + ", length " + + std::to_string(m_mpp->m_length) + ")"); + } + + m_mpp = NULL; } + + if (last_part == 0) { + /* start building a new part */ + m_mpp = new MultipartPart(); + + m_mpp_state = 0; + + m_reserve[0] = 0; + m_reserve[1] = 0; + m_reserve[2] = 0; + m_reserve[3] = 0; + } + + return 1; } -bool Multipart::process(std::string data) { - collection::Collections *col; - std::list blobs; - size_t start = data.find(m_boundary); - size_t endl = 1; - size_t lastValidBoundary = 0; - size_t firstValidBoundary = start; - double files_size = 0; - col = &m_transaction->m_collections; - if (start != 0) { -#ifndef NO_LOGS - debug(4, "Multipart: Boundary was not the first thing."); -#endif - this->containsDataBefore = true; +/** + * Finalize multipart processing. This method is invoked at the end, when it + * is clear that there is no more data to be processed. + */ +int Multipart::multipart_complete() { + m_transaction->m_collections.store("MULTIPART_UNMATCHED_BOUNDARY", + std::to_string(m_flag_unmatched_boundary)); + + m_transaction->m_collections.store("MULTIPART_DATA_BEFORE", + std::to_string(m_flag_data_before)); + if (m_flag_data_before) { + debug(4, "Multipart: Warning: seen data before first boundary."); } - while (start != std::string::npos) { - size_t end = data.find(m_boundary, start + m_boundary.length()); - if (end == std::string::npos) { - start = end; + + m_transaction->m_collections.store("MULTIPART_DATA_AFTER", + std::to_string(m_flag_data_after)); + if (m_flag_data_after) { + debug(4, "Multipart: Warning: seen data after last boundary."); + } + + m_transaction->m_collections.store("MULTIPART_BOUNDARY_QUOTED", + std::to_string(m_flag_boundary_quoted)); + if (m_flag_boundary_quoted) { + debug(4, "Multipart: Warning: boundary was quoted."); + } + + m_transaction->m_collections.store("MULTIPART_BOUNDARY_WHITESPACE", + std::to_string(m_flag_boundary_whitespace)); + if (m_flag_boundary_whitespace) { + debug(4, "Multipart: Warning: boundary whitespace in C-T header."); + } + + m_transaction->m_collections.store("MULTIPART_HEADER_FOLDING", + std::to_string(m_flag_header_folding)); + if (m_flag_header_folding) { + debug(4, "Multipart: Warning: header folding used."); + } + + m_transaction->m_collections.store("MULTIPART_CRLF_LINE", + std::to_string(m_flag_crlf_line)); + m_transaction->m_collections.store("MULTIPART_LF_LINE", + std::to_string(m_flag_lf_line)); + m_transaction->m_collections.store("MULTIPART_CRLF_LF_LINES", + std::to_string(m_flag_crlf_line && m_flag_lf_line)); + if (m_flag_crlf_line && m_flag_lf_line) { + debug(4, "Multipart: Warning: mixed line endings used (CRLF/LF)."); + } else if (m_flag_lf_line) { + debug(4, "Multipart: Warning: incorrect line endings used (LF)."); + } + + m_transaction->m_collections.store("MULTIPART_MISSING_SEMICOLON", + std::to_string(m_flag_missing_semicolon)); + if (m_flag_missing_semicolon) { + debug(4, "Multipart: Warning: missing semicolon in C-T header."); + } + + m_transaction->m_collections.store("MULTIPART_INVALID_QUOTING", + std::to_string(m_flag_invalid_quoting)); + if (m_flag_invalid_quoting) { + debug(4, "Multipart: Warning: invalid quoting used."); + } + + m_transaction->m_collections.store("MULTIPART_INVALID_PART", + std::to_string(m_flag_invalid_part)); + if (m_flag_invalid_part) { + debug(4, "Multipart: Warning: invalid part parsing."); + } + + m_transaction->m_collections.store("MULTIPART_INVALID_HEADER_FOLDING", + std::to_string(m_flag_invalid_header_folding)); + if (m_flag_invalid_header_folding) { + debug(4, "Multipart: Warning: invalid header folding used."); + } + + m_transaction->m_collections.store("MULTIPART_STRICT_ERROR", + std::to_string(m_flag_error || m_flag_boundary_quoted != 0 + || m_flag_boundary_whitespace != 0 || m_flag_data_before != 0 + || m_flag_data_after != 0 || m_flag_header_folding != 0 + || m_flag_lf_line != 0 || m_flag_missing_semicolon != 0 + || m_flag_invalid_quoting != 0 || m_flag_invalid_part != 0 + || m_flag_invalid_header_folding != 0 + || m_flag_file_limit_exceeded != 0)); + + + if ((m_seen_data != 0) && (m_is_complete == 0)) { + if (m_boundary_count > 0) { + /* Check if we have the final boundary (that we haven't + * processed yet) in the buffer. + */ + if (m_buf_contains_line) { + if (((unsigned int)(MULTIPART_BUF_SIZE - m_bufleft) + == (4 + m_boundary.size())) + && (*(m_buf) == '-') + && (*(m_buf + 1) == '-') + && (strncmp(m_buf + 2, m_boundary.c_str(), + m_boundary.size()) == 0) + && (*(m_buf + 2 + m_boundary.size()) == '-') + && (*(m_buf + 2 + m_boundary.size() + 1) == '-')) { + /* Looks like the final boundary - process it. */ + if (process_boundary(1 /* final */) < 0) { + m_flag_error = 1; + return -1; + } + + /* The payload is complete after all. */ + m_is_complete = 1; + } + } + + if (m_is_complete == 0) { + debug(1, "Multipart: Final boundary missing."); + return false; + } + } else { + debug(1, "Multipart: No boundaries found in payload."); + return false; + } + } + + int file_combined_size = 0; + for (MultipartPart *m : m_parts) { + // FIXME: duplicate code, see transaction. + // we need a helper function for this. + if (m->m_name.empty()) { continue; } - std::string block = std::string(data, start + m_boundary.length() + - + endl, end - (start + m_boundary.length() + endl) - endl); - checkForCrlfLf(block); - - // if (this->crlf) { - // block.erase(0, 1); - // } - - 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(""); - int i = 0; - for (std::string x : blobs) { - i++; -#ifndef NO_LOGS - debug(5, "Multipart: Inspecting blob: " + std::to_string(i)); -#endif - MultipartBlob m(x, this); - - if (m.name.empty() == false) { - name = m.name; + if (m->m_type == MULTIPART_FILE) { + std::string tmp_name; + std::string name; + if (!m->m_tmp_file_name.empty()) { + tmp_name.assign(m->m_tmp_file_name); + } + if (!m->m_filename.empty()) { + name.assign(m->m_filename); + } + m_transaction->m_collections.store("FILES:" + m->m_filename, + m->m_filename); + m_transaction->m_collections.store("FILES_NAMES:" + m->m_name, + m->m_name); + m_transaction->m_collections.store("FILES_SIZES:" + m->m_name, + std::to_string(m->m_tmp_file_size)); + m_transaction->m_collections.store("FILES_TMP_CONTENT:" \ + + m->m_name, m->m_value); + m_transaction->m_collections.store("FILES_TMPNAMES:" \ + + m->m_filename, tmp_name); + file_combined_size = file_combined_size + m->m_tmp_file_size; } else { - name = "no-name-" + std::to_string(i); + debug(4, "Adding request argument (BODY): name \"" + + m->m_name + "\", value \"" + m->m_value + "\""); + m_transaction->m_collections.store("ARGS:" + m->m_name, + m->m_value); + m_transaction->m_collections.store("ARGS_POST:" + m->m_name, + m->m_value); } - - if (m.filename.empty() == false) { - filename = m.filename; +#if 0 + if (m_transaction->m_namesArgs->empty()) { + m_transaction->m_namesArgs->assign(key); } else { - filename = "no-file-name-" + std::to_string(i); + m_transaction->m_namesArgs->assign(*m_namesArgs + " " + key); + } + if (m_transaction->m_namesArgsPost->empty()) { + m_transaction->m_namesArgsPost->assign(key); + } else { + m_transaction->m_namesArgsPost->assign( + *m_namesArgsPost + " " + key); } - col->storeOrUpdateFirst("FILES:" + name, filename); - col->storeOrUpdateFirst("FILES_NAMES:" + name, name); - col->storeOrUpdateFirst("FILES_SIZES:" + name, - std::to_string(m.content.size())); -#ifndef NO_LOGS - debug(5, "Multipart: Saving FILES_TMP_CONTENT:" + name + " variable."); + m_transaction->m_ARGScombinedSize = \ + m_transaction->->m_ARGScombinedSize + \ + m->m_name.length() + m->m_value.length(); + m_transaction->m_ARGScombinedSizeStr->assign( + std::to_string(m_transaction->->m_ARGScombinedSize)); #endif - col->storeOrUpdateFirst("FILES_TMP_CONTENT:" + name, m.content); - files_size = files_size + m.content.size(); - if (m.invalidQuote) { -#ifndef NO_LOGS - debug(4, "Multipart: Found invalid quoting."); -#endif - this->invalidQuote = true; + } + m_transaction->m_collections.store("FILES_COMBINED_SIZE", + std::to_string(file_combined_size)); + + return true; +} + + +int Multipart::count_boundary_params(const std::string& str_header_value) { + std::string lower = tolower(str_header_value); + const char *header_value = lower.c_str(); + char *duplicate = NULL; + char *s = NULL; + int count = 0; + + if (header_value == NULL) { + return -1; + } + + duplicate = strdup(header_value); + if (duplicate == NULL) { + return -1; + } + + s = duplicate; + while ((s = strstr(s, "boundary")) != NULL) { + s += 8; + + if (strchr(s, '=') != NULL) { + count++; } } - if (filename.empty() == false) { - col->storeOrUpdateFirst("MULTIPART_FILENAME", filename); + + free(duplicate); + return count; +} + + +bool Multipart::init() { + m_bufleft = MULTIPART_BUF_SIZE; + m_bufptr = m_buf; + m_buf_contains_line = true; + m_mpp = NULL; + const char *m_boundary_tmp = NULL; + + if (m_header.empty()) { + m_flag_error = true; + debug(4, "Multipart: Content-Type header not available."); + return false; } - if (name.empty() == false) { - col->storeOrUpdateFirst("MULTIPART_NAME", name); + + if (m_header.size() > 1024) { + m_flag_error = 1; + debug(4, "Multipart: Invalid boundary in C-T (length)."); + return false; + } + + if (strncasecmp(m_header.c_str(), "multipart/form-data", 19) != 0) { + m_flag_error = 1; + debug(4, "Multipart: Invalid MIME type."); + return false; + } + + /* Count how many times the word "boundary" appears in the C-T header. */ + if (count_boundary_params(m_header) > 1) { + m_flag_error = 1; + debug(4, "Multipart: Multiple boundary parameters in C-T."); + return false; + } + + m_boundary_tmp = strstr(m_header.c_str(), "boundary"); + if (m_boundary_tmp) { + m_boundary = std::string(m_boundary_tmp); + const char *p = NULL; + const char *b = NULL; + int seen_semicolon = 0; + int len = 0; + + /* Check for extra characters before the boundary. */ + for (p = m_header.c_str() + 19; + p < m_boundary_tmp; p++) { + if (!isspace(*p)) { + if ((seen_semicolon == 0) && (*p == ';')) { + seen_semicolon = 1; /* It is OK to have one semicolon. */ + } else { + m_flag_error = 1; + debug(4, "Multipart: Invalid boundary in C-T " \ + "(malformed)."); + return false; + } + } + } + + /* Have we seen the semicolon in the header? */ + if (seen_semicolon == 0) { + m_flag_missing_semicolon = 1; + } + + b = strchr(m_boundary_tmp + 8, '='); + if (b == NULL) { + m_flag_error = 1; + debug(4, "Multipart: Invalid boundary in C-T (malformed)."); + return false; + } + + /* Check parameter name ends well. */ + if (b != (m_boundary.c_str() + 8)) { + /* Check all characters between the end of the boundary + * and the = character. + */ + for (p = m_boundary_tmp + 8; p < b; p++) { + if (isspace(*p)) { + /* Flag for whitespace after parameter name. */ + m_flag_boundary_whitespace = 1; + } else { + m_flag_error = 1; + debug(4, "Multipart: Invalid boundary in C-T " \ + "(parameter name)."); + return false; + } + } + } + + b++; /* Go over the = character. */ + len = strlen(b); + + /* Flag for whitespace before parameter value. */ + if (isspace(*b)) { + m_flag_boundary_whitespace = 1; + } + + /* Is the boundary quoted? */ + if ((len >= 2) && (*b == '"') && (*(b + len - 1) == '"')) { + /* Quoted. */ + m_boundary.assign(std::string(b + 1, len - 2)); + if (m_boundary.empty()) { + return -1; + } + m_flag_boundary_quoted = 1; + } else { + /* Not quoted. */ + + /* Test for partial quoting. */ + if ((*b == '"') + || ((len >= 2) && (*(b + len - 1) == '"'))) { + m_flag_error = 1; + debug(4, "Multipart: Invalid boundary in C-T (quote)."); + return false; + } + + m_boundary.assign(b); + if (m_boundary.empty()) { + return false; + } + + m_flag_boundary_quoted = 0; + } + + /* Case-insensitive test for the string "boundary" in the boundary. */ + if (count_boundary_params(m_boundary.c_str()) != 0) { + m_flag_error = 1; + debug(4, "Multipart: Invalid boundary in C-T (content)."); + return false; + } + + /* Validate the characters used in the boundary. */ + if (boundary_characters_valid(m_boundary.c_str()) != 1) { + m_flag_error = 1; + debug(4, "Multipart: Invalid boundary in C-T (characters)."); + return false; + } + + debug(9, "Multipart: Boundary" + + (m_flag_boundary_quoted ? + std::string(" (quoted)") : std::string("")) + + std::string(": ") + m_boundary); + + if (m_boundary.size() == 0) { + m_flag_error = 1; + debug(4, "Multipart: Invalid boundary in C-T (empty)."); + return false; + } + } else { /* Could not find boundary in the C-T header. */ + m_flag_error = 1; + + /* Test for case-insensitive boundary. Allowed by the RFC but + * highly unusual. */ + if (count_boundary_params(m_header) > 0) { + debug(4, "Multipart: Invalid boundary in C-T (case sensitivity)."); + return false; + } + + debug(4, "Multipart: Boundary not found in C-T."); + return false; + } + + return 1; +} + + +/* + * Assuming that all data is on data. We are not processing chunks. + * + */ +bool Multipart::process(const std::string& data) { + const char *inptr = data.c_str(); + unsigned int inleft = data.size(); + + if (data.size() == 0) return true; + + m_seen_data = true; + + if (m_is_complete) { + m_flag_data_before = true; + + debug(4, "Multipart: Ignoring data after last boundary (received " \ + + std::to_string(data.size()) + " bytes)"); + + return true; + } + + if (m_bufleft == 0) { + m_flag_error = 1; + debug(4, "Multipart: Internal error in process_chunk: no space left " \ + "in the buffer"); + return false; + } + + /* here we loop through the available data, one byte at a time */ + while (inleft > 0) { + char c = *inptr; + int process_buffer = 0; + + if ((c == '\r') && (m_bufleft == 1)) { + /* we don't want to take \r as the last byte in the buffer */ + process_buffer = 1; + } else { + inptr++; + inleft = inleft - 1; + + *(m_bufptr) = c; + m_bufptr++; + m_bufleft--; + } + + /* until we either reach the end of the line + * or the end of our internal buffer + */ + if ((c == '\n') || (m_bufleft == 0) || (process_buffer)) { + int processed_as_boundary = 0; + + *(m_bufptr) = 0; + + /* Do we have something that looks like a boundary? */ + if (m_buf_contains_line && (strlen(m_buf) > 3) && (*(m_buf) == '-') + && (*(m_buf + 1) == '-')) { + /* Does it match our boundary? */ + if ((strlen(m_buf) >= m_boundary.size() + 2) + && (strncmp(m_buf + 2, m_boundary.c_str(), + m_boundary.size()) == 0)) { + char *boundary_end = m_buf + 2 + m_boundary.size(); + int is_final = 0; + + /* Is this the final boundary? */ + if ((*boundary_end == '-') + && (*(boundary_end + 1)== '-')) { + is_final = 1; + boundary_end += 2; + + if (m_is_complete != 0) { + m_flag_error = 1; + debug(4, "Multipart: Invalid boundary " \ + "(final duplicate)."); + return false; + } + } + + /* Allow for CRLF and LF line endings. */ + if (((*boundary_end == '\r') + && (*(boundary_end + 1) == '\n') + && (*(boundary_end + 2) == '\0')) + || ((*boundary_end == '\n') + && (*(boundary_end + 1) == '\0'))) { + if (*boundary_end == '\n') { + m_flag_lf_line = 1; + } else { + m_flag_crlf_line = 1; + } + + if (process_boundary((is_final ? 1 : 0)) < 0) { + m_flag_error = true; + return false; + } + + if (is_final) { + m_is_complete = 1; + } + + processed_as_boundary = 1; + m_boundary_count++; + } else { + /* error */ + m_flag_error = 1; + debug(4, "Multipart: Invalid boundary: " \ + + std::string(m_buf)); + return false; + } + } else { /* It looks like a boundary but */ + /* we couldn't match it. */ + char *p = NULL; + + /* Check if an attempt to use quotes around the + * boundary was made. */ + if ((m_flag_boundary_quoted) + && (strlen(m_buf) >= m_boundary.size() + 3) + && (*(m_buf + 2) == '"') + && (strncmp(m_buf + 3, m_boundary.c_str(), + m_boundary.size()) == 0)) { + m_flag_error = 1; + debug(4, "Multipart: Invalid boundary (quotes)."); + return false; + } + + /* Check the beginning of the boundary for whitespace. */ + p = m_buf + 2; + while (isspace(*p)) { + p++; + } + + if ((p != m_buf + 2) + && (strncmp(p, m_boundary.c_str(), + m_boundary.size()) == 0)) { + /* Found whitespace in front of a boundary. */ + m_flag_error = 1; + debug(4, "Multipart: Invalid boundary (whitespace)."); + return false; + } + + m_flag_unmatched_boundary = 1; + } + } else { /* We do not think the buffer contains a boundary. */ + /* Look into the buffer to see if there's anything + * there that resembles a boundary. + */ + if (m_buf_contains_line) { + int i, len = (MULTIPART_BUF_SIZE - m_bufleft); + char *p = m_buf; + + for (i = 0; i < len; i++) { + if ((p[i] == '-') && (i + 1 < len) + && (p[i + 1] == '-')) { + if (strncmp(p + i + 2, m_boundary.c_str(), + m_boundary.size()) == 0) { + m_flag_unmatched_boundary = 1; + break; + } + } + } + } + } + + /* Process as data if it was not a boundary. */ + if (processed_as_boundary == 0) { + if (m_mpp == NULL) { + m_flag_data_before = 1; + + debug(4, "Multipart: Ignoring data before first " \ + "boundary."); + } else { + if (m_mpp_state == 0) { + if ((m_bufleft == 0) || (process_buffer)) { + /* part header lines must be shorter than + * MULTIPART_BUF_SIZE bytes + */ + m_flag_error = 1; + debug(4, "Multipart: Part header line over " \ + + std::to_string(MULTIPART_BUF_SIZE) \ + + " bytes long"); + return false; + } + + if (process_part_header() < 0) { + m_flag_error = 1; + return false; + } + + } else { + if (process_part_data() < 0) { + m_flag_error = 1; + return false; + } + } + } + } + + /* Update the offset of the data we are about + * to process. This is to allow us to know the + * offsets of individual files and variables. + */ + m_buf_offset += (MULTIPART_BUF_SIZE - m_bufleft); + + /* reset the pointer to the beginning of the buffer + * and continue to accept input data + */ + m_bufptr = m_buf; + m_bufleft = MULTIPART_BUF_SIZE; + m_buf_contains_line = (c == 0x0a) ? 1 : 0; + } + + if ((m_is_complete) && (inleft != 0)) { + m_flag_data_after = 1; + debug(4, "Multipart: Ignoring data after last boundary (" \ + + std::to_string(inleft) + "bytes left)"); + + return true; + } } - col->storeOrUpdateFirst("FILES_COMBINED_SIZE", std::to_string(files_size)); return true; } diff --git a/src/request_body_processor/multipart.h b/src/request_body_processor/multipart.h index 94f2de47..34e9452a 100644 --- a/src/request_body_processor/multipart.h +++ b/src/request_body_processor/multipart.h @@ -15,7 +15,7 @@ #include #include - +#include #ifndef SRC_REQUEST_BODY_PROCESSOR_MULTIPART_H_ #define SRC_REQUEST_BODY_PROCESSOR_MULTIPART_H_ @@ -25,24 +25,153 @@ namespace modsecurity { namespace RequestBodyProcessor { +#define MULTIPART_BUF_SIZE 4096 +#define MULTIPART_FORMDATA 1 +#define MULTIPART_FILE 2 + + +struct MyHash { + size_t operator()(const std::string& Keyval) const { + size_t h = 0; + std::for_each(Keyval.begin(), Keyval.end(), [&](char c) { + h += tolower(c); + }); + return h; + } +}; + + +struct MyEqual { + bool operator()(const std::string& Left, const std::string& Right) const { + return Left.size() == Right.size() + && std::equal(Left.begin(), Left.end(), Right.begin(), + [](char a, char b) { + return tolower(a) == tolower(b); + }); + } +}; + + +class MultipartPart { + public: + MultipartPart() + : m_type(MULTIPART_FORMDATA), + m_tmp_file_fd(0), + m_tmp_file_size(0), + m_offset(0), + m_length(0) { } + + /* part type, can be MULTIPART_FORMDATA or MULTIPART_FILE */ + int m_type; + + /* the name */ + std::string m_name; + + /* variables only, variable value */ + std::string m_value; + std::list m_value_parts; + + /* files only, the content type (where available) */ + /* std::string m_content_type; */ + + /* files only, the name of the temporary file holding data */ + std::string m_tmp_file_name; + int m_tmp_file_fd; + unsigned int m_tmp_file_size; + + /* files only, filename as supplied by the browser */ + std::string m_filename; + + std::string m_last_header_name; + std::unordered_map m_headers; + + unsigned int m_offset; + unsigned int m_length; +}; + + class Multipart { public: Multipart(std::string header, Transaction *transaction); + ~Multipart(); + bool init(); - bool boundaryContainsOnlyValidCharacters(); - bool conuntBoundaryParameters(); - bool process(std::string data); - void checkForCrlfLf(const std::string &blob); + int boundary_characters_valid(const char *boundary); + int count_boundary_params(const std::string& str_header_value); + int is_token_char(unsigned char c); + int multipart_complete(); + + int parse_content_disposition(const char *c_d_value); + bool process(const std::string& data); + int process_boundary(int last_part); + int process_part_header(); + int process_part_data(); + + int tmp_file_name(std::string *filename); + + void validate_quotes(const char *data); + + size_t m_reqbody_no_files_length; + std::list m_parts; + + /* Number of parts that are files */ + int m_nfiles; + + /* mime boundary used to detect when + * parts end and begin + */ + std::string m_boundary; + int m_boundary_count; + + /* internal buffer and other variables + * used while parsing + */ + char m_buf[MULTIPART_BUF_SIZE + 2]; + int m_buf_contains_line; + char *m_bufptr; + int m_bufleft; + + unsigned int m_buf_offset; + + /* pointer that keeps track of a part while + * it is being built + */ + MultipartPart *m_mpp; + + + /* part parsing state; 0 means we are reading + * headers, 1 means we are collecting data + */ + int m_mpp_state; + + /* because of the way this parsing algorithm + * works we hold back the last two bytes of + * each data chunk so that we can discard it + * later if the next data chunk proves to be + * a boundary; the first byte is an indicator + * 0 - no content, 1 - two data bytes available + */ + char m_reserve[4]; + + int m_seen_data; + int m_is_complete; + + int m_flag_error; + int m_flag_data_before; + int m_flag_data_after; + int m_flag_header_folding; + int m_flag_boundary_quoted; + int m_flag_lf_line; + int m_flag_crlf_line; + int m_flag_unmatched_boundary; + int m_flag_boundary_whitespace; + int m_flag_missing_semicolon; + int m_flag_invalid_quoting; + int m_flag_invalid_part; + int m_flag_invalid_header_folding; + int m_flag_file_limit_exceeded; - bool crlf; - bool containsDataAfter; - bool containsDataBefore; - bool lf; - bool boundaryStartsWithWhiteSpace; - bool boundaryIsQuoted; - bool missingSemicolon; - bool invalidQuote; #ifndef NO_LOGS void debug(int a, std::string str) { m_transaction->debug(a, str); @@ -50,11 +179,11 @@ class Multipart { #endif private: - std::string m_boundary; std::string m_header; Transaction *m_transaction; }; + } // namespace RequestBodyProcessor } // namespace modsecurity diff --git a/src/request_body_processor/multipart_blob.cc b/src/request_body_processor/multipart_blob.cc deleted file mode 100644 index 7d3a2a2b..00000000 --- a/src/request_body_processor/multipart_blob.cc +++ /dev/null @@ -1,142 +0,0 @@ -/* - * 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_blob.h" - -#include -#include -#include - - -namespace modsecurity { -namespace RequestBodyProcessor { - -MultipartBlob::MultipartBlob(const std::string &blob, Multipart *parent) - : m_blob(blob), - invalidQuote(false), - m_parent(parent) { - processContent(); -} - - -bool MultipartBlob::processContent() { - size_t end = 0; - size_t offset = 0; - - end = m_blob.find("\n", offset); - if (end == std::string::npos) { -#ifndef NO_LOGS - debug(4, "Missing end of line"); -#endif - return false; - } - std::string firstLine = std::string(m_blob, offset, end); - offset = end + 1; - end = m_blob.find("\n", offset); - if (end == std::string::npos) { -#ifndef NO_LOGS - debug(4, "Missing end of line"); -#endif - return false; - } - std::string secondLine = std::string(m_blob, offset, end - offset); - - bool dispositionLine = processContentDispositionLine(firstLine); - if (dispositionLine == false) { - return false; - } - - bool contentTypeLine = processContentTypeLine(secondLine); - if (contentTypeLine == false) { - return false; - } - - offset = end + 1; - if (contentType.empty() == false) { - end = m_blob.find_first_of("\n", offset); - if (end == std::string::npos) { -#ifndef NO_LOGS - debug(4, "Missing end of line"); -#endif - return false; - } - offset = end + 1; - } - content = std::string(m_blob, offset, m_blob.length() - offset + 1); - - return true; -} - - -bool MultipartBlob::processContentTypeLine( - const std::string &contentTypeLine) { - size_t contentTypeKeyLength = 14; - - if (contentTypeLine.length() <= contentTypeKeyLength) { - return true; - } - - contentType = std::string(contentTypeLine, contentTypeKeyLength, - contentTypeLine.length() - contentTypeKeyLength); - return true; -} - - -bool MultipartBlob::processContentDispositionLine( - const std::string &dispositionLine) { - size_t offset; - - if (dispositionLine.size() < 30 || - dispositionLine.compare(21, 9, "form-data") != 0) { -#ifndef NO_LOGS - debug(4, "Multipart: Content-Disposition is unknown"); -#endif - return false; - } - - // Find name= - offset = dispositionLine.find("name="); - if (offset != std::string::npos) { - size_t invalidQuote = dispositionLine.find("\'", offset); - if (invalidQuote != std::string::npos) { - this->invalidQuote = true; - } - offset = offset + 5 /* name= */ + 1 /* " */; - size_t end = dispositionLine.find("\"", offset); - if (end != std::string::npos) { - name = std::string(dispositionLine, offset, end - offset); - } - } - - // Find filename= - offset = dispositionLine.find("filename="); - if (offset != std::string::npos) { - size_t invalidQuote = dispositionLine.find("\'", offset); - if (invalidQuote != std::string::npos) { - this->invalidQuote = true; - } - offset = offset + 9 /* filename= */ + 1 /* " */; - size_t end = dispositionLine.find("\"", offset); - if (end != std::string::npos) { - filename = std::string(dispositionLine, offset, end - offset); - } - } - - return true; -} - - -} // namespace RequestBodyProcessor -} // namespace modsecurity diff --git a/src/request_body_processor/multipart_blob.h b/src/request_body_processor/multipart_blob.h deleted file mode 100644 index 82ccd300..00000000 --- a/src/request_body_processor/multipart_blob.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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 -#include - -#include "request_body_processor/multipart.h" - -#ifndef SRC_REQUEST_BODY_PROCESSOR_MULTIPART_BLOB_H_ -#define SRC_REQUEST_BODY_PROCESSOR_MULTIPART_BLOB_H_ - -namespace modsecurity { -namespace RequestBodyProcessor { - -class MultipartBlob { - public: - explicit MultipartBlob(const std::string &blob, Multipart *parent); - - bool processContent(); - bool processContentDispositionLine(const std::string &dispositionLine); - bool processContentTypeLine(const std::string &contentTypeLine); - - bool invalidQuote; - std::string name; - std::string filename; - std::string contentType; - std::string content; - -#ifndef NO_LOGS - void debug(int a, std::string str) { - m_parent->debug(a, str); - } -#endif - private: - const std::string m_blob; - Multipart *m_parent; -}; - -} // namespace RequestBodyProcessor -} // namespace modsecurity - -#endif // SRC_REQUEST_BODY_PROCESSOR_MULTIPART_BLOB_H_ diff --git a/src/rules.cc b/src/rules.cc index ff10054d..85a075e3 100644 --- a/src/rules.cc +++ b/src/rules.cc @@ -222,6 +222,12 @@ int Rules::merge(Driver *from) { this->requestBodyLimitAction = from->requestBodyLimitAction; this->responseBodyLimitAction = from->responseBodyLimitAction; + this->uploadKeepFiles = from->uploadKeepFiles; + this->uploadFileLimit = from->uploadFileLimit; + this->uploadFileMode = from->uploadFileMode; + this->uploadDirectory = from->uploadDirectory; + this->tmpSaveUploadedFiles = from->tmpSaveUploadedFiles; + for (std::set::iterator it = from->m_responseBodyTypeToBeInspected.begin(); it != from->m_responseBodyTypeToBeInspected.end(); ++it) { @@ -278,6 +284,12 @@ int Rules::merge(Rules *from) { this->requestBodyLimitAction = from->requestBodyLimitAction; this->responseBodyLimitAction = from->responseBodyLimitAction; + this->uploadKeepFiles = from->uploadKeepFiles; + this->uploadFileLimit = from->uploadFileLimit; + this->uploadFileMode = from->uploadFileMode; + this->uploadDirectory = from->uploadDirectory; + this->tmpSaveUploadedFiles = from->tmpSaveUploadedFiles; + if (from->m_debugLog && this->m_debugLog && from->m_debugLog->isLogFileSet()) { this->m_debugLog->setDebugLogFile(from->m_debugLog->getDebugLogFile()); diff --git a/src/transaction.cc b/src/transaction.cc index 5dcbc999..866fab5e 100644 --- a/src/transaction.cc +++ b/src/transaction.cc @@ -606,72 +606,8 @@ int Transaction::processRequestBody() { if (m.init() == true) { m.process(m_requestBody.str()); - if (m.crlf && m.lf) { - m_collections.store("MULTIPART_CRLF_LF_LINES", "1"); - } else { - m_collections.store("MULTIPART_CRLF_LF_LINES", "0"); - } - if (m.boundaryStartsWithWhiteSpace) { -#ifndef NO_LOGS - debug(9, "Multipart: Boundary starts with white space, " \ - "setting MULTIPART_STRICT_ERROR to 1"); -#endif - m_collections.storeOrUpdateFirst( - "MULTIPART_STRICT_ERROR", "1"); - } - if (m.boundaryIsQuoted) { -#ifndef NO_LOGS - - debug(9, "Multipart: Boundary is quoted, " \ - "setting MULTIPART_STRICT_ERROR to 1"); -#endif - m_collections.storeOrUpdateFirst( - "MULTIPART_STRICT_ERROR", "1"); - } - if (m.containsDataAfter) { -#ifndef NO_LOGS - debug(9, "Multipart: There is data after the boundary, " \ - "setting MULTIPART_STRICT_ERROR to 1"); -#endif - m_collections.storeOrUpdateFirst( - "MULTIPART_STRICT_ERROR", "1"); - m_collections.store("MULTIPART_UNMATCHED_BOUNDARY", "1"); - } else { - m_collections.store("MULTIPART_UNMATCHED_BOUNDARY", "0"); - } - if (m.containsDataBefore) { -#ifndef NO_LOGS - debug(9, "Multipart: There is data before the boundary, " \ - "setting MULTIPART_STRICT_ERROR to 1"); -#endif - m_collections.storeOrUpdateFirst( - "MULTIPART_STRICT_ERROR", "1"); - } - if (m.lf) { -#ifndef NO_LOGS - debug(9, "Multipart: Lines are LF-terminated, " \ - "setting MULTIPART_STRICT_ERROR to 1"); -#endif - m_collections.storeOrUpdateFirst( - "MULTIPART_STRICT_ERROR", "1"); - } - if (m.missingSemicolon) { -#ifndef NO_LOGS - debug(9, "Multipart: Boundary missing semicolon, " \ - "setting MULTIPART_STRICT_ERROR to 1"); -#endif - m_collections.storeOrUpdateFirst( - "MULTIPART_STRICT_ERROR", "1"); - } - if (m.invalidQuote) { -#ifndef NO_LOGS - debug(9, "Multipart: Invalid quote, " \ - "setting MULTIPART_STRICT_ERROR to 1"); -#endif - m_collections.storeOrUpdateFirst( - "MULTIPART_STRICT_ERROR", "1"); - } } + m.multipart_complete(); } } diff --git a/src/utils.cc b/src/utils.cc index 78ee8b10..e70d90d4 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -117,7 +117,7 @@ std::string ascTime(time_t *t) { void chomp(std::string *str) { - std::string::size_type pos = str->find_last_not_of("\n\t"); + std::string::size_type pos = str->find_last_not_of("\n\r"); if (pos != std::string::npos) { str->erase(pos+1, str->length()-pos-1); } diff --git a/test/test-cases/regression/request-body-parser-multipart-crlf.json b/test/test-cases/regression/request-body-parser-multipart-crlf.json new file mode 100644 index 00000000..1ae54642 --- /dev/null +++ b/test/test-cases/regression/request-body-parser-multipart-crlf.json @@ -0,0 +1,67 @@ +[ + + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (final CRLF)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data; boundary=---------------------------69343412719991675451336310646", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "-----------------------------69343412719991675451336310646", + "Content-Disposition: form-data; name=\"a\"\r", + "\r", + "1\r", + "1.1\r", + "1.2\r", + "1.3\r", + "-----------------------------69343412719991675451336310646", + "Content-Disposition: form-data; name=\"b\"\r", + "\r", + "2\r", + "2.1\r", + "2.2\r", + "2.3\r", + "-----------------------------69343412719991675451336310646--" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "debug_log":"Target value: \"Adding request argument (BODY): name \"b\", value \"22\.12\.22\.3\"" + }, + "rules":[ + "SecRuleEngine On", + "SecRequestBodyAccess On", + "SecRule MULTIPART_STRICT_ERROR \"@eq 1\" \"phase:2,deny,id:500055\"", + "SecRule MULTIPART_UNMATCHED_BOUNDARY \"@eq 1\" \"phase:2,deny,id:500056\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"phase:2,deny,id:500057\"", + "SecRule ARGS_POST \"@eq 1231\" \"phase:2,deny,id:500067\"" + ] + } +] + diff --git a/test/test-cases/regression/request-body-parser-multipart.json b/test/test-cases/regression/request-body-parser-multipart.json new file mode 100644 index 00000000..729786bf --- /dev/null +++ b/test/test-cases/regression/request-body-parser-multipart.json @@ -0,0 +1,2453 @@ +[ + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (normal)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data; boundary=0000", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "--0000\r", + "Content-Disposition: form-data; name=\"name\"\r", + "\r", + "Brian Rectanus\r", + "--0000\r", + "Content-Disposition: form-data; name=\"email\"\r", + "\r", + "brian.rectanus@breach.com\r", + "--0000\r", + "Content-Disposition: form-data; name=\"image\"; filename=\"image.jpg\"\r", + "Content-Type: image/jpeg\r", + "\r", + "BINARYDATA\r", + "--0000--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "debug_log":"Added file part to the list: name \"image\" file name \"image.jpg\" \\(offset 258, length 10\\)" + }, + "rules":[ + "SecRuleEngine On", + "SecRequestBodyAccess On", + "SecRule MULTIPART_STRICT_ERROR \"@eq 1\" \"phase:2,deny,id:500055\"", + "SecRule MULTIPART_UNMATCHED_BOUNDARY \"@eq 1\" \"phase:2,deny,id:500056\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"phase:2,deny,id:500057\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (final CRLF)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data; boundary=---------------------------69343412719991675451336310646", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "-----------------------------69343412719991675451336310646\r", + "Content-Disposition: form-data; name=\"a\"\r", + "\r", + "1\r", + "-----------------------------69343412719991675451336310646\r", + "Content-Disposition: form-data; name=\"b\"\r", + "\r", + "2\r", + "-----------------------------69343412719991675451336310646--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "debug_log":"Adding request argument \\(BODY\\): name \"a\", value \"1\"" + }, + "rules":[ + "SecRuleEngine On", + "SecRequestBodyAccess On", + "SecRule MULTIPART_STRICT_ERROR \"@eq 1\" \"phase:2,deny,id:500055\"", + "SecRule MULTIPART_UNMATCHED_BOUNDARY \"@eq 1\" \"phase:2,deny,id:500056\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"phase:2,deny,id:500057\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (no final CRLF) - FIXME: test suit cannot work without the ending \\n", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data; boundary=---------------------------69343412719991675451336310646", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "-----------------------------69343412719991675451336310646\r", + "Content-Disposition: form-data; name=\"a\"\r", + "\r", + "1\r", + "-----------------------------69343412719991675451336310646\r", + "Content-Disposition: form-data; name=\"b\"\r", + "\r", + "2\r", + "-----------------------------69343412719991675451336310646--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "debug_log":"Adding request argument \\(BODY\\): name \"a\", value \"1\"" + }, + "rules":[ + "SecRuleEngine On", + "SecRequestBodyAccess On", + "SecRule MULTIPART_STRICT_ERROR \"@eq 1\" \"phase:2,deny,id:500055\"", + "SecRule MULTIPART_UNMATCHED_BOUNDARY \"@eq 1\" \"phase:2,deny,id:500056\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"phase:2,deny,id:500057\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (boundary contains \"boundary\")", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data; boundary=------------------------------------------------boundary", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "--------------------------------------------------boundary\r", + "Content-Disposition: form-data; name=\"a\"\r", + "\r", + "1\r", + "--------------------------------------------------boundary\r", + "Content-Disposition: form-data; name=\"b\"\r", + "\r", + "2\r", + "--------------------------------------------------boundary--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "debug_log":"Adding request argument \\(BODY\\): name \"a\", value \"1\"" + }, + "rules":[ + "SecRuleEngine On", + "SecRequestBodyAccess On", + "SecRule MULTIPART_STRICT_ERROR \"@eq 1\" \"phase:2,deny,id:500055\"", + "SecRule MULTIPART_UNMATCHED_BOUNDARY \"@eq 1\" \"phase:2,deny,id:500056\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"phase:2,deny,id:500057\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (boundary contains \"bOuNdArY\")", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data; boundary=--------0xKhTmLbOuNdArY", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "----------0xKhTmLbOuNdArY\r", + "Content-Disposition: form-data; name=\"a\"\r", + "\r", + "1\r", + "----------0xKhTmLbOuNdArY\r", + "Content-Disposition: form-data; name=\"b\"\r", + "\r", + "2\r", + "----------0xKhTmLbOuNdArY--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "debug_log":"Adding request argument \\(BODY\\): name \"a\", value \"1\"" + }, + "rules":[ + "SecRuleEngine On", + "SecRequestBodyAccess On", + "SecRule MULTIPART_STRICT_ERROR \"@eq 1\" \"phase:2,deny,id:500055\"", + "SecRule MULTIPART_UNMATCHED_BOUNDARY \"@eq 1\" \"phase:2,deny,id:500056\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"phase:2,deny,id:500057\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (data contains \"--\")", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data; boundary=---------------------------69343412719991675451336310646", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "-----------------------------69343412719991675451336310646\r", + "Content-Disposition: form-data; name=\"a\"\r", + "\r", + "--test\r", + "-----------------------------69343412719991675451336310646\r", + "Content-Disposition: form-data; name=\"b\"\r", + "\r", + "--\r", + "-----------------------------69343412719991675451336310646--\r" + + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "debug_log":"Adding request argument \\(BODY\\): name \"a\", value \"--test\"" + }, + "rules":[ + "SecRuleEngine On", + "SecRequestBodyAccess On", + "SecRule MULTIPART_STRICT_ERROR \"@eq 1\" \"phase:2,deny,id:500055\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"phase:2,deny,id:500057\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser error (no final boundary)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data; boundary=---------------------------69343412719991675451336310646", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "-----------------------------69343412719991675451336310646\r", + "Content-Disposition: form-data; name=\"a\"\r", + "\r", + "1\r", + "-----------------------------69343412719991675451336310646\r", + "Content-Disposition: form-data; name=\"b\"\r", + "\r", + "2\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "debug_log":"Final boundary missing" + }, + "rules":[ + "SecRuleEngine On", + "SecRequestBodyAccess On", + "SecRule MULTIPART_NAME \"@eq 1234\" \"phase:2,deny,id:500067\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser error (no disposition)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data; boundary=---------------------------69343412719991675451336310646", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "-----------------------------69343412719991675451336310646\r", + "\r", + "1\r", + "-----------------------------69343412719991675451336310646\r", + "\r", + "2\r", + "-----------------------------69343412719991675451336310646--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "debug_log":"Part missing Content-Disposition header" + }, + "rules":[ + "SecRuleEngine On", + "SecRequestBodyAccess On", + "SecRule MULTIPART_NAME \"@eq 1234\" \"phase:2,deny,id:500067\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser error (bad disposition)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data; boundary=---------------------------69343412719991675451336310646", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "-----------------------------69343412719991675451336310646\r", + "Content-Disposition: form-data name=\"a\"\r", + "\r", + "1\r", + "-----------------------------69343412719991675451336310646\r", + "Content-Disposition: form-data name=\"b\"\r", + "\r", + "2\r", + "-----------------------------69343412719991675451336310646--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "debug_log":"Invalid Content-Disposition header" + }, + "rules":[ + "SecRuleEngine On", + "SecRequestBodyAccess On", + "SecRule MULTIPART_NAME \"@eq 1234\" \"phase:2,deny,id:500067\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser error (no disposition name)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data; boundary=---------------------------69343412719991675451336310646", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "-----------------------------69343412719991675451336310646\r", + "Content-Disposition: form-data;\r", + "\r", + "1\r", + "-----------------------------69343412719991675451336310646\r", + "Content-Disposition: form-data;\r", + "\r", + "2\r", + "-----------------------------69343412719991675451336310646--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "debug_log":"Content-Disposition header missing name field" + }, + "rules":[ + "SecRuleEngine On", + "SecRequestBodyAccess On", + "SecRule MULTIPART_NAME \"@eq 1234\" \"phase:2,deny,id:500067\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser error (no disposition name)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data; boundary=---------------------------69343412719991675451336310646", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "-----------------------------69343412719991675451336310646\r", + "Content-Disposition: form-data; name=\"a\"\r", + "\r", + "1\r", + "-----------------------------69343412719991675451336310646\r", + ":\r", + "-----------------------------69343412719991675451336310646\r", + "Content-Disposition: form-data; name=\"b\"\r", + "\r", + "2\r", + "-----------------------------69343412719991675451336310646--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "debug_log":"nvalid part header \\(header name missing\\)" + }, + "rules":[ + "SecRuleEngine On", + "SecRequestBodyAccess On", + "SecRule MULTIPART_NAME \"@eq 1234\" \"phase:2,deny,id:500067\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (part header folding - space)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data; boundary=---------------------------69343412719991675451336310646", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "-----------------------------69343412719991675451336310646\r", + "Content-Disposition: form-data;\r", + " name=\"a\"\r", + "\r", + "1\r", + "-----------------------------69343412719991675451336310646\r", + "Content-Disposition: form-data;\r", + " name=\"b\"\r", + "\r", + "2\r", + "-----------------------------69343412719991675451336310646--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "debug_log":"name: a.*variable: 1.*" + }, + "rules":[ + "SecRuleEngine On", + "SecRule MULTIPART_STRICT_ERROR \"!@eq 1\" \"phase:2,deny,status:403,id:500074\"", + "SecRule MULTIPART_HEADER_FOLDING \"!@eq 1\" \"phase:2,deny,status:403,id:500075\"", + "SecRule MULTIPART_INVALID_HEADER_FOLDING \"!@eq 0\" \"phase:2,deny,status:403,id:500076\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"phase:2,deny,status:403,id:500077\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (part header folding - tab)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data; boundary=---------------------------69343412719991675451336310646", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "-----------------------------69343412719991675451336310646\r", + "Content-Disposition: form-data;\r", + " name=\"a\"\r", + "\r", + "1\r", + "-----------------------------69343412719991675451336310646\r", + "Content-Disposition: form-data;\r", + " name=\"b\"\r", + "\r", + "2\r", + "-----------------------------69343412719991675451336310646--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "debug_log":"name: a.*variable: 1.*" + }, + "rules":[ + "SecRuleEngine On", + "SecRule MULTIPART_STRICT_ERROR \"!@eq 1\" \"phase:2,deny,status:403,id:500074\"", + "SecRule MULTIPART_HEADER_FOLDING \"!@eq 1\" \"phase:2,deny,status:403,id:500075\"", + "SecRule MULTIPART_INVALID_HEADER_FOLDING \"!@eq 0\" \"phase:2,deny,status:403,id:500076\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"phase:2,deny,status:403,id:500077\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (part header folding - mixed)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data; boundary=---------------------------69343412719991675451336310646", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "-----------------------------69343412719991675451336310646\r", + "Content-Disposition: form-data; name=\"a\"\r", + "\r", + "1\r", + "-----------------------------69343412719991675451336310646\r", + "Content-Disposition: form-data;\r", + " name=\"b\"\r", + "\r", + "2\r", + "-----------------------------69343412719991675451336310646--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "debug_log":"name: a.*variable: 1.*" + }, + "rules":[ + "SecRuleEngine On", + "SecRule MULTIPART_STRICT_ERROR \"!@eq 1\" \"phase:2,deny,status:403,id:500074\"", + "SecRule MULTIPART_HEADER_FOLDING \"!@eq 1\" \"phase:2,deny,status:403,id:500075\"", + "SecRule MULTIPART_INVALID_HEADER_FOLDING \"!@eq 0\" \"phase:2,deny,status:403,id:500076\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"phase:2,deny,status:403,id:500077\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (part header folding - invalid)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data; boundary=---------------------------69343412719991675451336310646", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "-----------------------------69343412719991675451336310646\r", + "Content-Disposition: form-data; name=\"a\"\r", + "\r", + "1\r", + "-----------------------------69343412719991675451336310646\r", + "Content-Disposition: form-data;\r", + "\fname=\"b\"\r", + "\r", + "2\r", + "-----------------------------69343412719991675451336310646--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "debug_log":"name: a.*variable: 1.*" + }, + "rules":[ + "SecRuleEngine On", + "SecRule MULTIPART_STRICT_ERROR \"!@eq 1\" \"phase:2,deny,status:403,id:500074\"", + "SecRule MULTIPART_HEADER_FOLDING \"!@eq 1\" \"phase:2,deny,status:403,id:500075\"", + "SecRule MULTIPART_INVALID_HEADER_FOLDING \"!@eq 1\" \"phase:2,deny,status:403,id:500076\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"phase:2,deny,status:403,id:500077\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (part header folding - mixed invalid)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data; boundary=---------------------------69343412719991675451336310646", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "-----------------------------69343412719991675451336310646\r", + "Content-Disposition: form-data; name=\"a\"\r", + "\r", + "1\r", + "-----------------------------69343412719991675451336310646\r", + "Content-Disposition: form-data;\r", + "\f\tname=\"b\"\r", + "\r", + "2\r", + "-----------------------------69343412719991675451336310646--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "debug_log":"name: a.*variable: 1.*" + }, + "rules":[ + "SecRuleEngine On", + "SecRule MULTIPART_STRICT_ERROR \"!@eq 1\" \"phase:2,deny,status:403,id:500074\"", + "SecRule MULTIPART_HEADER_FOLDING \"!@eq 1\" \"phase:2,deny,status:403,id:500075\"", + "SecRule MULTIPART_INVALID_HEADER_FOLDING \"!@eq 1\" \"phase:2,deny,status:403,id:500076\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"phase:2,deny,status:403,id:500077\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (data after final boundary)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data; boundary=---------------------------69343412719991675451336310646", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "-----------------------------69343412719991675451336310646\r", + "Content-Disposition: form-data; name=\"a\"\r", + "\r", + "1\r", + "-----------------------------69343412719991675451336310646--\r", + "-----------------------------69343412719991675451336310646\r", + "Content-Disposition: form-data; name=\"b\"\r", + "\r", + "2\r", + "-----------------------------69343412719991675451336310646--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "debug_log":"name: a.*variable: 1.*", + "http_code": 403 + }, + "rules":[ + "SecRuleEngine On", + "SecRule MULTIPART_DATA_AFTER \"@eq 1\" \"phase:2,deny,status:403,id:500074\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (C-D uses single quotes)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data; boundary=---------------------------69343412719991675451336310646", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "-----------------------------69343412719991675451336310646\r", + "Content-Disposition: form-data; name=\"a\"\r", + "\r", + "1\r", + "-----------------------------69343412719991675451336310646\r", + "Content-Disposition: form-data; name=';filename=\"dummy';name=b;\"\r", + "\r", + "2\r", + "-----------------------------69343412719991675451336310646--\r" + + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "debug_log":"Duplicate Content-Disposition name", + "http_code": 200 + }, + "rules":[ + "SecRuleEngine On", + "SecRule MULTIPART_STRICT_ERROR \"!@eq 1\" \"phase:2,deny,id:500095\"", + "SecRule MULTIPART_INVALID_QUOTING \"!@eq 1\" \"phase:2,deny,id:500096\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"chain,phase:2,deny,id:500097\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (invalid C-T boundary separator - comma)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data, boundary=0000", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "--0000\r", + "Content-Disposition: form-data; name=\"name\"\r", + "\r", + "Brian Rectanus\r", + "--0000\r", + "Content-Disposition: form-data; name=\"email\"\r", + "\r", + "brian.rectanus@breach.com\r", + "--0000\r", + "Content-Disposition: form-data; name=\"image\"; filename=\"image.jpg\"\r", + "Content-Type: image/jpeg\r", + "\r", + "BINARYDATA\r", + "--0000--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "debug_log":"Invalid boundary in C-T \\(malformed\\)", + "http_code": 403 + }, + "rules":[ + "SecRuleEngine On", + "SecRule MULTIPART_STRICT_ERROR \"!@eq 1\" \"phase:2,deny,id:500095\"", + "SecRule MULTIPART_UNMATCHED_BOUNDARY \"!@eq 1\" \"phase:2,deny,id:500096\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"chain,phase:2,deny,id:500097\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (invalid C-T boundary separator - space)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data boundary=0000", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "--0000\r", + "Content-Disposition: form-data; name=\"name\"\r", + "\r", + "Brian Rectanus\r", + "--0000\r", + "Content-Disposition: form-data; name=\"email\"\r", + "\r", + "brian.rectanus@breach.com\r", + "--0000\r", + "Content-Disposition: form-data; name=\"image\"; filename=\"image.jpg\"\r", + "Content-Type: image/jpeg\r", + "\r", + "BINARYDATA\r", + "--0000--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "http_code": 403 + }, + "rules":[ + "SecRuleEngine On", + "SecRule MULTIPART_STRICT_ERROR \"!@eq 1\" \"phase:2,deny,id:500095\"", + "SecRule MULTIPART_UNMATCHED_BOUNDARY \"!@eq 1\" \"phase:2,deny,id:500096\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"chain,phase:2,deny,id:500097\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (invalid C-T boundary parameter name - case)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data; bOundAry=0000", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "--0000\r", + "Content-Disposition: form-data; name=\"name\"\r", + "\r", + "Brian Rectanus\r", + "--0000\r", + "Content-Disposition: form-data; name=\"email\"\r", + "\r", + "brian.rectanus@breach.com\r", + "--0000\r", + "Content-Disposition: form-data; name=\"image\"; filename=\"image.jpg\"\r", + "Content-Type: image/jpeg\r", + "\r", + "BINARYDATA\r", + "--0000--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "http_code": 403, + "debug_log": "Invalid boundary in C-T \\(case sensitivity\\)" + }, + "rules":[ + "SecRuleEngine On", + "SecRule MULTIPART_STRICT_ERROR \"!@eq 1\" \"phase:2,deny,id:500095\"", + "SecRule MULTIPART_UNMATCHED_BOUNDARY \"!@eq 1\" \"phase:2,deny,id:500096\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"chain,phase:2,deny,id:500097\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (invalid C-T boundary parameter name - trailing chars)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data; boundary123=0000", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "--0000\r", + "Content-Disposition: form-data; name=\"name\"\r", + "\r", + "Brian Rectanus\r", + "--0000\r", + "Content-Disposition: form-data; name=\"email\"\r", + "\r", + "brian.rectanus@breach.com\r", + "--0000\r", + "Content-Disposition: form-data; name=\"image\"; filename=\"image.jpg\"\r", + "Content-Type: image/jpeg\r", + "\r", + "BINARYDATA\r", + "--0000--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "http_code": 403, + "debug_log": "Invalid boundary in C-T \\(parameter name\\)" + }, + "rules":[ + "SecRuleEngine On", + "SecRule MULTIPART_STRICT_ERROR \"!@eq 1\" \"phase:2,deny,id:500095\"", + "SecRule MULTIPART_UNMATCHED_BOUNDARY \"!@eq 1\" \"phase:2,deny,id:500096\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"chain,phase:2,deny,id:500097\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (multiple C-T boundaries - first quoted)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data; boundary=\"0000\"; boundary=1111", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "--0000\r", + "Content-Disposition: form-data; name=\"name\"\r", + "\r", + "Brian Rectanus\r", + "--0000\r", + "Content-Disposition: form-data; name=\"email\"\r", + "\r", + "brian.rectanus@breach.com\r", + "--0000\r", + "Content-Disposition: form-data; name=\"image\"; filename=\"image.jpg\"\r", + "Content-Type: image/jpeg\r", + "\r", + "BINARYDATA\r", + "--0000--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "http_code": 403, + "debug_log": "Multiple boundary parameters in C-T" + }, + "rules":[ + "SecRuleEngine On", + "SecRule MULTIPART_STRICT_ERROR \"!@eq 1\" \"phase:2,deny,id:500095\"", + "SecRule MULTIPART_UNMATCHED_BOUNDARY \"!@eq 1\" \"phase:2,deny,id:500096\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"chain,phase:2,deny,id:500097\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (multiple C-T boundaries - comma separated)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data; boundary=0000, boundary=1111", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "--0000\r", + "Content-Disposition: form-data; name=\"name\"\r", + "\r", + "Brian Rectanus\r", + "--0000\r", + "Content-Disposition: form-data; name=\"email\"\r", + "\r", + "brian.rectanus@breach.com\r", + "--0000\r", + "Content-Disposition: form-data; name=\"image\"; filename=\"image.jpg\"\r", + "Content-Type: image/jpeg\r", + "\r", + "BINARYDATA\r", + "--0000--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "http_code": 403, + "debug_log": "Multiple boundary parameters in C-T" + }, + "rules":[ + "SecRuleEngine On", + "SecRule MULTIPART_STRICT_ERROR \"!@eq 1\" \"phase:2,deny,id:500095\"", + "SecRule MULTIPART_UNMATCHED_BOUNDARY \"!@eq 1\" \"phase:2,deny,id:500096\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"chain,phase:2,deny,id:500097\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (boundary whitespace in C-T - after name)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data; boundary =0000", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "--0000\r", + "Content-Disposition: form-data; name=\"name\"\r", + "\r", + "Brian Rectanus\r", + "--0000\r", + "Content-Disposition: form-data; name=\"email\"\r", + "\r", + "brian.rectanus@breach.com\r", + "--0000\r", + "Content-Disposition: form-data; name=\"image\"; filename=\"image.jpg\"\r", + "Content-Type: image/jpeg\r", + "\r", + "BINARYDATA\r", + "--0000--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "http_code": 403 + }, + "rules":[ + "SecRuleEngine On", + "SecRule MULTIPART_STRICT_ERROR \"!@eq 1\" \"phase:2,deny,id:500095\"", + "SecRule MULTIPART_UNMATCHED_BOUNDARY \"!@eq 1\" \"phase:2,deny,id:500096\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"chain,phase:2,deny,id:500097\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (boundary whitespace in C-T - before value)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data; boundary= 0000", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "--0000\r", + "Content-Disposition: form-data; name=\"name\"\r", + "\r", + "Brian Rectanus\r", + "--0000\r", + "Content-Disposition: form-data; name=\"email\"\r", + "\r", + "brian.rectanus@breach.com\r", + "--0000\r", + "Content-Disposition: form-data; name=\"image\"; filename=\"image.jpg\"\r", + "Content-Type: image/jpeg\r", + "\r", + "BINARYDATA\r", + "--0000--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "http_code": 403, + "debug_log": "boundary whitespace in C-T header" + }, + "rules":[ + "SecRuleEngine On", + "SecRule MULTIPART_STRICT_ERROR \"@eq 1\" \"phase:2,deny,id:500095\"", + "SecRule MULTIPART_UNMATCHED_BOUNDARY \"@eq 1\" \"phase:2,deny,id:500096\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"chain,phase:2,deny,id:500097\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (boundary whitespace in C-T - after value) - apache removes the whitespace, not the case for us... TODO", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data; boundary=0000 ", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "--0000\r", + "Content-Disposition: form-data; name=\"name\"\r", + "\r", + "Brian Rectanus\r", + "--0000\r", + "Content-Disposition: form-data; name=\"email\"\r", + "\r", + "brian.rectanus@breach.com\r", + "--0000\r", + "Content-Disposition: form-data; name=\"image\"; filename=\"image.jpg\"\r", + "Content-Type: image/jpeg\r", + "\r", + "BINARYDATA\r", + "--0000--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "http_code": 403 + }, + "rules":[ + "SecRuleEngine On", + "SecRule MULTIPART_STRICT_ERROR \"@eq 1\" \"phase:2,deny,id:500095\"", + "SecRule MULTIPART_UNMATCHED_BOUNDARY \"@eq 1\" \"phase:2,deny,id:500096\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"chain,phase:2,deny,id:500097\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (boundary special char - trailing whitespace+token)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data;boundary=0000 1111", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "--0000\r", + "Content-Disposition: form-data; name=\"name\"\r", + "\r", + "Brian Rectanus\r", + "--0000\r", + "Content-Disposition: form-data; name=\"email\"\r", + "\r", + "brian.rectanus@breach.com\r", + "--0000\r", + "Content-Disposition: form-data; name=\"image\"; filename=\"image.jpg\"\r", + "Content-Type: image/jpeg\r", + "\r", + "BINARYDATA\r", + "--0000--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "http_code": 403, + "debug_log": "No boundaries found in payload" + }, + "rules":[ + "SecRuleEngine On", + "SecRule MULTIPART_STRICT_ERROR \"@eq 1\" \"phase:2,deny,id:500095\"", + "SecRule MULTIPART_UNMATCHED_BOUNDARY \"@eq 1\" \"phase:2,deny,id:500096\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"chain,phase:2,deny,id:500097\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (boundary special char - trailing comma+token)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data;boundary=0000,1111", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "--0000\r", + "Content-Disposition: form-data; name=\"name\"\r", + "\r", + "Brian Rectanus\r", + "--0000\r", + "Content-Disposition: form-data; name=\"email\"\r", + "\r", + "brian.rectanus@breach.com\r", + "--0000\r", + "Content-Disposition: form-data; name=\"image\"; filename=\"image.jpg\"\r", + "Content-Type: image/jpeg\r", + "\r", + "BINARYDATA\r", + "--0000--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "http_code": 403, + "debug_log": "Invalid boundary in C-T \\(characters\\)" + }, + "rules":[ + "SecRuleEngine On", + "SecRule MULTIPART_STRICT_ERROR \"@eq 1\" \"phase:2,deny,id:500095\"", + "SecRule MULTIPART_UNMATCHED_BOUNDARY \"@eq 1\" \"phase:2,deny,id:500096\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"chain,phase:2,deny,id:500097\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (quoted boundary - normal)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data;boundary=\"0000\"", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "--0000\r", + "Content-Disposition: form-data; name=\"name\"\r", + "\r", + "Brian Rectanus\r", + "--0000\r", + "Content-Disposition: form-data; name=\"email\"\r", + "\r", + "brian.rectanus@breach.com\r", + "--0000\r", + "Content-Disposition: form-data; name=\"image\"; filename=\"image.jpg\"\r", + "Content-Type: image/jpeg\r", + "\r", + "BINARYDATA\r", + "--0000--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "http_code": 403, + "debug_log": "boundary was quoted" + }, + "rules":[ + "SecRuleEngine On", + "SecRule MULTIPART_STRICT_ERROR \"@eq 1\" \"phase:2,deny,id:500095\"", + "SecRule MULTIPART_UNMATCHED_BOUNDARY \"@eq 1\" \"phase:2,deny,id:500096\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"chain,phase:2,deny,id:500097\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (quoted boundary value - whitespace before)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data;boundary=\" 0000\"", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "--0000\r", + "Content-Disposition: form-data; name=\"name\"\r", + "\r", + "Brian Rectanus\r", + "--0000\r", + "Content-Disposition: form-data; name=\"email\"\r", + "\r", + "brian.rectanus@breach.com\r", + "--0000\r", + "Content-Disposition: form-data; name=\"image\"; filename=\"image.jpg\"\r", + "Content-Type: image/jpeg\r", + "\r", + "BINARYDATA\r", + "--0000--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "http_code": 403, + "debug_log": "boundary was quoted.*No boundaries found in payload" + }, + "rules":[ + "SecRuleEngine On", + "SecRule MULTIPART_STRICT_ERROR \"@eq 1\" \"phase:2,deny,id:500095\"", + "SecRule MULTIPART_UNMATCHED_BOUNDARY \"@eq 1\" \"phase:2,deny,id:500096\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"chain,phase:2,deny,id:500097\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (quoted boundary value - whitespace after)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data;boundary=\"0000 \"", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "--0000\r", + "Content-Disposition: form-data; name=\"name\"\r", + "\r", + "Brian Rectanus\r", + "--0000\r", + "Content-Disposition: form-data; name=\"email\"\r", + "\r", + "brian.rectanus@breach.com\r", + "--0000\r", + "Content-Disposition: form-data; name=\"image\"; filename=\"image.jpg\"\r", + "Content-Type: image/jpeg\r", + "\r", + "BINARYDATA\r", + "--0000--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "http_code": 403, + "debug_log": "boundary was quoted.*No boundaries found in payload" + }, + "rules":[ + "SecRuleEngine On", + "SecRule MULTIPART_STRICT_ERROR \"@eq 1\" \"phase:2,deny,id:500095\"", + "SecRule MULTIPART_UNMATCHED_BOUNDARY \"@eq 1\" \"phase:2,deny,id:500096\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"chain,phase:2,deny,id:500097\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (quoted boundary value - whitespace after)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data;boundary=\"0000 \"", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "--0000\r", + "Content-Disposition: form-data; name=\"name\"\r", + "\r", + "Brian Rectanus\r", + "--0000\r", + "Content-Disposition: form-data; name=\"email\"\r", + "\r", + "brian.rectanus@breach.com\r", + "--0000\r", + "Content-Disposition: form-data; name=\"image\"; filename=\"image.jpg\"\r", + "Content-Type: image/jpeg\r", + "\r", + "BINARYDATA\r", + "--0000--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "http_code": 403, + "debug_log": "boundary was quoted.*No boundaries found in payload" + }, + "rules":[ + "SecRuleEngine On", + "SecRule MULTIPART_STRICT_ERROR \"@eq 1\" \"phase:2,deny,id:500095\"", + "SecRule MULTIPART_UNMATCHED_BOUNDARY \"@eq 1\" \"phase:2,deny,id:500096\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"chain,phase:2,deny,id:500097\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (quoted boundary value - whitespace between)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data;boundary=\"0000 1111\"", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "--0000\r", + "Content-Disposition: form-data; name=\"name\"\r", + "\r", + "Brian Rectanus\r", + "--0000\r", + "Content-Disposition: form-data; name=\"email\"\r", + "\r", + "brian.rectanus@breach.com\r", + "--0000\r", + "Content-Disposition: form-data; name=\"image\"; filename=\"image.jpg\"\r", + "Content-Type: image/jpeg\r", + "\r", + "BINARYDATA\r", + "--0000--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "http_code": 403, + "debug_log": "boundary was quoted" + }, + "rules":[ + "SecRuleEngine On", + "SecRule MULTIPART_STRICT_ERROR \"@eq 1\" \"phase:2,deny,id:500095\"", + "SecRule MULTIPART_UNMATCHED_BOUNDARY \"@eq 1\" \"phase:2,deny,id:500096\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"chain,phase:2,deny,id:500097\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (quoted boundary value - contained quote)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data;boundary=\"00\"00\"", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "--00\"00\r", + "Content-Disposition: form-data; name=\"name\"\r", + "\r", + "Brian Rectanus\r", + "--00\"00\r", + "Content-Disposition: form-data; name=\"email\"\r", + "\r", + "brian.rectanus@breach.com\r", + "--00\"00\r", + "Content-Disposition: form-data; name=\"image\"; filename=\"image.jpg\"\r", + "Content-Type: image/jpeg\r", + "\r", + "BINARYDATA\r", + "--00\"00--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "http_code": 403, + "debug_log": "Invalid boundary in C-T \\(characters\\)" + }, + "rules":[ + "SecRuleEngine On", + "SecRule MULTIPART_STRICT_ERROR \"@eq 1\" \"phase:2,deny,id:500095\"", + "SecRule MULTIPART_UNMATCHED_BOUNDARY \"@eq 1\" \"phase:2,deny,id:500096\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"chain,phase:2,deny,id:500097\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (quoted boundary value - two quoted values)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data;boundary=\"00\"\"00\"", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "--00\"00\r", + "Content-Disposition: form-data; name=\"name\"\r", + "\r", + "Brian Rectanus\r", + "--00\"00\r", + "Content-Disposition: form-data; name=\"email\"\r", + "\r", + "brian.rectanus@breach.com\r", + "--00\"00\r", + "Content-Disposition: form-data; name=\"image\"; filename=\"image.jpg\"\r", + "Content-Type: image/jpeg\r", + "\r", + "BINARYDATA\r", + "--00\"00--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "http_code": 403, + "debug_log": "Invalid boundary in C-T \\(characters\\)" + }, + "rules":[ + "SecRuleEngine On", + "SecRule MULTIPART_STRICT_ERROR \"@eq 1\" \"phase:2,deny,id:500095\"", + "SecRule MULTIPART_UNMATCHED_BOUNDARY \"@eq 1\" \"phase:2,deny,id:500096\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"chain,phase:2,deny,id:500097\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (partial quoted boundary value - only start quote)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data;boundary=\"0000", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "--0000\r", + "Content-Disposition: form-data; name=\"name\"\r", + "\r", + "Brian Rectanus\r", + "--0000\r", + "Content-Disposition: form-data; name=\"email\"\r", + "\r", + "brian.rectanus@breach.com\r", + "--0000\r", + "Content-Disposition: form-data; name=\"image\"; filename=\"image.jpg\"\r", + "Content-Type: image/jpeg\r", + "\r", + "BINARYDATA\r", + "--0000--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "http_code": 403, + "debug_log": "Invalid boundary in C-T \\(quote\\)" + }, + "rules":[ + "SecRuleEngine On", + "SecRule MULTIPART_STRICT_ERROR \"@eq 1\" \"phase:2,deny,id:500095\"", + "SecRule MULTIPART_UNMATCHED_BOUNDARY \"@eq 1\" \"phase:2,deny,id:500096\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"chain,phase:2,deny,id:500097\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (partial quoted boundary value - only end quote)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data;boundary=0000\"", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "--0000\r", + "Content-Disposition: form-data; name=\"name\"\r", + "\r", + "Brian Rectanus\r", + "--0000\r", + "Content-Disposition: form-data; name=\"email\"\r", + "\r", + "brian.rectanus@breach.com\r", + "--0000\r", + "Content-Disposition: form-data; name=\"image\"; filename=\"image.jpg\"\r", + "Content-Type: image/jpeg\r", + "\r", + "BINARYDATA\r", + "--0000--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "http_code": 403, + "debug_log": "Invalid boundary in C-T \\(quote\\)" + }, + "rules":[ + "SecRuleEngine On", + "SecRule MULTIPART_STRICT_ERROR \"@eq 1\" \"phase:2,deny,id:500095\"", + "SecRule MULTIPART_UNMATCHED_BOUNDARY \"@eq 1\" \"phase:2,deny,id:500096\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"chain,phase:2,deny,id:500097\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (multipart mixed - normal)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data; boundary=0000", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "--0000\r", + "Content-Disposition: form-data; name=\"name\"\r", + "\r", + "Brian Rectanus\r", + "--0000\r", + "Content-Disposition: form-data; name=\"email\"\r", + "\r", + "brian.rectanus@breach.com\r", + "--0000\r", + "Content-Disposition: attachment\r", + "Content-Type: multipart/mixed; boundary=BbC04y\r", + "\r", + "--BbC04y\r", + "Content-Disposition: file; filename=\"file1.txt\"\r", + "Content-Type: text/plain\r", + "\r", + "... contents of file1.txt ...\r", + "--BbC04y\r", + "Content-Disposition: file; filename=\"file2.gif\r", + "Content-Type: image/jpeg\r", + "Content-Transfer-Encoding: binary\r", + "\r", + "...contents of file2.gif...\r", + "--0000--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "http_code": 403, + "debug_log": "Invalid Content-Disposition header" + }, + "rules":[ + "SecRuleEngine On", + "SecRule MULTIPART_STRICT_ERROR \"@eq 1\" \"phase:2,deny,id:500095\"", + "SecRule MULTIPART_UNMATCHED_BOUNDARY \"@eq 1\" \"phase:2,deny,id:500096\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"chain,phase:2,deny,id:500097\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (multipart mixed - missing disposition)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data; boundary=0000", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "--0000\r", + "Content-Disposition: form-data; name=\"name\"\r", + "\r", + "Brian Rectanus\r", + "--0000\r", + "Content-Disposition: form-data; name=\"email\"\r", + "\r", + "brian.rectanus@breach.com\r", + "--0000\r", + "Content-Type: multipart/mixed; boundary=BbC04y\r", + "\r", + "--BbC04y\r", + "Content-Disposition: file; filename=\"file1.txt\"\r", + "Content-Type: text/plain\r", + "\r", + "... contents of file1.txt ...\r", + "--BbC04y\r", + "Content-Disposition: file; filename=\"file2.gif\r", + "Content-Type: image/jpeg\r", + "Content-Transfer-Encoding: binary\r", + "\r", + "...contents of file2.gif...\r", + "--0000--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "http_code": 403, + "debug_log": "Part missing Content-Disposition header" + }, + "rules":[ + "SecRuleEngine On", + "SecRule MULTIPART_STRICT_ERROR \"@eq 1\" \"phase:2,deny,id:500095\"", + "SecRule MULTIPART_UNMATCHED_BOUNDARY \"@eq 1\" \"phase:2,deny,id:500096\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"chain,phase:2,deny,id:500097\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"multipart parser (normal)", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data; boundary=0000", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "--0000\r", + "Content-Disposition: form-data; name=\"name\"\r", + "\r", + "Brian Rectanus\r", + "--0000\r", + "Content-Disposition: form-data; name=\"email\"\r", + "\r", + "brian.rectanus@breach.com\r", + "--0000\r", + "Content-Disposition: form-data; name=\"image1\"; filename=\"image1.jpg\"\r", + "Content-Type: image/jpeg\r", + "\r", + "BINARYDATA1\r", + "--0000\r", + "Content-Disposition: form-data; name=\"image2\"; filename=\"image2.jpg\"\r", + "Content-Type: image/jpeg\r", + "\r", + "BINARYDATA2\r", + "--0000\r", + "Content-Disposition: form-data; name=\"image3\"; filename=\"image3.jpg\"\r", + "Content-Type: image/jpeg\r", + "\r", + "BINARYDATA3\r", + "--0000\r", + "Content-Disposition: form-data; name=\"test\"\r", + "\r", + "This is test data.\r", + "--0000--\r" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "http_code": 403, + "debug_log": "Upload file limit exceeded" + }, + "rules":[ + "SecRuleEngine On", + "SecUploadKeepFiles On", + "SecUploadDir /tmp", + "SecUploadFileLimit 2", + "SecRule MULTIPART_STRICT_ERROR \"!@eq 1\" \"phase:2,deny,id:500161\"", + "SecRule MULTIPART_FILE_LIMIT_EXCEEDED \"!@eq 1\" \"phase:2,deny,id:500162\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"phase:2,deny,id:500163\"", + "SecRule &FILES \"!@eq 3\" \"phase:2,deny,id:500164\"", + "SecRule &FILES_NAMES \"!@eq 3\" \"phase:2,deny,id:500165\"", + "SecRule &FILES_SIZES \"!@eq 3\" \"phase:2,deny,id:500166\"", + "SecRule FILES_SIZES:/^image/ \"@eq 0\" \"phase:2,deny,id:500167\"", + "SecRule &FILES_TMPNAMES \"!@eq 2\" \"phase:2,deny,id:500168\"" + ] + } +] + diff --git a/test/test-cases/regression/variable-FILES.json b/test/test-cases/regression/variable-FILES.json index dc296ea7..12056feb 100644 --- a/test/test-cases/regression/variable-FILES.json +++ b/test/test-cases/regression/variable-FILES.json @@ -23,21 +23,21 @@ "uri":"/", "method":"POST", "body":[ - "--------------------------756b6d74fa1a8ee2", + "----------------------------756b6d74fa1a8ee2", "Content-Disposition: form-data; name=\"name\"", "", "test", - "--------------------------756b6d74fa1a8ee2", + "----------------------------756b6d74fa1a8ee2", "Content-Disposition: form-data; name=\"filedata\"; filename=\"small_text_file.txt\"", "Content-Type: text/plain", "", "This is a very small test file..", - "--------------------------756b6d74fa1a8ee2", + "----------------------------756b6d74fa1a8ee2", "Content-Disposition: form-data; name=\"filedata\"; filename=\"small_text_file.txt\"", "Content-Type: text/plain", "", "This is another very small test file..", - "--------------------------756b6d74fa1a8ee2--" + "----------------------------756b6d74fa1a8ee2--" ] }, "response":{ diff --git a/test/test-cases/regression/variable-FILES_COMBINED_SIZE.json b/test/test-cases/regression/variable-FILES_COMBINED_SIZE.json index 68690b13..f776fef7 100644 --- a/test/test-cases/regression/variable-FILES_COMBINED_SIZE.json +++ b/test/test-cases/regression/variable-FILES_COMBINED_SIZE.json @@ -23,21 +23,21 @@ "uri":"/", "method":"POST", "body":[ - "--------------------------756b6d74fa1a8ee2", + "----------------------------756b6d74fa1a8ee2", "Content-Disposition: form-data; name=\"name\"", "", "test", - "--------------------------756b6d74fa1a8ee2", + "----------------------------756b6d74fa1a8ee2", "Content-Disposition: form-data; name=\"filedata\"; filename=\"small_text_file.txt\"", "Content-Type: text/plain", "", "This is a very small test file..", - "--------------------------756b6d74fa1a8ee2", + "----------------------------756b6d74fa1a8ee2", "Content-Disposition: form-data; name=\"filedata\"; filename=\"small_text_file.txt\"", "Content-Type: text/plain", "", "This is another very small test file..", - "--------------------------756b6d74fa1a8ee2--" + "----------------------------756b6d74fa1a8ee2--" ] }, "response":{ @@ -51,13 +51,13 @@ ] }, "expected":{ - "debug_log":"Target value: \"74.000000\" \\(Variable: FILES_COMBINED_SIZE\\)" + "debug_log":"Target value: \"70\" \\(Variable: FILES_COMBINED_SIZE\\)" }, "rules":[ "SecRuleEngine On", "SecDebugLog \/tmp\/modsec_debug.log", "SecDebugLogLevel 9", - "SecRule FILES_COMBINED_SIZE \"@gt 74.000000\" \"id:1,phase:3,pass,t:trim\"" + "SecRule FILES_COMBINED_SIZE \"@gt 70\" \"id:1,phase:3,pass,t:trim\"" ] } ] \ No newline at end of file diff --git a/test/test-cases/regression/variable-FILES_NAMES.json b/test/test-cases/regression/variable-FILES_NAMES.json index 9df56616..ee306203 100644 --- a/test/test-cases/regression/variable-FILES_NAMES.json +++ b/test/test-cases/regression/variable-FILES_NAMES.json @@ -23,21 +23,21 @@ "uri":"/", "method":"POST", "body":[ - "--------------------------756b6d74fa1a8ee2", + "----------------------------756b6d74fa1a8ee2", "Content-Disposition: form-data; name=\"name\"", "", "test", - "--------------------------756b6d74fa1a8ee2", + "----------------------------756b6d74fa1a8ee2", "Content-Disposition: form-data; name=\"filedata\"; filename=\"small_text_file.txt\"", "Content-Type: text/plain", "", "This is a very small test file..", - "--------------------------756b6d74fa1a8ee2", + "----------------------------756b6d74fa1a8ee2", "Content-Disposition: form-data; name=\"filedata\"; filename=\"small_text_file.txt\"", "Content-Type: text/plain", "", "This is another very small test file..", - "--------------------------756b6d74fa1a8ee2--" + "----------------------------756b6d74fa1a8ee2--" ] }, "response":{ diff --git a/test/test-cases/regression/variable-FILES_SIZES.json b/test/test-cases/regression/variable-FILES_SIZES.json index b3fa7b62..7ac11478 100644 --- a/test/test-cases/regression/variable-FILES_SIZES.json +++ b/test/test-cases/regression/variable-FILES_SIZES.json @@ -23,21 +23,21 @@ "uri":"/", "method":"POST", "body":[ - "--------------------------756b6d74fa1a8ee2", + "----------------------------756b6d74fa1a8ee2", "Content-Disposition: form-data; name=\"name\"", "", "test", - "--------------------------756b6d74fa1a8ee2", + "----------------------------756b6d74fa1a8ee2", "Content-Disposition: form-data; name=\"filedata\"; filename=\"small_text_file.txt\"", "Content-Type: text/plain", "", "This is a very small test file..", - "--------------------------756b6d74fa1a8ee2", + "----------------------------756b6d74fa1a8ee2", "Content-Disposition: form-data; name=\"filedata\"; filename=\"small_text_file.txt\"", "Content-Type: text/plain", "", "This is another very small test file..", - "--------------------------756b6d74fa1a8ee2--" + "----------------------------756b6d74fa1a8ee2--" ] }, "response":{ diff --git a/test/test-cases/regression/variable-FILES_TMP_CONTENT.json b/test/test-cases/regression/variable-FILES_TMP_CONTENT.json deleted file mode 100644 index d57f6c1a..00000000 --- a/test/test-cases/regression/variable-FILES_TMP_CONTENT.json +++ /dev/null @@ -1,63 +0,0 @@ -[ - { - "enabled":1, - "version_min":300000, - "title":"Testing Variables :: FILES_NAMES (1/1)", - "client":{ - "ip":"200.249.12.31", - "port":123 - }, - "server":{ - "ip":"200.249.12.31", - "port":80 - }, - "request":{ - "headers":{ - "Host":"localhost", - "User-Agent":"curl/7.38.0", - "Accept":"*/*", - "Content-Length":"330", - "Content-Type":"multipart/form-data; boundary=--------------------------756b6d74fa1a8ee2", - "Expect":"100-continue" - }, - "uri":"/", - "method":"POST", - "body":[ - "--------------------------756b6d74fa1a8ee2", - "Content-Disposition: form-data; name=\"name\"", - "", - "test", - "--------------------------756b6d74fa1a8ee2", - "Content-Disposition: form-data; name=\"filedata\"; filename=\"small_text_file.txt\"", - "Content-Type: text/plain", - "", - "This is a very small test file..", - "--------------------------756b6d74fa1a8ee2", - "Content-Disposition: form-data; name=\"filedata\"; filename=\"small_text_file.txt\"", - "Content-Type: text/plain", - "", - "This is another very small test file..", - "--------------------------756b6d74fa1a8ee2--" - ] - }, - "response":{ - "headers":{ - "Date":"Mon, 13 Jul 2015 20:02:41 GMT", - "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", - "Content-Type":"text/html" - }, - "body":[ - "no need." - ] - }, - "expected":{ - "debug_log":"trim: \"This is another very small test file.." - }, - "rules":[ - "SecRuleEngine On", - "SecDebugLog \/tmp\/modsec_debug.log", - "SecDebugLogLevel 9", - "SecRule FILES_TMP_CONTENT \"@contains small_text_file.txt\" \"id:1,phase:3,pass,t:trim\"" - ] - } -] \ No newline at end of file diff --git a/test/test-cases/regression/variable-MULTIPART_CRLF_LF_LINES.json b/test/test-cases/regression/variable-MULTIPART_CRLF_LF_LINES.json index 667223a3..5415d1e3 100644 --- a/test/test-cases/regression/variable-MULTIPART_CRLF_LF_LINES.json +++ b/test/test-cases/regression/variable-MULTIPART_CRLF_LF_LINES.json @@ -23,21 +23,21 @@ "uri":"/", "method":"POST", "body":[ - "--------------------------756b6d74fa1a8ee2", + "----------------------------756b6d74fa1a8ee2", "Content-Disposition: form-data; name=\"name\"", "", "test", - "--------------------------756b6d74fa1a8ee2", + "----------------------------756b6d74fa1a8ee2", "Content-Disposition: form-data; name=\"filedata\"; filename=\"small_text_file.txt\"", "Content-Type: text/plain", "", "This is a very small test file..", - "--------------------------756b6d74fa1a8ee2", + "----------------------------756b6d74fa1a8ee2", "Content-Disposition: form-data; name=\"filedata\"; filename=\"small_text_file.txt\"", "Content-Type: text/plain", "", "This is another very small test file..", - "--------------------------756b6d74fa1a8ee2--" + "----------------------------756b6d74fa1a8ee2--" ] }, "response":{ @@ -84,21 +84,21 @@ "uri":"/", "method":"POST", "body":[ - "--------------------------756b6d74fa1a8ee2", + "----------------------------756b6d74fa1a8ee2", "Content-Disposition: form-data; name=\"name\"", "", "test", - "--------------------------756b6d74fa1a8ee2", + "----------------------------756b6d74fa1a8ee2", "Content-Disposition: form-data; name=\"filedata\"; filename=\"small_text_file.txt\"", "Content-Type: text/plain", "", "This is a very small test file..", - "--------------------------756b6d74fa1a8ee2", + "----------------------------756b6d74fa1a8ee2", "Content-Disposition: form-data; name=\"filedata\"; filename=\"small_text_file.txt\"\r", "Content-Type: text/plain\r", "\r", "This is another very small test file..\r", - "--------------------------756b6d74fa1a8ee2--\r" + "----------------------------756b6d74fa1a8ee2--\r" ] }, "response":{ diff --git a/test/test-cases/regression/variable-MULTIPART_FILENAME.json b/test/test-cases/regression/variable-MULTIPART_FILENAME.json index 58ccf402..dbae0b31 100644 --- a/test/test-cases/regression/variable-MULTIPART_FILENAME.json +++ b/test/test-cases/regression/variable-MULTIPART_FILENAME.json @@ -23,21 +23,21 @@ "uri":"/", "method":"POST", "body":[ - "--------------------------756b6d74fa1a8ee2", + "----------------------------756b6d74fa1a8ee2", "Content-Disposition: form-data; name=\"name\"", "", "test", - "--------------------------756b6d74fa1a8ee2", + "----------------------------756b6d74fa1a8ee2", "Content-Disposition: form-data; name=\"filedata\"; filename=\"small_text_file.txt\"", "Content-Type: text/plain", "", "This is a very small test file..", - "--------------------------756b6d74fa1a8ee2", + "----------------------------756b6d74fa1a8ee2", "Content-Disposition: form-data; name=\"filedata\"; filename=\"small_text_file.txt\"", "Content-Type: text/plain", "", "This is another very small test file..", - "--------------------------756b6d74fa1a8ee2--" + "----------------------------756b6d74fa1a8ee2--" ] }, "response":{ @@ -84,21 +84,21 @@ "uri":"/", "method":"POST", "body":[ - "--------------------------756b6d74fa1a8ee2", + "----------------------------756b6d74fa1a8ee2", "Content-Disposition: form-data; name=\"name\"", "", "test", - "--------------------------756b6d74fa1a8ee2", + "----------------------------756b6d74fa1a8ee2", "Content-Disposition: form-data; name=\"filedata\"; filename=\"small_text_file.txt\"", "Content-Type: text/plain", "", "This is a very small test file..", - "--------------------------756b6d74fa1a8ee2", + "----------------------------756b6d74fa1a8ee2", "Content-Disposition: form-data; name=\"filedata\"; filename=\"small_text_file2.txt\"\r", "Content-Type: text/plain\r", "\r", "This is another very small test file..\r", - "--------------------------756b6d74fa1a8ee2--\r" + "----------------------------756b6d74fa1a8ee2--\r" ] }, "response":{ diff --git a/test/test-cases/regression/variable-MULTIPART_INVALID_HEADER_FOLDING.json b/test/test-cases/regression/variable-MULTIPART_INVALID_HEADER_FOLDING.json new file mode 100644 index 00000000..b9e14f98 --- /dev/null +++ b/test/test-cases/regression/variable-MULTIPART_INVALID_HEADER_FOLDING.json @@ -0,0 +1,61 @@ +[ + { + "enabled":1, + "version_min":300000, + "title":"Testing Variables :: MULTIPART_INVALID_HEADER_FOLDING", + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Content-Length":"330", + "Content-Type":"multipart/form-data; boundary=-----------------------------69343412719991675451336310646", + "Expect":"100-continue" + }, + "uri":"/", + "method":"POST", + "body":[ + "-------------------------------69343412719991675451336310646", + "Content-Disposition: form-data;", + " name=\"a\"", + "", + "1", + "-------------------------------69343412719991675451336310646", + "Content-Disposition: form-data;", + " name=\"b\"", + "", + "2", + "-------------------------------69343412719991675451336310646--" + ] + }, + "response":{ + "headers":{ + "Date":"Mon, 13 Jul 2015 20:02:41 GMT", + "Last-Modified":"Sun, 26 Oct 2014 22:33:37 GMT", + "Content-Type":"text/html" + }, + "body":[ + "no need." + ] + }, + "expected":{ + "debug_log":"ARGS:a" + }, + "rules":[ + "SecRule MULTIPART_STRICT_ERROR \"!@eq 1\" \"phase:2,deny,status:403,id:500074\"", + "SecRule MULTIPART_HEADER_FOLDING \"!@eq 1\" \"phase:2,deny,status:403,id:500075\"", + "SecRule MULTIPART_INVALID_HEADER_FOLDING \"!@eq 0\" \"phase:2,deny,status:403,id:500076\"", + "SecRule REQBODY_PROCESSOR_ERROR \"@eq 1\" \"phase:2,deny,status:403,id:500077\"", + "SecRule ARGS \"@eq 1\" \"phase:2,deny,status:403,id:5000277\"" + ] + } +] + diff --git a/test/test-cases/regression/variable-MULTIPART_NAME.json b/test/test-cases/regression/variable-MULTIPART_NAME.json index 2f63d5a6..212dbdce 100644 --- a/test/test-cases/regression/variable-MULTIPART_NAME.json +++ b/test/test-cases/regression/variable-MULTIPART_NAME.json @@ -23,21 +23,21 @@ "uri":"/", "method":"POST", "body":[ - "--------------------------756b6d74fa1a8ee2", + "----------------------------756b6d74fa1a8ee2", "Content-Disposition: form-data; name=\"name\"", "", "test", - "--------------------------756b6d74fa1a8ee2", + "----------------------------756b6d74fa1a8ee2", "Content-Disposition: form-data; name=\"filedata\"; filename=\"small_text_file.txt\"", "Content-Type: text/plain", "", "This is a very small test file..", - "--------------------------756b6d74fa1a8ee2", + "----------------------------756b6d74fa1a8ee2", "Content-Disposition: form-data; name=\"filedata\"; filename=\"small_text_file.txt\"", "Content-Type: text/plain", "", "This is another very small test file..", - "--------------------------756b6d74fa1a8ee2--" + "----------------------------756b6d74fa1a8ee2--" ] }, "response":{ @@ -84,21 +84,21 @@ "uri":"/", "method":"POST", "body":[ - "--------------------------756b6d74fa1a8ee2", + "----------------------------756b6d74fa1a8ee2", "Content-Disposition: form-data; name=\"name\"", "", "test", - "--------------------------756b6d74fa1a8ee2", + "----------------------------756b6d74fa1a8ee2", "Content-Disposition: form-data; name=\"filedata\"; filename=\"small_text_file.txt\"", "Content-Type: text/plain", "", "This is a very small test file..", - "--------------------------756b6d74fa1a8ee2", + "----------------------------756b6d74fa1a8ee2", "Content-Disposition: form-data; name=\"filedata2\"; filename=\"small_text_file2.txt\"\r", "Content-Type: text/plain\r", "\r", "This is another very small test file..\r", - "--------------------------756b6d74fa1a8ee2--\r" + "----------------------------756b6d74fa1a8ee2--\r" ] }, "response":{ diff --git a/test/test-cases/regression/variable-MULTIPART_STRICT_ERROR.json b/test/test-cases/regression/variable-MULTIPART_STRICT_ERROR.json index 4876f97d..bd042162 100644 --- a/test/test-cases/regression/variable-MULTIPART_STRICT_ERROR.json +++ b/test/test-cases/regression/variable-MULTIPART_STRICT_ERROR.json @@ -267,21 +267,21 @@ "uri":"/", "method":"POST", "body":[ - "--------------------------756b6d74fa1a8ee2", + "----------------------------756b6d74fa1a8ee2", "Content-Disposition: form-data; name=\"name\"", "", "test", - "--------------------------756b6d74fa1a8ee2", + "----------------------------756b6d74fa1a8ee2", "Content-Disposition: form-data; name=\"filedata\"; filename=\"small_text_file.txt\"", "Content-Type: text/plain", "", "This is a very small test file..", - "--------------------------756b6d74fa1a8ee2", + "----------------------------756b6d74fa1a8ee2", "Content-Disposition: form-data; name=\"filedata\"; filename=\"small_text_file.txt\"", "Content-Type: text/plain", "", "This is another very small test file..", - "--------------------------756b6d74fa1a8ee2--" + "----------------------------756b6d74fa1a8ee2--" ] }, "response":{ @@ -295,7 +295,7 @@ ] }, "expected":{ - "debug_log":"Boundary missing semicolon, setting MULTIPART_STRICT_ERROR to 1" + "debug_log":"Warning: incorrect line endings used \(LF\)" }, "rules":[ "SecRuleEngine On", @@ -356,7 +356,7 @@ ] }, "expected":{ - "debug_log":"Multipart: Invalid quote, setting MULTIPART_STRICT_ERROR to 1" + "debug_log":"Multipart: Warning: seen data before first boundary" }, "rules":[ "SecRuleEngine On",