diff --git a/CHANGES b/CHANGES index f43692ab..c7f7bdd4 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,8 @@ v3.x.y - YYYY-MMM-DD (to be released) ------------------------------------- + - Implement id ranges for ctl:ruleRemoveTargetById + [#2110 - @j0k2r, @martinhsv] - Removed unnecessary while processing the transformations. [#2368 - @WGH-, @zimmerle] - auditlog: Computes whether or not to save while loading the rules. diff --git a/headers/modsecurity/transaction.h b/headers/modsecurity/transaction.h index 06a84494..439b9f98 100644 --- a/headers/modsecurity/transaction.h +++ b/headers/modsecurity/transaction.h @@ -545,6 +545,7 @@ class Transaction : public TransactionAnchoredVariables, public TransactionSecMa * */ std::list< std::pair > m_ruleRemoveTargetById; + std::list< std::pair, std::string> > m_ruleRemoveTargetByIdRange; /** * diff --git a/src/actions/ctl/rule_remove_target_by_id.cc b/src/actions/ctl/rule_remove_target_by_id.cc index def0ad7d..26c89e47 100644 --- a/src/actions/ctl/rule_remove_target_by_id.cc +++ b/src/actions/ctl/rule_remove_target_by_id.cc @@ -39,12 +39,36 @@ bool RuleRemoveTargetById::init(std::string *error) { return false; } - try { - m_id = std::stoi(param[0]); - } catch(...) { - error->assign("Not able to convert '" + param[0] + - "' into a number"); - return false; + size_t dash = param[0].find('-'); + if (dash != std::string::npos) { + std::string n1s = std::string(param[0], 0, dash); + std::string n2s = std::string(param[0], dash + 1, param[0].size() - (dash + 1)); + int n1n = 0; + int n2n = 0; + try { + n1n = std::stoi(n1s); + } catch(...) { + error->assign("Not a number: " + n1s); + return false; + } + try { + n2n = std::stoi(n2s); + } catch(...) { + error->assign("Not a number: " + n2s); + return false; + } + if (n1n > n2n) { + error->assign("Invalid range: " + param[0]); + } + m_id = n1n; + m_id_end_of_range = n2n; + } else { + try { + m_id = std::stoi(param[0]); + } catch(...) { + error->assign("Not able to convert '" + param[0] + "' into a number"); + return false; + } } m_target = param[1]; @@ -54,8 +78,12 @@ bool RuleRemoveTargetById::init(std::string *error) { bool RuleRemoveTargetById::execute(Transaction *transaction) const noexcept { - transaction->m_ruleRemoveTargetById.push_back( - std::make_pair(m_id, m_target)); + if (m_id_end_of_range == 0) { + transaction->m_ruleRemoveTargetById.push_back(std::make_pair(m_id, m_target)); + } else { + std::pair id_range = std::make_pair(m_id, m_id_end_of_range); + transaction->m_ruleRemoveTargetByIdRange.push_back(std::make_pair(id_range, m_target)); + } return true; } diff --git a/src/actions/ctl/rule_remove_target_by_id.h b/src/actions/ctl/rule_remove_target_by_id.h index 7dfec0f1..6ff98dfc 100644 --- a/src/actions/ctl/rule_remove_target_by_id.h +++ b/src/actions/ctl/rule_remove_target_by_id.h @@ -35,6 +35,7 @@ class RuleRemoveTargetById : public ActionWithExecution { explicit RuleRemoveTargetById(const std::string &action) : Action(action), m_id(0), + m_id_end_of_range(0), m_target("") { } @@ -44,6 +45,7 @@ class RuleRemoveTargetById : public ActionWithExecution { private: int m_id; + int m_id_end_of_range; std::string m_target; }; diff --git a/src/rule_with_operator.cc b/src/rule_with_operator.cc index 9a097779..9bef6d52 100644 --- a/src/rule_with_operator.cc +++ b/src/rule_with_operator.cc @@ -194,6 +194,14 @@ inline void RuleWithOperator::getFinalVars(variables::Variables *vars, }) != trans->m_ruleRemoveTargetById.end()) { continue; } + if (std::find_if(trans->m_ruleRemoveTargetByIdRange.begin(), + trans->m_ruleRemoveTargetByIdRange.end(), + [&, variable, this](std::pair, std::string> &m) -> bool { + return (m.first.first <= getId() && m.first.second >= getId() + && m.second == *variable->getVariableKeyWithCollection()); + }) != trans->m_ruleRemoveTargetByIdRange.end()) { + continue; + } if (std::find_if(trans->m_ruleRemoveTargetByTag.begin(), trans->m_ruleRemoveTargetByTag.end(), [&, variable, trans, this]( @@ -289,6 +297,17 @@ bool RuleWithOperator::evaluate(Transaction *trans) const { continue; } + if (exclusion.contains(v) || + std::find_if(trans->m_ruleRemoveTargetByIdRange.begin(), + trans->m_ruleRemoveTargetByIdRange.end(), + [&, v, this](std::pair, std::string> &m) -> bool { + return (m.first.first <= getId() && m.first.second >= getId() + && m.second == v->getName()); + }) != trans->m_ruleRemoveTargetByIdRange.end() + ) { + continue; + } + if (exclusion.contains(v) || std::find_if(trans->m_ruleRemoveTargetByTag.begin(), trans->m_ruleRemoveTargetByTag.end(), diff --git a/test/test-cases/regression/action-ctl_rule_remove_target_by_id.json b/test/test-cases/regression/action-ctl_rule_remove_target_by_id.json index 142ac623..7c95fbc5 100644 --- a/test/test-cases/regression/action-ctl_rule_remove_target_by_id.json +++ b/test/test-cases/regression/action-ctl_rule_remove_target_by_id.json @@ -95,5 +95,170 @@ "SecRule REQUEST_FILENAME \"@endsWith /wp-login.php\" \"id:9002100,phase:2,t:none,nolog,pass,ctl:ruleRemoveTargetById=1;ARGS\"", "SecRule ARGS \"@contains lhebs\" \"id:1,phase:3,t:none,status:202,block,deny,tag:'CRS'\"" ] + }, + { + "enabled":1, + "version_min":300000, + "title":"Testing CtlRuleRemoveTargetById (4) range: within range", + "expected":{ + "http_code": 200 + }, + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Cookie": "PHPSESSID=rAAAAAAA2t5uvjq435r4q7ib3vtdjq120", + "Content-Type": "text/xml" + }, + "uri":"/wp-login.php?whee&pwd=lhebs", + "method":"GET", + "body": [ ] + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "rules":[ + "SecRuleEngine On", + "SecRule REQUEST_FILENAME \"@endsWith /wp-login.php\" \"id:1,phase:2,t:none,nolog,pass,ctl:ruleRemoveTargetById=4-6;ARGS:pwd\"", + "SecRule ARGS \"@contains lhebs\" \"id:5,phase:2,t:none,deny,status:403\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"Testing CtlRuleRemoveTargetById (5) range: within range but !target", + "expected":{ + "http_code": 403 + }, + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Cookie": "PHPSESSID=rAAAAAAA2t5uvjq435r4q7ib3vtdjq120", + "Content-Type": "text/xml" + }, + "uri":"/wp-login.php?whee&pswd=lhebs", + "method":"GET", + "body": [ ] + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "rules":[ + "SecRuleEngine On", + "SecRule REQUEST_FILENAME \"@endsWith /wp-login.php\" \"id:1,phase:2,t:none,nolog,pass,ctl:ruleRemoveTargetById=4-6;ARGS:pwd\"", + "SecRule ARGS \"@contains lhebs\" \"id:5,phase:2,t:none,deny,status:403\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"Testing CtlRuleRemoveTargetById (6) range: outside of range", + "expected":{ + "http_code": 403 + }, + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Cookie": "PHPSESSID=rAAAAAAA2t5uvjq435r4q7ib3vtdjq120", + "Content-Type": "text/xml" + }, + "uri":"/wp-login.php?whee&pwd=lhebs", + "method":"GET", + "body": [ ] + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "rules":[ + "SecRuleEngine On", + "SecRule REQUEST_FILENAME \"@endsWith /wp-login.php\" \"id:1,phase:2,t:none,nolog,pass,ctl:ruleRemoveTargetById=4-6;ARGS:pwd\"", + "SecRule ARGS \"@contains lhebs\" \"id:7,phase:2,t:none,deny,status:403\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"Testing CtlRuleRemoveTargetById (7) range: remove the collection", + "expected":{ + "http_code": 200 + }, + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Cookie": "PHPSESSID=rAAAAAAA2t5uvjq435r4q7ib3vtdjq120", + "Content-Type": "text/xml" + }, + "uri":"/wp-login.php?whee&pwd=lhebs", + "method":"GET", + "body": [ ] + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "rules":[ + "SecRuleEngine On", + "SecRule REQUEST_FILENAME \"@endsWith /wp-login.php\" \"id:1,phase:2,t:none,nolog,pass,ctl:ruleRemoveTargetById=4-6;ARGS\"", + "SecRule ARGS_NAMES|ARGS \"@contains lhebs\" \"id:5,phase:2,t:none,deny,status:403\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"Testing CtlRuleRemoveTargetById (8) range: remove other collection", + "expected":{ + "http_code": 403 + }, + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "Cookie": "PHPSESSID=rAAAAAAA2t5uvjq435r4q7ib3vtdjq120", + "Content-Type": "text/xml" + }, + "uri":"/wp-login.php?whee&pwd=lhebs", + "method":"GET", + "body": [ ] + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "rules":[ + "SecRuleEngine On", + "SecRule REQUEST_FILENAME \"@endsWith /wp-login.php\" \"id:1,phase:2,t:none,nolog,pass,ctl:ruleRemoveTargetById=4-6;ARGS_NAMES\"", + "SecRule ARGS_NAMES|ARGS \"@contains lhebs\" \"id:5,phase:2,t:none,deny,status:403\"" + ] } ]