diff --git a/headers/modsecurity/rules_properties.h b/headers/modsecurity/rules_properties.h index faf9982c12..f66f1c93e5 100644 --- a/headers/modsecurity/rules_properties.h +++ b/headers/modsecurity/rules_properties.h @@ -385,6 +385,7 @@ class RulesProperties { PropertyNotSetConfigBoolean); 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 cd71a0391c..ffb57965fa 100644 --- a/src/transaction.cc +++ b/src/transaction.cc @@ -785,6 +785,9 @@ int Transaction::processRequestBody() { m_variableInboundDataError.set("0", 0); } + // set the default reqBody length + // if CT *IS NOT* multipart, no_files value will the whole request length + size_t reqbodyNoFilesLength = m_requestBody.str().size(); /* * Process the request body even if there is nothing to be done. * @@ -852,6 +855,9 @@ int Transaction::processRequestBody() { if (m.init(&error) == true) { m.process(m_requestBody.str(), &error, m_variableOffset); } + // if CT *IS* multipart, no_files value will the m.m_reqbody_no_files_length + // calculated in request_body_processor/multipart.cc + reqbodyNoFilesLength = m.m_reqbody_no_files_length; m.multipart_complete(&error); } if (error.empty() == false) { @@ -907,6 +913,16 @@ int Transaction::processRequestBody() { } } + // check the SecRequestBodyNoFilsLimit criteria + if (this->m_rules->m_requestBodyNoFilesLimit.m_value > 0 + && this->m_rules->m_requestBodyNoFilesLimit.m_value < reqbodyNoFilesLength) { + m_variableInboundDataError.set("1", m_variableOffset); + ms_dbg(5, "Request body no files is bigger than the maximum expected."); + m_variableReqbodyError.set("1", m_variableOffset); + m_variableReqbodyErrorMsg.set("Request body no files is bigger than the maximum expected.", + m_variableOffset); + } + /** * FIXME: This variable should be calculated on demand, it is * computationally intensive. diff --git a/test/test-cases/regression/config-body_nofiles_limits.json b/test/test-cases/regression/config-body_nofiles_limits.json new file mode 100644 index 0000000000..853eb807ee --- /dev/null +++ b/test/test-cases/regression/config-body_nofiles_limits.json @@ -0,0 +1,508 @@ +[ + { + "enabled":1, + "version_min":300000, + "title":"SecRequestBodyNoFilesLimit 10/01 - True positive form-data", + "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": "9", + "Content-Type": "application/x-www-form-urlencoded" + }, + "uri":"/", + "method":"POST", + "body": [ + "login=foo" + ] + }, + "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":400 + }, + "rules":[ + "SecRuleEngine On", + "SecRequestBodyAccess On", + "SecRequestBodyNoFilesLimit 5", + "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 10/02 - True negative form-data", + "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": "9", + "Content-Type": "application/x-www-form-urlencoded" + }, + "uri":"/", + "method":"POST", + "body": [ + "login=foo" + ] + }, + "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 10", + "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 10/03 - True positive multipart", + "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": "300", + "Content-Type": "multipart/form-data; boundary=------------------------305d3b6fb72cf618" + }, + "uri":"/", + "method":"POST", + "body": [ + "--------------------------305d3b6fb72cf618\r", + "Content-Disposition: form-data; name=\"token\"\r", + "\r", + "blah\r", + "--------------------------305d3b6fb72cf618\r", + "Content-Disposition: form-data; name=\"attachments\"; filename=\"simple_empty.txt\"\r", + "Content-Type: text/plain\r", + "\r", + "a\r", + "--------------------------305d3b6fb72cf618--\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":400 + }, + "rules":[ + "SecRuleEngine On", + "SecRequestBodyAccess On", + "SecRequestBodyNoFilesLimit 5", + "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 10/04 - True negative multipart", + "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": "300", + "Content-Type": "multipart/form-data; boundary=------------------------305d3b6fb72cf618" + }, + "uri":"/", + "method":"POST", + "body": [ + "--------------------------305d3b6fb72cf618\r", + "Content-Disposition: form-data; name=\"token\"\r", + "\r", + "blah\r", + "--------------------------305d3b6fb72cf618\r", + "Content-Disposition: form-data; name=\"attachments\"; filename=\"simple_empty.txt\"\r", + "Content-Type: text/plain\r", + "\r", + "a\r", + "--------------------------305d3b6fb72cf618--\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":200 + }, + "rules":[ + "SecRuleEngine On", + "SecRequestBodyAccess On", + "SecRequestBodyNoFilesLimit 161", + "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 10/05 - True positive multipart full req", + "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": "300", + "Content-Type": "multipart/form-data; boundary=------------------------305d3b6fb72cf618" + }, + "uri":"/", + "method":"POST", + "body": [ + "--------------------------305d3b6fb72cf618\r", + "Content-Disposition: form-data; name=\"token\"\r", + "\r", + "blah\r", + "--------------------------305d3b6fb72cf618\r", + "Content-Disposition: form-data; name=\"attachments\"; filename=\"simple_empty.txt\"\r", + "Content-Type: text/plain\r", + "\r", + "a\r", + "--------------------------305d3b6fb72cf618--\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", + "SecRequestBodyAccess On", + "SecRequestBodyLimitAction Reject", + "SecRequestBodyLimit 299", + "SecRequestBodyNoFilesLimit 161", + "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 10/06 - True negative multipart full req", + "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": "300", + "Content-Type": "multipart/form-data; boundary=------------------------305d3b6fb72cf618" + }, + "uri":"/", + "method":"POST", + "body": [ + "--------------------------305d3b6fb72cf618\r", + "Content-Disposition: form-data; name=\"token\"\r", + "\r", + "blah\r", + "--------------------------305d3b6fb72cf618\r", + "Content-Disposition: form-data; name=\"attachments\"; filename=\"simple_empty.txt\"\r", + "Content-Type: text/plain\r", + "\r", + "a\r", + "--------------------------305d3b6fb72cf618--\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":200 + }, + "rules":[ + "SecRuleEngine On", + "SecRequestBodyAccess On", + "SecRequestBodyLimitAction Reject", + "SecRequestBodyLimit 300", + "SecRequestBodyNoFilesLimit 161", + "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 10/07 - True positive JSON", + "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": "17", + "Content-Type": "application/json" + }, + "uri":"/", + "method":"POST", + "body": [ + "{\"login\": \"foo\"}" + ] + }, + "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":400 + }, + "rules":[ + "SecRuleEngine On", + "SecRequestBodyAccess On", + "SecRequestBodyNoFilesLimit 5", + "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 10/08 - True negative JSON", + "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": "17", + "Content-Type": "application/x-www-form-urlencoded" + }, + "uri":"/", + "method":"POST", + "body": [ + "{\"login\": \"foo\"}" + ] + }, + "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 17", + "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 10/09 - True positive XML", + "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": "95", + "Content-Type": "application/xml" + }, + "uri":"/", + "method":"POST", + "body": [ + "\r", + "\r", + " data\r", + "\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":400 + }, + "rules":[ + "SecRuleEngine On", + "SecRequestBodyAccess On", + "SecRequestBodyNoFilesLimit 5", + "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 10/10 - True negative XML", + "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": "95", + "Content-Type": "application/xml" + }, + "uri":"/", + "method":"POST", + "body": [ + "\r", + "\r", + " data\r", + "\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":200 + }, + "rules":[ + "SecRuleEngine On", + "SecRequestBodyAccess On", + "SecRequestBodyNoFilesLimit 95", + "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\"" + ] + } +]