diff --git a/headers/modsecurity/assay.h b/headers/modsecurity/assay.h index bb23a155..fafd78b8 100644 --- a/headers/modsecurity/assay.h +++ b/headers/modsecurity/assay.h @@ -194,6 +194,7 @@ class Assay { std::string *m_namesArgsGet; std::string *m_requestHeadersNames; std::string *m_responseHeadersNames; + std::string *m_responseContentType; double m_ARGScombinedSize; /** TODO: Support to save double in the storage. */ std::string *m_ARGScombinedSizeStr; diff --git a/headers/modsecurity/rules_properties.h b/headers/modsecurity/rules_properties.h index 46b91b10..06ce47e3 100644 --- a/headers/modsecurity/rules_properties.h +++ b/headers/modsecurity/rules_properties.h @@ -201,6 +201,7 @@ class RulesProperties { std::string audit_log_path; std::string audit_log_parts; std::list components; + std::set m_responseBodyTypeToBeInspected; DebugLog *m_debugLog; diff --git a/src/assay.cc b/src/assay.cc index 634f9087..28d01460 100644 --- a/src/assay.cc +++ b/src/assay.cc @@ -105,6 +105,7 @@ Assay::Assay(ModSecurity *ms, Rules *rules, void *logCbData) m_requestBodyType(UnknownFormat), m_requestHeadersNames(NULL), m_responseHeadersNames(NULL), + m_responseContentType(NULL), m_marker(""), start(cpu_seconds()), m_logCbData(logCbData), @@ -127,6 +128,10 @@ Assay::Assay(ModSecurity *ms, Rules *rules, void *logCbData) m_collections.store("RESPONSE_HEADERS_NAMES", std::string("")); this->m_responseHeadersNames = m_collections.resolveFirst( "RESPONSE_HEADERS_NAMES"); + m_collections.store("RESPONSE_CONTENT_TYPE", std::string("")); + this->m_responseContentType = m_collections.resolveFirst( + "RESPONSE_CONTENT_TYPE"); + #ifndef NO_LOGS this->debug(4, "Initialising transaction"); @@ -860,7 +865,7 @@ int Assay::addResponseHeader(const std::string& key, this->m_collections.store("RESPONSE_HEADERS:" + key, value); if (tolower(key) == "content-type") { - this->m_collections.store("RESPONSE_CONTENT_TYPE", value); + this->m_responseContentType->assign(value); } return 1; } @@ -951,6 +956,21 @@ int Assay::processResponseBody() { return true; } + std::set &bi = this->m_rules->m_responseBodyTypeToBeInspected; + auto t = bi.find(*m_responseContentType); + if (t == bi.end() && bi.empty() == false) { +#ifndef NO_LOGS + debug(5, "Response Content-Type is " + *m_responseContentType + \ + ". It is not marked to be inspected."); + std::string validContetTypes(""); + for (std::set::iterator i = bi.begin(); + i != bi.end(); i++) { + validContetTypes.append(*i + " "); + } + debug(8, "Content-Type(s) marked to be inspected: " + validContetTypes); +#endif + return true; + } if (m_collections.resolveFirst("OUTBOUND_DATA_ERROR") == NULL) { m_collections.store("OUTBOUND_DATA_ERROR", "0"); } @@ -985,6 +1005,17 @@ int Assay::processResponseBody() { int Assay::appendResponseBody(const unsigned char *buf, size_t len) { int current_size = this->m_responseBody.tellp(); + std::set &bi = this->m_rules->m_responseBodyTypeToBeInspected; + auto t = bi.find(*m_responseContentType); + if (t == bi.end() && bi.empty() == false) { +#ifndef NO_LOGS + debug(4, "Not appending response body. " \ + "Response Content-Type is " + *m_responseContentType + \ + ". It is not marked to be inspected."); +#endif + return true; + } + #ifndef NO_LOGS debug(9, "Appending response body: " + std::to_string(len + current_size) + " bytes. Limit set to: " + diff --git a/src/parser/seclang-parser.yy b/src/parser/seclang-parser.yy index 11c90ac1..68a18d56 100644 --- a/src/parser/seclang-parser.yy +++ b/src/parser/seclang-parser.yy @@ -8,7 +8,8 @@ %define parse.assert %code requires { -# include +#include +#include namespace ModSecurity { namespace Parser { @@ -601,6 +602,16 @@ expression: | CONFIG_DIR_PCRE_MATCH_LIMIT_RECURSION | CONFIG_DIR_PCRE_MATCH_LIMIT | CONGIG_DIR_RESPONSE_BODY_MP + { + std::istringstream buf($1); + std::istream_iterator beg(buf), end; + std::set tokens(beg, end); + for (std::set::iterator it=tokens.begin(); + it!=tokens.end(); ++it) + { + driver.m_responseBodyTypeToBeInspected.insert(*it); + } + } | CONGIG_DIR_SEC_TMP_DIR | CONGIG_DIR_SEC_DATA_DIR | CONGIG_DIR_SEC_ARG_SEP diff --git a/src/parser/seclang-scanner.ll b/src/parser/seclang-scanner.ll index 61b23cc5..ee3f56b0 100755 --- a/src/parser/seclang-scanner.ll +++ b/src/parser/seclang-scanner.ll @@ -259,7 +259,7 @@ CONFIG_DIR_UNICODE_MAP_FILE (?i:SecUnicodeMapFile) %{ /* Other configurations */ %} {CONFIG_DIR_PCRE_MATCH_LIMIT_RECURSION}[ ]{CONFIG_VALUE_NUMBER} { return yy::seclang_parser::make_CONFIG_DIR_PCRE_MATCH_LIMIT_RECURSION(strchr(yytext, ' ') + 1, *driver.loc.back()); } {CONFIG_DIR_PCRE_MATCH_LIMIT}[ ]{CONFIG_VALUE_NUMBER} { return yy::seclang_parser::make_CONFIG_DIR_PCRE_MATCH_LIMIT(strchr(yytext, ' ') + 1, *driver.loc.back()); } -{CONGIG_DIR_RESPONSE_BODY_MP}[ ]{FREE_TEXT_NEW_LINE} { return yy::seclang_parser::make_CONGIG_DIR_RESPONSE_BODY_MP(yytext, *driver.loc.back()); } +{CONGIG_DIR_RESPONSE_BODY_MP}[ ]{FREE_TEXT_NEW_LINE} { return yy::seclang_parser::make_CONGIG_DIR_RESPONSE_BODY_MP(strchr(yytext, ' ') + 1, *driver.loc.back()); } {CONGIG_DIR_SEC_TMP_DIR}[ ]{CONFIG_VALUE_PATH} { return yy::seclang_parser::make_CONGIG_DIR_SEC_TMP_DIR(strchr(yytext, ' ') + 1, *driver.loc.back()); } {CONGIG_DIR_SEC_DATA_DIR}[ ]{CONFIG_VALUE_PATH} { return yy::seclang_parser::make_CONGIG_DIR_SEC_DATA_DIR(strchr(yytext, ' ') + 1, *driver.loc.back()); } {CONGIG_DIR_SEC_ARG_SEP}[ ]{FREE_TEXT_NEW_LINE} { return yy::seclang_parser::make_CONGIG_DIR_SEC_ARG_SEP(yytext, *driver.loc.back()); } diff --git a/src/rules.cc b/src/rules.cc index 78170f0c..d8ed622d 100644 --- a/src/rules.cc +++ b/src/rules.cc @@ -221,6 +221,13 @@ int Rules::merge(Driver *from) { this->requestBodyLimitAction = from->requestBodyLimitAction; this->responseBodyLimitAction = from->responseBodyLimitAction; + for (std::set::iterator + it=from->m_responseBodyTypeToBeInspected.begin(); + it!=from->m_responseBodyTypeToBeInspected.end(); ++it) + { + m_responseBodyTypeToBeInspected.insert(*it); + } + /* * * default Actions is something per configuration context, there is diff --git a/test/test-cases/regression/config-response_type.json b/test/test-cases/regression/config-response_type.json new file mode 100644 index 00000000..c324300c --- /dev/null +++ b/test/test-cases/regression/config-response_type.json @@ -0,0 +1,87 @@ +[ + { + "enabled":1, + "version_min":300000, + "title":"SecResponseBodyMimeType (1/x)", + "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":"*/*" + }, + "uri":"/?key=value&key=other_value", + "method":"GET" + }, + "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":"T \\(0\\) trim: \"no need.\"" + }, + "rules":[ + "SecRuleEngine On", + "SecDebugLog \/tmp\/modsec_debug.log", + "SecDebugLogLevel 9", + "SecResponseBodyMimeType text\/plain text\/html text\/xml", + "SecRule RESPONSE_BODY \"@contains RESPONSE_CONTENT_TYPE\" \"id:9,pass,t:trim,phase:4\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"SecResponseBodyMimeType (1/x)", + "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":"*/*" + }, + "uri":"/?key=value&key=other_value", + "method":"GET" + }, + "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":"T \\(0\\) trim: \"text/html\"" + }, + "rules":[ + "SecRuleEngine On", + "SecDebugLog \/tmp\/modsec_debug.log", + "SecDebugLogLevel 9", + "SecResponseBodyMimeType application\/something", + "SecRule RESPONSE_BODY \"@contains RESPONSE_CONTENT_TYPE\" \"id:9,pass,t:trim,phase:4\"" + ] + } + +] \ No newline at end of file