diff --git a/headers/modsecurity/rules_set_properties.h b/headers/modsecurity/rules_set_properties.h index ff65f4b7a6..00cf9ee3c1 100644 --- a/headers/modsecurity/rules_set_properties.h +++ b/headers/modsecurity/rules_set_properties.h @@ -368,6 +368,7 @@ class RulesSetProperties { to->m_argumentsLimit.merge(&from->m_argumentsLimit); to->m_requestBodyJsonDepthLimit.merge(&from->m_requestBodyJsonDepthLimit); to->m_requestBodyLimit.merge(&from->m_requestBodyLimit); + to->m_requestBodyNoFilesLimit.merge(&from->m_requestBodyNoFilesLimit); to->m_responseBodyLimit.merge(&from->m_responseBodyLimit); merge_bodylimitaction_value(to->m_requestBodyLimitAction, diff --git a/src/transaction.cc b/src/transaction.cc index 132ab9eb74..96f9c21b34 100644 --- a/src/transaction.cc +++ b/src/transaction.cc @@ -804,25 +804,43 @@ int Transaction::processRequestBody() { */ std::unique_ptr a = m_variableRequestHeaders.resolveFirst( "Content-Type"); + + bool requestBodyNoFilesLimitExceeded = false; + if ((m_requestBodyType == WWWFormUrlEncoded) || + (m_requestBodyProcessor == JSONRequestBody) || + (m_requestBodyProcessor == XMLRequestBody)) { + if ((m_rules->m_requestBodyNoFilesLimit.m_set) + && (m_requestBody.str().size() > m_rules->m_requestBodyNoFilesLimit.m_value)) { + m_variableReqbodyError.set("1", 0); + m_variableReqbodyErrorMsg.set("Request body excluding files is bigger than the maximum expected.", 0); + m_variableInboundDataError.set("1", m_variableOffset); + ms_dbg(5, "Request body excluding files is bigger than the maximum expected."); + requestBodyNoFilesLimitExceeded = true; + } + } + #ifdef WITH_LIBXML2 if (m_requestBodyProcessor == XMLRequestBody) { - std::string error; - if (m_xml->init() == true) { - m_xml->processChunk(m_requestBody.str().c_str(), - m_requestBody.str().size(), - &error); - m_xml->complete(&error); - } - if (error.empty() == false) { - m_variableReqbodyError.set("1", m_variableOffset); - m_variableReqbodyErrorMsg.set("XML parsing error: " + error, - m_variableOffset); - m_variableReqbodyProcessorErrorMsg.set("XML parsing error: " \ - + error, m_variableOffset); - m_variableReqbodyProcessorError.set("1", m_variableOffset); - } else { - m_variableReqbodyError.set("0", m_variableOffset); - m_variableReqbodyProcessorError.set("0", m_variableOffset); + // large size might cause issues in the parsing itself; omit if exceeded + if (!requestBodyNoFilesLimitExceeded) { + std::string error; + if (m_xml->init() == true) { + m_xml->processChunk(m_requestBody.str().c_str(), + m_requestBody.str().size(), + &error); + m_xml->complete(&error); + } + if (error.empty() == false) { + m_variableReqbodyError.set("1", m_variableOffset); + m_variableReqbodyErrorMsg.set("XML parsing error: " + error, + m_variableOffset); + m_variableReqbodyProcessorErrorMsg.set("XML parsing error: " \ + + error, m_variableOffset); + m_variableReqbodyProcessorError.set("1", m_variableOffset); + } else { + m_variableReqbodyError.set("0", m_variableOffset); + m_variableReqbodyProcessorError.set("0", m_variableOffset); + } } #endif #if WITH_YAJL @@ -831,26 +849,29 @@ int Transaction::processRequestBody() { #else if (m_requestBodyProcessor == JSONRequestBody) { #endif - std::string error; - if (m_rules->m_requestBodyJsonDepthLimit.m_set) { - m_json->setMaxDepth(m_rules->m_requestBodyJsonDepthLimit.m_value); - } - if (m_json->init() == true) { - m_json->processChunk(m_requestBody.str().c_str(), - m_requestBody.str().size(), - &error); - m_json->complete(&error); - } - if (error.empty() == false && m_requestBody.str().size() > 0) { - m_variableReqbodyError.set("1", m_variableOffset); - m_variableReqbodyProcessorError.set("1", m_variableOffset); - m_variableReqbodyErrorMsg.set("JSON parsing error: " + error, - m_variableOffset); - m_variableReqbodyProcessorErrorMsg.set("JSON parsing error: " \ - + error, m_variableOffset); - } else { - m_variableReqbodyError.set("0", m_variableOffset); - m_variableReqbodyProcessorError.set("0", m_variableOffset); + // large size might cause issues in the parsing itself; omit if exceeded + if (!requestBodyNoFilesLimitExceeded) { + std::string error; + if (m_rules->m_requestBodyJsonDepthLimit.m_set) { + m_json->setMaxDepth(m_rules->m_requestBodyJsonDepthLimit.m_value); + } + if (m_json->init() == true) { + m_json->processChunk(m_requestBody.str().c_str(), + m_requestBody.str().size(), + &error); + m_json->complete(&error); + } + if (error.empty() == false && m_requestBody.str().size() > 0) { + m_variableReqbodyError.set("1", m_variableOffset); + m_variableReqbodyProcessorError.set("1", m_variableOffset); + m_variableReqbodyErrorMsg.set("JSON parsing error: " + error, + m_variableOffset); + m_variableReqbodyProcessorErrorMsg.set("JSON parsing error: " \ + + error, m_variableOffset); + } else { + m_variableReqbodyError.set("0", m_variableOffset); + m_variableReqbodyProcessorError.set("0", m_variableOffset); + } } #endif #if defined(WITH_LIBXML2) or defined(WITH_YAJL) @@ -859,11 +880,13 @@ int Transaction::processRequestBody() { if (m_requestBodyType == MultiPartRequestBody) { #endif std::string error; + int reqbodyNoFilesLength = 0; if (a != NULL) { Multipart m(*a, this); if (m.init(&error) == true) { m.process(m_requestBody.str(), &error, m_variableOffset); } + reqbodyNoFilesLength = m.m_reqbody_no_files_length; m.multipart_complete(&error); } if (error.empty() == false) { @@ -873,13 +896,22 @@ int Transaction::processRequestBody() { m_variableOffset); m_variableReqbodyProcessorErrorMsg.set("Multipart parsing " \ "error: " + error, m_variableOffset); + } else if (((m_rules->m_requestBodyNoFilesLimit.m_set) + && (reqbodyNoFilesLength > m_rules->m_requestBodyNoFilesLimit.m_value))) { + m_variableReqbodyError.set("1", 0); + m_variableReqbodyErrorMsg.set("Request body excluding files is bigger than the maximum expected.", 0); + m_variableInboundDataError.set("1", m_variableOffset); + ms_dbg(5, "Request body excluding files is bigger than the maximum expected."); } else { m_variableReqbodyError.set("0", m_variableOffset); m_variableReqbodyProcessorError.set("0", m_variableOffset); } } else if (m_requestBodyType == WWWFormUrlEncoded) { m_variableOffset++; - extractArguments("POST", m_requestBody.str(), m_variableOffset); + // large size might cause issues in the parsing itself; omit if exceeded + if (!requestBodyNoFilesLimitExceeded) { + extractArguments("POST", m_requestBody.str(), m_variableOffset); + } } else if (m_requestBodyType != UnknownFormat) { /** * FIXME: double check to see if that is a valid scenario... diff --git a/test/test-cases/regression/config-body_limits.json b/test/test-cases/regression/config-body_limits.json index a88035ba01..ebc047db56 100644 --- a/test/test-cases/regression/config-body_limits.json +++ b/test/test-cases/regression/config-body_limits.json @@ -386,6 +386,398 @@ "SecResponseBodyLimitAction Reject", "SecResponseBodyLimit 5" ] + }, + { + "enabled":1, + "version_min":300000, + "title":"SecRequestBodyNoFilesLimit - urlencoded, limit exceeded", + "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": "41", + "Content-Type": "application/x-www-form-urlencoded" + }, + "uri":"/", + "method":"POST", + "body": [ + "param1=value1¶m2=value2¶m3=value3" + ] + }, + "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":"Request body excluding files is bigger than the maximum expected.", + "http_code":400 + }, + "rules":[ + "SecRuleEngine On", + "SecRequestBodyAccess On", + "SecRequestBodyNoFilesLimit 20", + "SecRule REQBODY_ERROR \"!@eq 0\" \"id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"SecRequestBodyNoFilesLimit - urlencoded, limit not exceeded", + "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": "41", + "Content-Type": "application/x-www-form-urlencoded" + }, + "uri":"/", + "method":"POST", + "body": [ + "param1=value1¶m2=value2¶m3=value3" + ] + }, + "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":200 + }, + "rules":[ + "SecRuleEngine On", + "SecRequestBodyAccess On", + "SecRequestBodyNoFilesLimit 60", + "SecRule REQBODY_ERROR \"!@eq 0\" \"id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"SecRequestBodyNoFilesLimit - json, limit exceeded", + "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": "41", + "Content-Type": "application/json" + }, + "uri":"/", + "method":"POST", + "body": [ + "{\"param1\":{\"param2\":\"value2\",\"param3\":\"value3\"}}" + ] + }, + "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":"Request body excluding files is bigger than the maximum expected.", + "http_code":400 + }, + "rules":[ + "SecRuleEngine On", + "SecRequestBodyAccess On", + "SecRequestBodyNoFilesLimit 20", + "SecRule REQUEST_HEADERS:Content-Type \"application/json\" \"id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON\"", + "SecRule REQBODY_ERROR \"!@eq 0\" \"id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"SecRequestBodyNoFilesLimit - json, limit not exceeded", + "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": "41", + "Content-Type": "application/json" + }, + "uri":"/", + "method":"POST", + "body": [ + "{\"param1\":{\"param2\":\"value2\",\"param3\":\"value3\"}}" + ] + }, + "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":200 + }, + "rules":[ + "SecRuleEngine On", + "SecRequestBodyAccess On", + "SecRequestBodyNoFilesLimit 80", + "SecRule REQUEST_HEADERS:Content-Type \"application/json\" \"id:'200001',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=JSON\"", + "SecRule REQBODY_ERROR \"!@eq 0\" \"id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"SecRequestBodyNoFilesLimit - xml, limit exceeded", + "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": "77", + "Content-Type": "application/xml" + }, + "uri":"/", + "method":"POST", + "body": [ + "ccceee" + ] + }, + "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":"Request body excluding files is bigger than the maximum expected.", + "http_code":400 + }, + "rules":[ + "SecRuleEngine On", + "SecRequestBodyAccess On", + "SecRequestBodyNoFilesLimit 20", + "SecRule REQUEST_HEADERS:Content-Type \"(?:application(?:/soap\\+|/)|text/)xml\" \"id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML\"", + "SecRule REQBODY_ERROR \"!@eq 0\" \"id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"SecRequestBodyNoFilesLimit - xml, limit not exceeded", + "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": "77", + "Content-Type": "application/xml" + }, + "uri":"/", + "method":"POST", + "body": [ + "ccceee" + ] + }, + "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":200 + }, + "rules":[ + "SecRuleEngine On", + "SecRequestBodyAccess On", + "SecRequestBodyNoFilesLimit 90", + "SecRule REQUEST_HEADERS:Content-Type \"(?:application(?:/soap\\+|/)|text/)xml\" \"id:'200000',phase:1,t:none,t:lowercase,pass,nolog,ctl:requestBodyProcessor=XML\"", + "SecRule REQBODY_ERROR \"!@eq 0\" \"id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"SecRequestBodyNoFilesLimit - multipart, limit exceeded", + "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": "77", + "Content-Type": "multipart/form-data; boundary=0000" + }, + "uri":"/", + "method":"POST", + "body": [ + "--0000", + "Content-Disposition: form-data; name=\"a\"", + "", + "1", + "--0000", + "Content-Disposition: form-data; name=\"b\"; filename=\"c.txt\"", + "", + "2222222222222222222222222222222222222222222222222222222222222222222222", + "--0000--" + ] + }, + "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":"Request body excluding files is bigger than the maximum expected.", + "http_code":400 + }, + "rules":[ + "SecRuleEngine On", + "SecRequestBodyAccess On", + "SecRequestBodyNoFilesLimit 80", + "SecRule REQBODY_ERROR \"!@eq 0\" \"id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"SecRequestBodyNoFilesLimit - multipart, limit not exceeded", + "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": "77", + "Content-Type": "multipart/form-data; boundary=0000" + }, + "uri":"/", + "method":"POST", + "body": [ + "--0000", + "Content-Disposition: form-data; name=\"a\"", + "", + "1", + "--0000", + "Content-Disposition: form-data; name=\"b\"; filename=\"c.txt\"", + "", + "2222222222222222222222222222222222222222222222222222222222222222222222", + "--0000--" + ] + }, + "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":200 + }, + "rules":[ + "SecRuleEngine On", + "SecRequestBodyAccess On", + "SecRequestBodyNoFilesLimit 120", + "SecRule REQBODY_ERROR \"!@eq 0\" \"id:'200002', phase:2,t:none,log,deny,status:400,msg:'Failed to parse request body.',logdata:'%{reqbody_error_msg}',severity:2\"" + ] } ]