diff --git a/src/parser/driver.cc b/src/parser/driver.cc index d85a2fc6..bcb94195 100644 --- a/src/parser/driver.cc +++ b/src/parser/driver.cc @@ -39,6 +39,17 @@ Driver::~Driver() { delete loc.back(); } +int Driver::addSecAction(Rule *rule) { + if (rule->phase >= ModSecurity::Phases::NUMBER_OF_PHASES) { + parserError << "Unknown phase: " << std::to_string(rule->phase); + parserError << std::endl; + return false; + } + + rules[rule->phase].push_back(rule); + + return true; +} int Driver::addSecRule(Rule *rule) { if (rule->phase >= ModSecurity::Phases::NUMBER_OF_PHASES) { diff --git a/src/parser/driver.h b/src/parser/driver.h index 2cf69d5a..f3ee29df 100644 --- a/src/parser/driver.h +++ b/src/parser/driver.h @@ -57,6 +57,7 @@ class Driver : public RulesProperties { virtual ~Driver(); int addSecRule(Rule *rule); + int addSecAction(Rule *rule); int result; diff --git a/src/parser/seclang-parser.yy b/src/parser/seclang-parser.yy index 0b609be6..4f64c136 100644 --- a/src/parser/seclang-parser.yy +++ b/src/parser/seclang-parser.yy @@ -190,6 +190,7 @@ using ModSecurity::Variables::Variable; %token CONFIG_DIR_DEBUG_LOG %token CONFIG_DIR_DEBUG_LVL +%token CONFIG_DIR_SEC_ACTION %token CONFIG_DIR_SEC_DEFAULT_ACTION %token VARIABLE @@ -356,6 +357,24 @@ expression: ); driver.addSecRule(rule); } + | CONFIG_DIR_SEC_ACTION SPACE QUOTATION_MARK actions QUOTATION_MARK + { + Rule *rule = new Rule( + /* op */ NULL, + /* variables */ NULL, + /* actions */ $4 + ); + driver.addSecAction(rule); + } + | CONFIG_DIR_SEC_ACTION SPACE actions + { + Rule *rule = new Rule( + /* op */ NULL, + /* variables */ NULL, + /* actions */ $3 + ); + driver.addSecAction(rule); + } | CONFIG_DIR_SEC_DEFAULT_ACTION SPACE QUOTATION_MARK actions QUOTATION_MARK { std::vector *actions = $4; diff --git a/src/parser/seclang-scanner.ll b/src/parser/seclang-scanner.ll index 43186d57..c3cb3121 100755 --- a/src/parser/seclang-scanner.ll +++ b/src/parser/seclang-scanner.ll @@ -39,6 +39,7 @@ DIRECTIVE (?i:SecRule) LOG_DATA (?i:logdata) CONFIG_DIR_SEC_DEFAULT_ACTION (?i:SecDefaultAction) +CONFIG_DIR_SEC_ACTION (?i:SecAction) CONFIG_DIR_PCRE_MATCH_LIMIT_RECURSION (?i:SecPcreMatchLimitRecursion) CONFIG_DIR_PCRE_MATCH_LIMIT (?i:SecPcreMatchLimit) @@ -243,6 +244,7 @@ CONFIG_DIR_UNICODE_MAP_FILE (?i:SecUnicodeMapFile) {CONFIG_VALUE_PROCESS_PARTIAL} { return yy::seclang_parser::make_CONFIG_VALUE_PROCESS_PARTIAL(yytext, *driver.loc.back()); } {CONFIG_VALUE_REJECT} { return yy::seclang_parser::make_CONFIG_VALUE_REJECT(yytext, *driver.loc.back()); } +{CONFIG_DIR_SEC_ACTION} { return yy::seclang_parser::make_CONFIG_DIR_SEC_ACTION(yytext, *driver.loc.back()); } {CONFIG_DIR_SEC_DEFAULT_ACTION} { return yy::seclang_parser::make_CONFIG_DIR_SEC_DEFAULT_ACTION(yytext, *driver.loc.back()); } { diff --git a/src/rule.cc b/src/rule.cc index b83919f6..557f9766 100644 --- a/src/rule.cc +++ b/src/rule.cc @@ -56,7 +56,7 @@ Rule::~Rule() { actions_runtime_pos.pop_back(); delete a; } - while (variables->empty() == false) { + while (variables != NULL && variables->empty() == false) { auto *a = variables->back(); variables->pop_back(); delete a; @@ -75,6 +75,7 @@ Rule::Rule(Operator *_op, op(_op), rule_id(0), phase(-1), + m_unconditional(false), m_referenceCount(0) { for (Action *a : *actions) { if (a->action_kind == Action::ConfigurationKind) { @@ -98,13 +99,81 @@ Rule::Rule(Operator *_op, phase = ModSecurity::Phases::RequestHeadersPhase; } + if (op == NULL) { + m_unconditional = true; + } + delete actions; } + +bool Rule::evaluateActions(Assay *assay) { + int none = 0; + int transformations = 0; + for (Action *a : this->actions_runtime_pre) { + None *z = dynamic_cast(a); + if (z != NULL) { + none++; + } + } + + assay->debug(4, "Running unconditional rule."); + + if (none == 0) { + /* + for (Action *a : assay->m_rules->defaultActions[this->phase]) { + if (a->action_kind == actions::Action::RunTimeBeforeMatchAttemptKind) { + value = a->evaluate(value, assay); + assay->debug(9, "(SecDefaultAction) T (" + \ + std::to_string(transformations) + ") " + \ + a->name + ": \"" + value +"\""); + transformations++; + } + } + */ + } + + for (Action *a : this->actions_runtime_pre) { + None *z = dynamic_cast(a); + /* + if (none == 0) { + value = a->evaluate(value, assay); + assay->debug(9, " T (" + \ + std::to_string(transformations) + ") " + \ + a->name + ": \"" + value +"\""); + transformations++; + } + */ + if (z != NULL) { + none--; + } + } + + for (Action *a : assay->m_rules->defaultActions[this->phase]) { + if (a->action_kind == actions::Action::RunTimeOnlyIfMatchKind) { + assay->debug(4, "(SecDefaultAction) Running action: " + a->action); + a->evaluate(this, assay); + } + } + + for (Action *a : + this->actions_runtime_pos) { + assay->debug(4, "Running action: " + a->action); + a->evaluate(this, assay); + } + + return true; +} + + bool Rule::evaluate(Assay *assay) { bool ret = false; std::vector *variables = this->variables; + if (m_unconditional == true) { + return evaluateActions(assay); + } + assay->debug(4, "Executing operator \"" + this->op->op \ + "\" with param \"" + this->op->param + "\" against " \ + Variable::to_s(variables) + "."); diff --git a/src/rule.h b/src/rule.h index a7286b83..ae8bba34 100644 --- a/src/rule.h +++ b/src/rule.h @@ -37,6 +37,7 @@ class Rule { ~Rule(); bool evaluate(Assay *assay); + bool evaluateActions(Assay *assay); operators::Operator *op; std::vector actions_conf; @@ -64,6 +65,7 @@ class Rule { std::string rev; private: + bool m_unconditional; int m_referenceCount; }; diff --git a/test/test-cases/regression/secaction.json b/test/test-cases/regression/secaction.json new file mode 100644 index 00000000..460c8634 --- /dev/null +++ b/test/test-cases/regression/secaction.json @@ -0,0 +1,63 @@ +[ + { + "enabled": 1, + "version_min": 300000, + "version_max": 0, + "title": "sec action", + "client": { + "ip": "200.249.12.31", + "port": 2313 + }, + "server": { + "ip": "200.249.12.31", + "port": 80 + }, + "request": { + "headers": { + "Host": "net.tutsplus.com", + "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=r2t5uvjq435r4q7ib3vtdjq120", + "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": "Running unconditional rule.", + "error_log": "" + }, + "rules": [ + "SecRuleEngine On", + "SecDebugLog \/tmp\/modsec_debug.log", + "SecDebugLogLevel 9", + "SecRule ARGS \"@contains test\" \"phase:2,id:1,t:trim\"", + "SecAction \"phase:2,nolog,pass\"" + ] + } +]