diff --git a/Makefile.am b/Makefile.am index 813d419a..5a80380e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -224,4 +224,5 @@ TESTS+=test/test-cases/regression/variable-SERVER_NAME.json TESTS+=test/test-cases/regression/operator-UnconditionalMatch.json TESTS+=test/test-cases/regression/request-body-parser-json.json TESTS+=test/test-cases/regression/action-skip.json +TESTS+=test/test-cases/regression/action-allow.json diff --git a/headers/modsecurity/transaction.h b/headers/modsecurity/transaction.h index 5594e09c..5b40b3b7 100644 --- a/headers/modsecurity/transaction.h +++ b/headers/modsecurity/transaction.h @@ -73,6 +73,7 @@ class Rules; class RuleMessage; namespace actions { class Action; +enum AllowType : short; } namespace RequestBodyProcessor { class XML; @@ -310,6 +311,11 @@ class Transaction { */ int m_skip_next; + /** + * If allow action was utilized, this variable holds the allow type. + */ + modsecurity::actions::AllowType m_allowType; + /** * Holds the decode URI. Notice that m_uri holds the raw version * of the URI. diff --git a/src/Makefile.am b/src/Makefile.am index 9d8ab7c1..8ffd39bf 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -78,6 +78,7 @@ VARIABLES = \ ACTIONS = \ actions/accuracy.cc \ actions/action.cc \ + actions/allow.cc \ actions/audit_log.cc \ actions/block.cc \ actions/capture.cc \ diff --git a/src/actions/allow.cc b/src/actions/allow.cc new file mode 100644 index 00000000..4938d9df --- /dev/null +++ b/src/actions/allow.cc @@ -0,0 +1,58 @@ +/* + * ModSecurity, http://www.modsecurity.org/ + * Copyright (c) 2015 Trustwave Holdings, Inc. (http://www.trustwave.com/) + * + * You may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * If any of the files related to licensing are missing or if you have any + * other questions related to licensing please contact Trustwave Holdings, Inc. + * directly using the email address security@modsecurity.org. + * + */ + +#include "actions/allow.h" + +#include +#include + +#include "modsecurity/transaction.h" +#include "modsecurity/rule.h" +#include "src/utils.h" +#include "modsecurity/modsecurity.h" + +namespace modsecurity { +namespace actions { + +bool Allow::init(std::string *error) { + std::string a = tolower(m_parser_payload); + + if (a == "phase") { + m_allowType = PhaseAllowType; + } else if (a == "request") { + m_allowType = RequestAllowType; + } else if (a == "") { + m_allowType = FromNowOneAllowType; + } else { + error->assign("Allow: if specified, the parameter most be: phase, request"); + return false; + } + + return true; +} + + +bool Allow::evaluate(Rule *rule, Transaction *transaction) { + transaction->debug(4, "Dropping the evaluation of upcoming rules " \ + "in favor of an `allow' action of type: " \ + + allowTypeToName(m_allowType)); + + transaction->m_allowType = m_allowType; + + return true; +} + +} // namespace actions +} // namespace modsecurity diff --git a/src/actions/allow.h b/src/actions/allow.h new file mode 100644 index 00000000..926c10c2 --- /dev/null +++ b/src/actions/allow.h @@ -0,0 +1,83 @@ +/* + * ModSecurity, http://www.modsecurity.org/ + * Copyright (c) 2015 Trustwave Holdings, Inc. (http://www.trustwave.com/) + * + * You may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * If any of the files related to licensing are missing or if you have any + * other questions related to licensing please contact Trustwave Holdings, Inc. + * directly using the email address security@modsecurity.org. + * + */ + +#include + +#include "actions/action.h" + +#ifndef SRC_ACTIONS_ALLOW_H_ +#define SRC_ACTIONS_ALLOW_H_ + +#ifdef __cplusplus +class Transaction; + +namespace modsecurity { +class Transaction; +class Rule; + +namespace actions { + +enum AllowType : short { + /** + * + */ + NoneAllowType, + /** + * + */ + RequestAllowType, + /** + * + */ + PhaseAllowType, + /** + * + */ + FromNowOneAllowType, +}; + + +class Allow : public Action { + public: + explicit Allow(std::string action) + : Action(action, RunTimeOnlyIfMatchKind), + m_allowType(NoneAllowType) { } + + + bool init(std::string *error) override; + bool evaluate(Rule *rule, Transaction *transaction) override; + + AllowType m_allowType; + + static std::string allowTypeToName (AllowType a) { + if (a == NoneAllowType) { + return "None"; + } else if (a = RequestAllowType) { + return "Request"; + } else if (a = PhaseAllowType) { + return "Phase"; + } else if (a = FromNowOneAllowType) { + return "FromNowOne"; + } else { + return "Unknown"; + } + } +}; + +} // namespace actions +} // namespace modsecurity +#endif + +#endif // SRC_ACTIONS_ALLOW_H_ diff --git a/src/parser/seclang-parser.yy b/src/parser/seclang-parser.yy index 9fcc8885..4525ff2a 100644 --- a/src/parser/seclang-parser.yy +++ b/src/parser/seclang-parser.yy @@ -34,6 +34,7 @@ class Driver; #include "actions/skip_after.h" #include "actions/msg.h" #include "actions/phase.h" +#include "actions/allow.h" #include "actions/log_data.h" #include "actions/maturity.h" #include "actions/redirect.h" @@ -87,6 +88,7 @@ using modsecurity::actions::Rev; using modsecurity::actions::Ver; using modsecurity::actions::Msg; using modsecurity::actions::Phase; +using modsecurity::actions::Allow; using modsecurity::actions::transformations::None; using modsecurity::actions::LogData; using modsecurity::actions::Maturity; @@ -932,6 +934,15 @@ act: YYERROR; } } + | ACTION_ALLOW + { + std::string error; + $$ = new Allow($1); + if ($$->init(&error) == false) { + driver.error(@0, error); + YYERROR; + } + } | ACTION_PHASE { std::string error; @@ -986,24 +997,6 @@ act: */ $$ = Action::instantiate($1); } - | ACTION_ALLOW - { - /* - - TODO: allow is not implemented yet. - - std::string error; - Allow *allow = new Allow($1); - - if (allow->init(&error) == false) { - driver.parserError << error; - YYERROR; - } - - $$ = allow; - */ - $$ = Action::instantiate($1); - } | ACTION_REDIRECT { std::string error; diff --git a/src/parser/seclang-scanner.ll b/src/parser/seclang-scanner.ll index cbb81efe..5b36aea9 100755 --- a/src/parser/seclang-scanner.ll +++ b/src/parser/seclang-scanner.ll @@ -25,7 +25,7 @@ using modsecurity::split; ACTION (?i:accuracy|append|block|capture|chain|deny|deprecatevar|drop|expirevar|id:[0-9]+|id:'[0-9]+'|log|multiMatch|noauditlog|nolog|pass|pause|prepend|proxy|sanitiseArg|sanitiseMatched|sanitiseMatchedBytes|sanitiseRequestHeader|sanitiseResponseHeader|setrsc|setenv|status:[0-9]+) ACTION_XMLNS (?i:xmlns) -ACTION_ALLOW (?i:allow) +ACTION_ALLOW ((?i:allow:(?i:REQUEST|PHASE))|(?i:phase:'(?i:REQUEST|PHASE)')|(?i:allow)) ACTION_INITCOL (?i:initcol) ACTION_ACCURACY (?i:accuracy) @@ -418,9 +418,7 @@ CONFIG_DIR_UNICODE_MAP_FILE (?i:SecUnicodeMapFile) {LOG_DATA}:'{FREE_TEXT_QUOTE}' { return yy::seclang_parser::make_LOG_DATA(yytext, *driver.loc.back()); } {ACTION_MSG}:'{FREE_TEXT_QUOTE}' { return yy::seclang_parser::make_ACTION_MSG(yytext, *driver.loc.back()); } -{ACTION_ALLOW}:'{FREE_TEXT_QUOTE}' { return yy::seclang_parser::make_ACTION_ALLOW(yytext, *driver.loc.back()); } -{ACTION_ALLOW}:{FREE_TEXT_QUOTE_COMMA} { return yy::seclang_parser::make_ACTION_ALLOW(yytext, *driver.loc.back()); } -{ACTION_ALLOW} { return yy::seclang_parser::make_ACTION_ALLOW("", *driver.loc.back()); } +{ACTION_ALLOW} { return yy::seclang_parser::make_ACTION_ALLOW(yytext, *driver.loc.back()); } {ACTION_REDIRECT}:{FREE_TEXT} { return yy::seclang_parser::make_ACTION_REDIRECT(yytext, *driver.loc.back()); } {ACTION_TAG}:'{FREE_TEXT_QUOTE}' { return yy::seclang_parser::make_ACTION_TAG(yytext, *driver.loc.back()); } {ACTION_REV}:'{FREE_TEXT_QUOTE_COMMA}' { return yy::seclang_parser::make_ACTION_REV(yytext, *driver.loc.back()); } diff --git a/src/rules.cc b/src/rules.cc index f0d61be4..f5ab60ca 100644 --- a/src/rules.cc +++ b/src/rules.cc @@ -183,6 +183,22 @@ int Rules::evaluate(int phase, Transaction *transaction) { debug(9, "This phase consists of " + std::to_string(rules.size()) + \ " rule(s)."); + if (transaction->m_allowType == actions::FromNowOneAllowType + && phase != ModSecurity::Phases::LoggingPhase) { + debug(9, "Skipping all rules evaluation on this phase as request " \ + "through the utilization of an `allow' action."); + return true; + } + if (transaction->m_allowType == actions::RequestAllowType + && phase <= ModSecurity::Phases::RequestBodyPhase) { + debug(9, "Skipping all rules evaluation on this phase as request " \ + "through the utilization of an `allow' action."); + return true; + } + if (transaction->m_allowType != actions::NoneAllowType) { + transaction->m_allowType = actions::NoneAllowType; + } + for (int i = 0; i < rules.size(); i++) { Rule *rule = rules[i]; if (transaction->m_marker.empty() == false) { @@ -199,8 +215,11 @@ int Rules::evaluate(int phase, Transaction *transaction) { } else if (transaction->m_skip_next > 0) { transaction->m_skip_next--; debug(9, "Skipped rule id '" + std::to_string(rule->rule_id) \ - + "' due to `skip' action. Still " + \ + + "' due to a `skip' action. Still " + \ std::to_string(transaction->m_skip_next) + " to be skipped."); + } else if (transaction->m_allowType != actions::NoneAllowType) { + debug(9, "Skipped rule id '" + std::to_string(rule->rule_id) \ + + "' as request trough the utilization of an `allow' action."); } else { rule->evaluate(transaction); } diff --git a/src/transaction.cc b/src/transaction.cc index 5aec575c..1f6a6f5d 100644 --- a/src/transaction.cc +++ b/src/transaction.cc @@ -43,6 +43,7 @@ #include "src/unique_id.h" #include "src/utils.h" #include "modsecurity/rule.h" +#include "src/actions/allow.h" using modsecurity::actions::Action; using modsecurity::RequestBodyProcessor::Multipart; @@ -113,6 +114,7 @@ Transaction::Transaction(ModSecurity *ms, Rules *rules, void *logCbData) m_responseHeadersNames(NULL), m_responseContentType(NULL), m_marker(""), + m_allowType(modsecurity::actions::NoneAllowType), m_skip_next(0), m_creationTimeStamp(cpu_seconds()), m_logCbData(logCbData), diff --git a/test/test-cases/regression/action-allow.json b/test/test-cases/regression/action-allow.json new file mode 100644 index 00000000..2a127271 --- /dev/null +++ b/test/test-cases/regression/action-allow.json @@ -0,0 +1,101 @@ +[ + { + "enabled":1, + "version_min":300000, + "title":"Testing allow action 1/3", + "expected":{ + "debug_log": "Skipped rule id '500066' as request trough the utilization of an `allow' action", + "http_code": 200 + }, + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "User-Agent":"My sweet little browser", + "Cookie": "PHPSESSID=rAAAAAAA2t5uvjq435r4q7ib3vtdjq120" + }, + "uri":"/?key=value&key=other_value", + "method":"GET" + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "rules":[ + "SecRuleEngine On", + "SecAction \"phase:1,allow,msg:'ALLOWED',id:500065\"", + "SecAction \"phase:1,deny,msg:'DENIED',id:500066\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"Testing allow action 1/3", + "expected":{ + "debug_log": "", + "http_code": 500 + }, + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "User-Agent":"My sweet little browser", + "Cookie": "PHPSESSID=rAAAAAAA2t5uvjq435r4q7ib3vtdjq120" + }, + "uri":"/?key=value&key=other_value", + "method":"GET" + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "rules":[ + "SecRuleEngine On", + "SecAction \"phase:1,allow:request,msg:'ALLOWED',id:500065\"", + "SecRule ARGS \"@contains value\" \"id:1,t:trim,status:500,deny,phase:3\"" + ] + }, + { + "enabled":1, + "version_min":300000, + "title":"Testing allow action 1/3", + "expected":{ + "debug_log": "", + "http_code": 500 + }, + "client":{ + "ip":"200.249.12.31", + "port":123 + }, + "request":{ + "headers":{ + "Host":"localhost", + "User-Agent":"curl/7.38.0", + "Accept":"*/*", + "User-Agent":"My sweet little browser", + "Cookie": "PHPSESSID=rAAAAAAA2t5uvjq435r4q7ib3vtdjq120" + }, + "uri":"/?key=value&key=other_value", + "method":"GET" + }, + "server":{ + "ip":"200.249.12.31", + "port":80 + }, + "rules":[ + "SecRuleEngine On", + "SecAction \"phase:1,allow:phase,msg:'ALLOWED',id:500065\"", + "SecRule ARGS \"@contains value\" \"id:1,t:trim,status:500,deny,phase:3\"" + ] + } +] \ No newline at end of file diff --git a/test/test-cases/regression/action-skip.json b/test/test-cases/regression/action-skip.json index 829839ad..f5189cc4 100644 --- a/test/test-cases/regression/action-skip.json +++ b/test/test-cases/regression/action-skip.json @@ -4,7 +4,7 @@ "version_min":300000, "title":"Testing skip action 1/3", "expected":{ - "debug_log": "\\[9\\] Skipped rule id \\'2\\' due to \\`skip\\' action." + "debug_log": "\\[9\\] Skipped rule id \\'2\\' due to a \\`skip\\' action." }, "client":{ "ip":"200.249.12.31", @@ -74,7 +74,7 @@ "version_min":300000, "title":"Testing skip action 3/3", "expected":{ - "debug_log": "\\[9\\] Skipped rule id \\'3\\' due to \\`skip\\' action." + "debug_log": "\\[9\\] Skipped rule id \\'3\\' due to a \\`skip\\' action." }, "client":{ "ip":"200.249.12.31",