diff --git a/Makefile.am b/Makefile.am index 77de1f65..1c93343b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -216,3 +216,4 @@ TESTS+=test/test-cases/secrules-language-tests/operators/detectXSS.json TESTS+=test/test-cases/secrules-language-tests/operators/eq.json TESTS+=test/test-cases/regression/variable-REQBODY_PROCESSOR.json TESTS+=test/test-cases/regression/variable-REQBODY_PROCESSOR_ERROR.json +TESTS+=test/test-cases/regression/variable-URLENCODED_ERROR.json diff --git a/src/actions/transformations/url_decode.cc b/src/actions/transformations/url_decode.cc index 8efece4a..d8977c14 100644 --- a/src/actions/transformations/url_decode.cc +++ b/src/actions/transformations/url_decode.cc @@ -31,64 +31,6 @@ namespace actions { namespace transformations { -int UrlDecode::urldecode_nonstrict_inplace(unsigned char *input, - uint64_t input_len, int *invalid_count, int *changed) { - unsigned char *d = (unsigned char *)input; - uint64_t i, count; - - *changed = 0; - - if (input == NULL) { - return -1; - } - - i = count = 0; - while (i < input_len) { - if (input[i] == '%') { - /* Character is a percent sign. */ - - /* Are there enough bytes available? */ - if (i + 2 < input_len) { - char c1 = input[i + 1]; - char c2 = input[i + 2]; - if (VALID_HEX(c1) && VALID_HEX(c2)) { - uint64_t uni = x2c(&input[i + 1]); - - *d++ = (wchar_t)uni; - count++; - i += 3; - *changed = 1; - } else { - /* Not a valid encoding, skip this % */ - *d++ = input[i++]; - count++; - (*invalid_count)++; - } - } else { - /* Not enough bytes available, copy the raw bytes. */ - *d++ = input[i++]; - count++; - (*invalid_count)++; - } - } else { - /* Character is not a percent sign. */ - if (input[i] == '+') { - *d++ = ' '; - *changed = 1; - } else { - *d++ = input[i]; - } - count++; - i++; - } - } - - *d = '\0'; - - return count; -} - - UrlDecode::UrlDecode(std::string action) : Transformation(action) { this->action_kind = 1; diff --git a/src/actions/transformations/url_decode.h b/src/actions/transformations/url_decode.h index 5e72b79e..66477fde 100644 --- a/src/actions/transformations/url_decode.h +++ b/src/actions/transformations/url_decode.h @@ -53,9 +53,6 @@ class UrlDecode : public Transformation { explicit UrlDecode(std::string action); std::string evaluate(std::string exp, Transaction *transaction) override; - - int urldecode_nonstrict_inplace(unsigned char *input, uint64_t input_len, - int *invalid_count, int *changed); }; } // namespace transformations diff --git a/src/parser/seclang-scanner.ll b/src/parser/seclang-scanner.ll index f9e2c44b..e82a6d50 100755 --- a/src/parser/seclang-scanner.ll +++ b/src/parser/seclang-scanner.ll @@ -149,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_PROCESSOR_ERROR_MSG|REQBODY_PROCESSOR_ERROR|REQBODY_PROCESSOR|REQBODY_ERROR_MSG|REQBODY_ERROR|MULTIPART_FILE_LIMIT_EXCEEDED|MULTIPART_INVALID_QUOTING|MULTIPART_HEADER_FOLDING|MULTIPART_INVALID_HEADER_FOLDING|MULTIPART_STRICT_ERROR|MULTIPART_UNMATCHED_BOUNDARY|REMOTE_ADDR|REQUEST_LINE) +VARIABLENOCOLON (?i:URLENCODED_ERROR|REQBODY_PROCESSOR_ERROR_MSG|REQBODY_PROCESSOR_ERROR|REQBODY_PROCESSOR|REQBODY_ERROR_MSG|REQBODY_ERROR|MULTIPART_FILE_LIMIT_EXCEEDED|MULTIPART_INVALID_QUOTING|MULTIPART_HEADER_FOLDING|MULTIPART_INVALID_HEADER_FOLDING|MULTIPART_STRICT_ERROR|MULTIPART_UNMATCHED_BOUNDARY|REMOTE_ADDR|REQUEST_LINE) CONFIG_VALUE_ON (?i:On) diff --git a/src/transaction.cc b/src/transaction.cc index 456dad62..ad386d50 100644 --- a/src/transaction.cc +++ b/src/transaction.cc @@ -140,6 +140,7 @@ Transaction::Transaction(ModSecurity *ms, Rules *rules, void *logCbData) this->m_responseContentType = m_collections.resolveFirst( "RESPONSE_CONTENT_TYPE"); + m_collections.storeOrUpdateFirst("URLENCODED_ERROR", "0"); #ifndef NO_LOGS this->debug(4, "Initialising transaction"); @@ -239,6 +240,10 @@ bool Transaction::extractArguments(const std::string &orig, for (std::string t : key_value_sets) { char sep2 = '='; int i = 0; + size_t key_s = 0; + size_t value_s = 0; + int invalid = 0; + int changed = 0; std::string key; std::string value; @@ -254,9 +259,29 @@ bool Transaction::extractArguments(const std::string &orig, i++; } - key = uri_decode(key); - value = uri_decode(value); - addArgument(orig, key, value); + key_s = (key.length() + 1); + value_s = (value.length() + 1); + unsigned char *key_c = (unsigned char *) malloc(sizeof(char) * key_s); + unsigned char *value_c = (unsigned char *) malloc(sizeof(char) * value_s); + + memset(key_c, '\0', sizeof(char) * key_s); + memset(value_c, '\0', sizeof(char) * value_s); + + memcpy(key_c, key.c_str(), key_s); + memcpy(value_c, value.c_str(), value_s); + + key_s = urldecode_nonstrict_inplace(key_c, key_s, &invalid, &changed); + value_s = urldecode_nonstrict_inplace(value_c, value_s, &invalid, &changed); + + if (invalid) { + m_collections.storeOrUpdateFirst("URLENCODED_ERROR", "1"); + } + + addArgument(orig, std::string((char *)key_c, key_s-1), + std::string((char *)value_c, value_s-1)); + + free(key_c); + free(value_c); } } diff --git a/src/utils.cc b/src/utils.cc index e70d90d4..3c3e89da 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -73,6 +73,67 @@ std::string phase_name(int x) { } +int urldecode_nonstrict_inplace(unsigned char *input, + uint64_t input_len, int *invalid_count, int *changed) { + unsigned char *d = (unsigned char *)input; + uint64_t i, count; + + *changed = 0; + + if (input == NULL) { + return -1; + } + + i = count = 0; + while (i < input_len) { + if (input[i] == '%') { + /* Character is a percent sign. */ + + /* Are there enough bytes available? */ + if (i + 2 < input_len) { + char c1 = input[i + 1]; + char c2 = input[i + 2]; + if (VALID_HEX(c1) && VALID_HEX(c2)) { + uint64_t uni = x2c(&input[i + 1]); + + *d++ = (wchar_t)uni; + count++; + i += 3; + *changed = 1; + } else { + /* Not a valid encoding, skip this % */ + *d++ = input[i++]; + count++; + (*invalid_count)++; + } + } else { + /* Not enough bytes available, copy the raw bytes. */ + *d++ = input[i++]; + count++; + (*invalid_count)++; + } + } else { + /* Character is not a percent sign. */ + if (input[i] == '+') { + *d++ = ' '; + *changed = 1; + } else { + *d++ = input[i]; + } + count++; + i++; + } + } + +#if 0 + *d = '\0'; +#endif + + return count; +} + + + std::vector split(std::string str, char delimiter) { std::vector internal; std::stringstream ss(str); // Turn the string into a stream. diff --git a/src/utils.h b/src/utils.h index 3178cd63..beac334f 100644 --- a/src/utils.h +++ b/src/utils.h @@ -31,6 +31,8 @@ namespace modsecurity { std::vector split(std::string str, char delimiter); + int urldecode_nonstrict_inplace(unsigned char *input, + uint64_t input_len, int *invalid_count, int *changed); double random_number(const double from, const double to); double generate_transaction_unique_id(); std::string ascTime(time_t *t); diff --git a/test/test-cases/regression/variable-ARGS_COMBINED_SIZE.json b/test/test-cases/regression/variable-ARGS_COMBINED_SIZE.json index abb003df..a1db2aa2 100644 --- a/test/test-cases/regression/variable-ARGS_COMBINED_SIZE.json +++ b/test/test-cases/regression/variable-ARGS_COMBINED_SIZE.json @@ -118,7 +118,7 @@ ] }, "expected":{ - "debug_log":"Target value: \"24." + "debug_log":"Target value: \"25." }, "rules":[ "SecRuleEngine On", @@ -144,7 +144,7 @@ "Host":"localhost", "User-Agent":"curl/7.38.0", "Accept":"*/*", - "Content-Length": "27", + "Content-Length": "28", "Content-Type": "application/x-www-form-urlencoded" }, "uri":"/", @@ -164,7 +164,7 @@ ] }, "expected":{ - "debug_log":"Target value: \"27." + "debug_log":"Target value: \"28." }, "rules":[ "SecRuleEngine On", @@ -210,7 +210,7 @@ ] }, "expected":{ - "debug_log":"Target value: \"15." + "debug_log":"Target value: \"16." }, "rules":[ "SecRuleEngine On", diff --git a/test/test-cases/regression/variable-URLENCODED_ERROR.json b/test/test-cases/regression/variable-URLENCODED_ERROR.json new file mode 100644 index 00000000..15c61e99 --- /dev/null +++ b/test/test-cases/regression/variable-URLENCODED_ERROR.json @@ -0,0 +1,309 @@ +[ + { + "enabled":1, + "version_min":300000, + "title":"Testing Variables :: URLENCODED_ERROR - GET (1/7)", + "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%2", + "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":"Target value: \"1\" \\(Variable: URLENCODED_ERROR\\)" + }, + "rules":[ + "SecRuleEngine On", + "SecDebugLog \/tmp\/modsec_debug.log", + "SecDebugLogLevel 9", + "SecRule URLENCODED_ERROR \"@gt 10 \" \"id:1,pass\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"Testing Variables :: URLENCODED_ERROR - GET (2/7)", + "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&a=b%2a", + "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":"Target value: \"0\" \\(Variable: URLENCODED_ERROR\\)" + }, + "rules":[ + "SecRuleEngine On", + "SecDebugLog \/tmp\/modsec_debug.log", + "SecDebugLogLevel 9", + "SecRule URLENCODED_ERROR \"@gt 10 \" \"id:1,pass\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"Testing Variables :: URLENCODED_ERROR - POST (3/7)", + "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": "27", + "Content-Type": "application/x-www-form-urlencoded" + }, + "uri":"/", + "method":"POST", + "body": [ + "param1=value12%¶m2=value2%2" + ] + }, + "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: \"1\" \\(Variable: URLENCODED_ERROR\\)" + }, + "rules":[ + "SecRuleEngine On", + "SecDebugLog \/tmp\/modsec_debug.log", + "SecDebugLogLevel 9", + "SecRule URLENCODED_ERROR \"@gt 10 \" \"id:1,phase:3,pass\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"Testing Variables :: URLENCODED_ERROR - POST (4/7)", + "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": "28", + "Content-Type": "application/x-www-form-urlencoded" + }, + "uri":"/", + "method":"POST", + "body": [ + "param1=value1¶m2=value2&a=b5%2a\n" + ] + }, + "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: \"0\" \\(Variable: URLENCODED_ERROR\\)" + }, + "rules":[ + "SecRuleEngine On", + "SecDebugLog \/tmp\/modsec_debug.log", + "SecDebugLogLevel 9", + "SecRule URLENCODED_ERROR \"@gt 10 \" \"id:1,phase:3,pass,t:trim\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"Testing Variables :: URLENCODED_ERROR - POST (5/7)", + "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": "27", + "Content-Type": "application/x-www-form-urlencoded" + }, + "uri":"/", + "method":"POST", + "body": [ + "a=%EC%A7%84%20%EB%A7%88%EC%9D%BC%20%EB%A6%AC" + ] + }, + "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: \"0\" \\(Variable: URLENCODED_ERROR\\)" + }, + "rules":[ + "SecRuleEngine On", + "SecDebugLog \/tmp\/modsec_debug.log", + "SecDebugLogLevel 9", + "SecRule URLENCODED_ERROR \"@gt 10 \" \"id:1,phase:3,pass,t:trim\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"Testing Variables :: URLENCODED_ERROR - GET (6/7)", + "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": "27", + "Content-Type": "application/x-www-form-urlencoded" + }, + "uri":"/?z=%EC%A7%84%20%EB%A7%88%EC%9D%BC%20%EB%A6%A%AC", + "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":"Target value: \"1\" \\(Variable: URLENCODED_ERROR\\)" + }, + "rules":[ + "SecRuleEngine On", + "SecDebugLog \/tmp\/modsec_debug.log", + "SecDebugLogLevel 9", + "SecRule URLENCODED_ERROR \"@gt 10 \" \"id:1,phase:3,pass,t:trim\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"Testing Variables :: URLENCODED_ERROR - GET (7/7)", + "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": "27", + "Content-Type": "application/x-www-form-urlencoded" + }, + "uri":"/?z=진 마일 리", + "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":"Target value: \"0\" \\(Variable: URLENCODED_ERROR\\)" + }, + "rules":[ + "SecRuleEngine On", + "SecDebugLog \/tmp\/modsec_debug.log", + "SecDebugLogLevel 9", + "SecRule URLENCODED_ERROR \"@gt 10 \" \"id:1,phase:3,pass,t:trim\"" + ] + } +] +