diff --git a/src/actions/set_var.cc b/src/actions/set_var.cc index be60027c..dc032a19 100644 --- a/src/actions/set_var.cc +++ b/src/actions/set_var.cc @@ -20,6 +20,7 @@ #include "modsecurity/assay.h" #include "src/rule.h" +#include "src/macro_expansion.h" namespace ModSecurity { namespace actions { @@ -94,6 +95,7 @@ void SetVar::dump() { bool SetVar::evaluate(Rule *rule, Assay *assay) { std::string targetValue; + variableName = MacroExpansion::expand(variableName, assay); int value = 0; try { std::string *resolvedValue = @@ -111,14 +113,16 @@ bool SetVar::evaluate(Rule *rule, Assay *assay) { try { pre = stoi(predicate); } catch (...) { - /* perform a macro expansion. */ - pre = 0; + try { + pre = stoi(MacroExpansion::expand(predicate, assay)); + } catch (...) { + pre = 0; + } } switch (operation) { case setOperation: - /* perform a macro expansion. */ - targetValue = predicate; + targetValue = MacroExpansion::expand(predicate, assay); break; case sumAndSetOperation: targetValue = std::to_string(value + pre); diff --git a/src/assay.cc b/src/assay.cc index 347a08bb..921506d4 100644 --- a/src/assay.cc +++ b/src/assay.cc @@ -1374,8 +1374,9 @@ std::string* Assay::resolve_variable_first(std::string var) { std::string* Assay::resolve_variable_first(const std::string collectionName, std::string var) { for (auto &a : collections) { - if (a.first == collectionName) { - auto range = a.second->equal_range(collectionName + ":" + var); + if (tolower(a.first) == tolower(collectionName)) { + auto range = a.second->equal_range(toupper(collectionName) + + ":" + var); for (auto it = range.first; it != range.second; ++it) { return &it->second; } @@ -1391,8 +1392,8 @@ void Assay::setCollection(const std::string& collectionName, ModSecurityStringVariables *collection; try { - collection = collections.at(collectionName); - collection->storeOrUpdateVariable(collectionName + ":" + collection = collections.at(toupper(collectionName)); + collection->storeOrUpdateVariable(toupper(collectionName) + ":" + variableName, targetValue); } catch (...) { debug(9, "don't know any collection named: " diff --git a/src/macro_expansion.cc b/src/macro_expansion.cc index 2e0222c8..aff491db 100644 --- a/src/macro_expansion.cc +++ b/src/macro_expansion.cc @@ -14,10 +14,43 @@ */ #include "src/macro_expansion.h" +#include "modsecurity/assay.h" namespace ModSecurity { -MacroExpansion::MacroExpansion() { +MacroExpansion::MacroExpansion() { } + +std::string MacroExpansion::expand(const std::string& input, Assay *assay) { + std::string res(input); + + size_t pos = res.find("%{"); + while (pos != std::string::npos) { + size_t start = pos; + size_t end = res.find("}%"); + if (end == std::string::npos) { + return res; + } + std::string variable(res, start + 2, end - (start + 2)); + std::string *variableValue; + size_t collection = variable.find("."); + if (collection == std::string::npos) { + variableValue = assay->resolve_variable_first(variable); + } else { + std::string col = std::string(variable, 0, collection); + std::string var = std::string(variable, collection + 1, + variable.length() - (collection + 1)); + variableValue = assay->resolve_variable_first(col, var); + } + + res.erase(start, end + 2); + if (variableValue != NULL) { + res.insert(start, *variableValue); + } + + pos = res.find("%{"); + } + + return res; } } // namespace ModSecurity diff --git a/src/macro_expansion.h b/src/macro_expansion.h index 1573d46e..ae4163dd 100644 --- a/src/macro_expansion.h +++ b/src/macro_expansion.h @@ -19,6 +19,7 @@ #include #include "modsecurity/modsecurity.h" +#include "modsecurity/assay.h" #ifndef SRC_MACRO_EXPANSION_H_ #define SRC_MACRO_EXPANSION_H_ @@ -29,6 +30,8 @@ namespace ModSecurity { class MacroExpansion { public: MacroExpansion(); + + static std::string expand(const std::string& input, Assay *assay); }; diff --git a/src/utils.cc b/src/utils.cc index f27d8ceb..84e7a194 100644 --- a/src/utils.cc +++ b/src/utils.cc @@ -112,6 +112,16 @@ std::string tolower(std::string str) { return value; } +std::string toupper(std::string str) { + std::locale loc; + std::string value; + + for (std::string::size_type i=0; i < str.length(); ++i) { + value.assign(value + std::toupper(str[i], loc)); + } + + return value; +} const char SAFE[256] = { /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ diff --git a/src/utils.h b/src/utils.h index 3f3dccee..b2f2125d 100644 --- a/src/utils.h +++ b/src/utils.h @@ -32,6 +32,7 @@ namespace ModSecurity { void chomp(std::string *str); std::string uri_decode(const std::string & sSrc); std::string tolower(std::string str); + std::string toupper(std::string str); double cpu_seconds(void); int js_decode_nonstrict_inplace(unsigned char *input, int64_t input_len); static unsigned char x2c(unsigned char *what); diff --git a/test/test-cases/regression/collection-tx-with-macro.json b/test/test-cases/regression/collection-tx-with-macro.json new file mode 100644 index 00000000..425ad320 --- /dev/null +++ b/test/test-cases/regression/collection-tx-with-macro.json @@ -0,0 +1,245 @@ +[ + { + "enabled":1, + "version_min":300000, + "version_max":0, + "title":"Testing collection :: TX (with macro) (1/4)", + "client":{ + "ip":"200.249.12.31", + "port":2313 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "User-Agent":"Mozilla\/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko\/20091102 Firefox\/3.5.5 (.NET CLR 3.5.30729)", + "Accept":"text\/html,application\/xhtml+xml,application\/xml;q=0.9,*\/*;q=0.8", + "Accept-Language":"en-us,en;q=0.5", + "Accept-Encoding":"gzip,deflate", + "Accept-Charset":"ISO-8859-1,utf-8;q=0.7,*;q=0.7", + "Keep-Alive":"300", + "Connection":"keep-alive", + "Cookie":"PHPSESSID=rAAAAAAA2t5uvjq435r4q7ib3vtdjq120", + "Pragma":"no-cache", + "Cache-Control":"no-cache" + }, + "uri":"\/test.pl?param1= test ¶m2=test2", + "protocol":"GET", + "http_version":1.1, + "body":"" + }, + "response":{ + "headers":{ + "Content-Type":"text\/xml; charset=utf-8\n\r", + "Content-Length":"length\n\r" + }, + "body":[ + "\n\r", + "\n\r", + " \n\r", + " \n\r", + " string<\/EnlightenResult>\n\r", + " <\/EnlightenResponse>\n\r", + " <\/soap:Body>\n\r", + "<\/soap:Envelope>\n\r" + ] + }, + "expected":{ + "audit_log":"", + "debug_log":"Target value: \"PHPSESSID=rAAAAAAA2t5uvjq435r4q7ib3vtdjq120\" \\(Variable: TX:something\\)", + "error_log":"" + }, + "rules":[ + "SecRuleEngine On", + "SecDebugLog \/tmp\/modsec_debug.log", + "SecDebugLogLevel 9", + "SecRule REQUEST_HEADERS \"@contains PHPSESSID\" \"t:lowercase,t:none,setvar:TX.something=%{REQUEST_HEADERS:Cookie}%\"", + "SecRule TX \"@contains to_test\" \"t:lowercase,t:none\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "version_max":0, + "title":"Testing collection :: TX (with macro) (2/4)", + "client":{ + "ip":"200.249.12.31", + "port":2313 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "User-Agent":"Mozilla\/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko\/20091102 Firefox\/3.5.5 (.NET CLR 3.5.30729)", + "Accept":"text\/html,application\/xhtml+xml,application\/xml;q=0.9,*\/*;q=0.8", + "Accept-Language":"en-us,en;q=0.5", + "Accept-Encoding":"gzip,deflate", + "Accept-Charset":"ISO-8859-1,utf-8;q=0.7,*;q=0.7", + "Keep-Alive":"300", + "Connection":"keep-alive", + "Cookie":"PHPSESSID=rAAAAAAA2t5uvjq435r4q7ib3vtdjq120", + "Pragma":"no-cache", + "Cache-Control":"no-cache" + }, + "uri":"\/test.pl?param1= test ¶m2=test2", + "protocol":"GET", + "http_version":1.1, + "body":"" + }, + "response":{ + "headers":{ + "Content-Type":"text\/xml; charset=utf-8\n\r", + "Content-Length":"length\n\r" + }, + "body":[ + "\n\r", + "\n\r", + " \n\r", + " \n\r", + " string<\/EnlightenResult>\n\r", + " <\/EnlightenResponse>\n\r", + " <\/soap:Body>\n\r", + "<\/soap:Envelope>\n\r" + ] + }, + "expected":{ + "audit_log":"", + "debug_log":"Target value: \"1\" \\(Variable: TX:somethingPHPSESSID=rAAAAAAA2t5uvjq435r4q7ib3vtdjq120\\)", + "error_log":"" + }, + "rules":[ + "SecRuleEngine On", + "SecDebugLog \/tmp\/modsec_debug.log", + "SecDebugLogLevel 9", + "SecRule REQUEST_HEADERS \"@contains PHPSESSID\" \"t:lowercase,t:none,setvar:TX.something%{REQUEST_HEADERS:Cookie}%\"", + "SecRule TX \"@contains to_test\" \"t:lowercase,t:none\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "version_max":0, + "title":"Testing collection :: TX (with macro) (3/4)", + "client":{ + "ip":"200.249.12.31", + "port":2313 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "User-Agent":"Mozilla\/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko\/20091102 Firefox\/3.5.5 (.NET CLR 3.5.30729)", + "Accept":"text\/html,application\/xhtml+xml,application\/xml;q=0.9,*\/*;q=0.8", + "Accept-Language":"en-us,en;q=0.5", + "Accept-Encoding":"gzip,deflate", + "Accept-Charset":"ISO-8859-1,utf-8;q=0.7,*;q=0.7", + "Keep-Alive":"300", + "Connection":"keep-alive", + "Cookie":"PHPSESSID=rAAAAAAA2t5uvjq435r4q7ib3vtdjq120", + "Pragma":"no-cache", + "Cache-Control":"no-cache" + }, + "uri":"\/test.pl?param1= test ¶m2=test2", + "protocol":"GET", + "http_version":1.1, + "body":"" + }, + "response":{ + "headers":{ + "Content-Type":"text\/xml; charset=utf-8\n\r", + "Content-Length":"length\n\r" + }, + "body":[ + "\n\r", + "\n\r", + " \n\r", + " \n\r", + " string<\/EnlightenResult>\n\r", + " <\/EnlightenResponse>\n\r", + " <\/soap:Body>\n\r", + "<\/soap:Envelope>\n\r" + ] + }, + "expected":{ + "audit_log":"", + "debug_log":"Target value: \"310\" \\(Variable: TX:something\\)", + "error_log":"" + }, + "rules":[ + "SecRuleEngine On", + "SecDebugLog \/tmp\/modsec_debug.log", + "SecDebugLogLevel 9", + "SecRule REQUEST_HEADERS \"@contains PHPSESSID\" \"t:lowercase,t:none,setvar:TX.something=%{REQUEST_HEADERS:Keep-Alive}%\"", + "SecRule REQUEST_HEADERS \"@contains PHPSESSID\" \"t:lowercase,t:none,setvar:TX.something=+10\"", + "SecRule TX \"@contains to_test\" \"t:lowercase,t:none\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "version_max":0, + "title":"Testing collection :: TX (with macro) (4/4)", + "client":{ + "ip":"200.249.12.31", + "port":2313 + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "request":{ + "headers":{ + "User-Agent":"Mozilla\/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko\/20091102 Firefox\/3.5.5 (.NET CLR 3.5.30729)", + "Accept":"text\/html,application\/xhtml+xml,application\/xml;q=0.9,*\/*;q=0.8", + "Accept-Language":"en-us,en;q=0.5", + "Accept-Encoding":"gzip,deflate", + "Accept-Charset":"ISO-8859-1,utf-8;q=0.7,*;q=0.7", + "Keep-Alive":"300", + "Connection":"keep-alive", + "Cookie":"PHPSESSID=rAAAAAAA2t5uvjq435r4q7ib3vtdjq120", + "Pragma":"no-cache", + "Cache-Control":"no-cache" + }, + "uri":"\/test.pl?param1= test ¶m2=test2", + "protocol":"GET", + "http_version":1.1, + "body":"" + }, + "response":{ + "headers":{ + "Content-Type":"text\/xml; charset=utf-8\n\r", + "Content-Length":"length\n\r" + }, + "body":[ + "\n\r", + "\n\r", + " \n\r", + " \n\r", + " string<\/EnlightenResult>\n\r", + " <\/EnlightenResponse>\n\r", + " <\/soap:Body>\n\r", + "<\/soap:Envelope>\n\r" + ] + }, + "expected":{ + "audit_log":"", + "debug_log":"Target value: \"5\" \\(Variable: TX:something_else\\)", + "error_log":"" + }, + "rules":[ + "SecRuleEngine On", + "SecDebugLog \/tmp\/modsec_debug.log", + "SecDebugLogLevel 9", + "SecRule REQUEST_HEADERS \"@contains PHPSESSID\" \"t:lowercase,t:none,setvar:TX.something=+10\"", + "SecRule REQUEST_HEADERS \"@contains PHPSESSID\" \"t:lowercase,t:none,setvar:TX.something_else=%{tx.something}%\"", + "SecRule REQUEST_HEADERS \"@contains PHPSESSID\" \"t:lowercase,t:none,setvar:TX.something_else=-5\"", + "SecRule TX:something_else \"@contains to_test\" \"t:lowercase,t:none\"" + ] + } +] \ No newline at end of file