From e5acc95de8053e5cba321c1046ba68998e089797 Mon Sep 17 00:00:00 2001 From: Felipe Zimmerle Date: Wed, 30 Mar 2016 18:22:00 -0300 Subject: [PATCH] First version of `global' and `ip' collections --- headers/modsecurity/modsecurity.h | 3 + headers/modsecurity/transaction/collections.h | 9 +- .../transaction/global_variables.h | 119 +++++++++++++++++ src/Makefile.am | 2 + src/actions/init_col.cc | 23 +++- src/collections.cc | 73 +++++++++- src/global_variables.cc | 126 ++++++++++++++++++ src/transaction.cc | 5 +- .../test-cases/regression/action-initcol.json | 18 ++- 9 files changed, 368 insertions(+), 10 deletions(-) create mode 100644 headers/modsecurity/transaction/global_variables.h create mode 100644 src/global_variables.cc diff --git a/headers/modsecurity/modsecurity.h b/headers/modsecurity/modsecurity.h index 12b42f5a..d88edbf8 100644 --- a/headers/modsecurity/modsecurity.h +++ b/headers/modsecurity/modsecurity.h @@ -93,6 +93,7 @@ typedef struct ModSecurity_t modsecurity; #include "modsecurity/transaction.h" #include "modsecurity/debug_log.h" #include "modsecurity/rules.h" +#include "modsecurity/transaction/global_variables.h" /** * TAG_NUM: @@ -222,6 +223,8 @@ class ModSecurity { NUMBER_OF_PHASES, }; + transaction::GlobalVariables m_global_collection; + transaction::GlobalVariables m_ip_collection; private: std::string m_connector; LogCb m_logCb; diff --git a/headers/modsecurity/transaction/collections.h b/headers/modsecurity/transaction/collections.h index 2b3e6ecb..8aaba1e7 100644 --- a/headers/modsecurity/transaction/collections.h +++ b/headers/modsecurity/transaction/collections.h @@ -27,6 +27,7 @@ #include #endif +#include "modsecurity/transaction/global_variables.h" #include "modsecurity/transaction/variables.h" #include "modsecurity/transaction/variable.h" #include "modsecurity/transaction/collection.h" @@ -45,7 +46,7 @@ namespace transaction { class Collections : public std::unordered_map { public: - Collections(); + Collections(GlobalVariables *global, GlobalVariables *ip); ~Collections(); void init(const std::string& name, const std::string& key); @@ -84,6 +85,12 @@ class Collections : * Notice that it is not the TX collection. */ transaction::Variables m_transient; + + std::string m_global_collection_key; + std::string m_ip_collection_key; + + transaction::GlobalVariables *m_global_collection; + transaction::GlobalVariables *m_ip_collection; }; } // namespace transaction diff --git a/headers/modsecurity/transaction/global_variables.h b/headers/modsecurity/transaction/global_variables.h new file mode 100644 index 00000000..33ec90d9 --- /dev/null +++ b/headers/modsecurity/transaction/global_variables.h @@ -0,0 +1,119 @@ +/* + * 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. + * + */ + + +#ifdef __cplusplus +#include +#include +#include +#include +#include +#include +#endif + + +#include "modsecurity/transaction/variable.h" + +#ifndef HEADERS_MODSECURITY_TRANSACTION_GLOBAL_VARIABLES_H_ +#define HEADERS_MODSECURITY_TRANSACTION_GLOBAL_VARIABLES_H_ + +#ifndef __cplusplus +typedef struct Variable_t Variables; +#endif + +#ifdef __cplusplus +namespace modsecurity { +namespace transaction { + + +class CollectionKey { + public: + CollectionKey() + : m_compartiment(""), + m_name("") { }; + CollectionKey(std::string name) + : m_compartiment(""), + m_name(name) { }; + CollectionKey(std::string name, std::string compartiment) + : m_compartiment(compartiment), + m_name(name) { }; + + std::string m_name; + std::string m_compartiment; +}; + + +class collection_hash +{ +public: + size_t operator()(const CollectionKey *v) const + { + size_t h = 0; + std::for_each(v->m_name.begin(), v->m_name.end(), [&](char c) { + h += tolower(c); + }); + std::for_each(v->m_compartiment.begin(), v->m_compartiment.end(), [&](char c) { + h += tolower(c); + }); + + return h; + }; +}; + + +class collection_equal +{ +public: + bool operator()(const CollectionKey *u, const CollectionKey *v) const + { + return u->m_name == v->m_name && u->m_compartiment == v->m_compartiment; + }; +}; + + +class GlobalVariables : + public std::unordered_multimap { + public: + GlobalVariables(); + ~GlobalVariables(); + void store(std::string key, std::string compartment, std::string value); + + bool storeOrUpdateFirst(const std::string &key, std::string compartment, + const std::string &value); + + bool updateFirst(const std::string &key, std::string compartment, + const std::string &value); + + void del(const std::string& key, std::string compartment); + + std::string* resolveFirst(const std::string& var, std::string compartment); + void resolveSingleMatch(const std::string& var, std::string compartment, + std::vector *l); + void resolveMultiMatches(const std::string& var, std::string compartment, + std::vector *l); + + void resolveRegularExpression(const std::string& var, std::string compartment, + std::vector *l); + + +}; + +} // namespace transaction +} // namespace modsecurity +#endif + + +#endif // HEADERS_MODSECURITY_TRANSACTION_GLOBAL_VARIABLES_H_ diff --git a/src/Makefile.am b/src/Makefile.am index a655ad7f..71539cc1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -36,6 +36,7 @@ libmodsecurity_includesub_HEADERS = \ ../headers/modsecurity/transaction/collections.h \ ../headers/modsecurity/transaction/variable.h \ ../headers/modsecurity/transaction/variables.h + ../headers/modsecurity/transaction/global_variables.h @@ -188,6 +189,7 @@ libmodsecurity_la_SOURCES = \ utils.cc \ collections.cc \ variables.cc \ + global_variables.cc \ debug_log.cc \ debug_log_writer.cc \ debug_log_writer_agent.cc \ diff --git a/src/actions/init_col.cc b/src/actions/init_col.cc index e7eaec8a..bebcb885 100644 --- a/src/actions/init_col.cc +++ b/src/actions/init_col.cc @@ -46,15 +46,30 @@ bool InitCol::init(std::string *error) { m_collection_key = std::string(action, posInit, posEquals - posInit); m_collection_value = std::string(action, posEquals + 1); + if (m_collection_key != "ip" && m_collection_key != "global") { + return false; + } + return true; } -bool InitCol::evaluate(Rule *rule, Transaction *transaction) { +bool InitCol::evaluate(Rule *rule, Transaction *t) { std::string collectionName; - collectionName = MacroExpansion::expand(m_collection_value, transaction); - std::cout << "Collection is not implemented yet, here is the "; - std::cout << "collection name: " << collectionName << std::endl; + collectionName = MacroExpansion::expand(m_collection_value, t); + + + if (m_collection_key == "ip") { + t->m_collections.m_ip_collection_key = collectionName; + } else if (m_collection_key == "global") { + t->m_collections.m_global_collection_key = collectionName; + } else { + return false; + } + + t->debug(5, "Collection `" + m_collection_key + "' initialized with " \ + "value: " + collectionName); + return true; } diff --git a/src/collections.cc b/src/collections.cc index 6442c581..ee1f9948 100644 --- a/src/collections.cc +++ b/src/collections.cc @@ -31,7 +31,12 @@ namespace modsecurity { namespace transaction { -Collections::Collections() { +Collections::Collections(transaction::GlobalVariables *global, + transaction::GlobalVariables *ip) + : m_global_collection_key(""), + m_ip_collection_key(""), + m_global_collection(global), + m_ip_collection(ip) { /* Create collection TX */ this->emplace("TX", new Collection("TX", "")); } @@ -53,6 +58,20 @@ void Collections::init(const std::string& name, const std::string& key) { void Collections::storeOrUpdateFirst(const std::string& collectionName, const std::string& variableName, const std::string& targetValue) { + if (tolower(collectionName) == "ip" + && !m_ip_collection_key.empty()) { + m_ip_collection->storeOrUpdateFirst(collectionName + ":" + + variableName, m_ip_collection_key, targetValue); + return; + } + + if (tolower(collectionName) == "global" + && !m_global_collection_key.empty()) { + m_global_collection->storeOrUpdateFirst(collectionName + ":" + + variableName, m_global_collection_key, targetValue); + return; + } + try { transaction::Variables *collection; collection = this->at(collectionName); @@ -99,7 +118,7 @@ std::string* Collections::resolveFirst(const std::string& var) { for (auto &a : *this) { auto range = a.second->equal_range(var); for (auto it = range.first; it != range.second; ++it) { - return &it->second; + return & it->second; } } @@ -109,6 +128,19 @@ std::string* Collections::resolveFirst(const std::string& var) { std::string* Collections::resolveFirst(const std::string& collectionName, const std::string& var) { + + if (tolower(collectionName) == "ip" + && !m_ip_collection_key.empty()) { + return m_ip_collection->resolveFirst(toupper(collectionName) + + ":" + var, m_ip_collection_key); + } + + if (tolower(collectionName) == "global" + && !m_global_collection_key.empty()) { + return m_global_collection->resolveFirst(toupper(collectionName) + + ":" + var, m_global_collection_key); + } + for (auto &a : *this) { if (tolower(a.first) == tolower(collectionName)) { transaction::Variables *t = a.second; @@ -135,6 +167,18 @@ void Collections::resolveSingleMatch(const std::string& var, const std::string& collection, std::vector *l) { + if (tolower(collection) == "ip" + && !m_ip_collection_key.empty()) { + m_ip_collection->resolveSingleMatch(var, m_ip_collection_key, l); + return; + } + + if (tolower(collection) == "global" + && !m_global_collection_key.empty()) { + m_global_collection->resolveSingleMatch(var, m_global_collection_key, l); + return; + } + try { this->at(collection)->resolveSingleMatch(var, l); } catch (...) { } @@ -150,6 +194,18 @@ void Collections::resolveMultiMatches(const std::string& var, void Collections::resolveMultiMatches(const std::string& var, const std::string& collection, std::vector *l) { + if (tolower(collection) == "ip" + && !m_ip_collection_key.empty()) { + m_ip_collection->resolveMultiMatches(var, m_ip_collection_key, l); + return; + } + + if (tolower(collection) == "global" + && !m_global_collection_key.empty()) { + m_global_collection->resolveMultiMatches(var, m_global_collection_key, l); + return; + } + try { this->at(collection)->resolveMultiMatches(var, l); } catch (...) { } @@ -164,6 +220,19 @@ void Collections::resolveRegularExpression(const std::string& var, void Collections::resolveRegularExpression(const std::string& var, const std::string& collection, std::vector *l) { + if (tolower(collection) == "ip" + && !m_ip_collection_key.empty()) { + m_ip_collection->resolveRegularExpression(toupper(collection) + + ":" + var, m_ip_collection_key, l); + return; + } + + if (tolower(collection) == "global" + && !m_global_collection_key.empty()) { + m_global_collection->resolveRegularExpression(toupper(collection) + + ":" + var, m_global_collection_key, l); + return; + } try { this->at(collection)->resolveRegularExpression(var, l); diff --git a/src/global_variables.cc b/src/global_variables.cc new file mode 100644 index 00000000..e1b6c71b --- /dev/null +++ b/src/global_variables.cc @@ -0,0 +1,126 @@ +/* + * 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 "modsecurity/transaction/global_variables.h" + +#ifdef __cplusplus +#include +#include +#include +#include +#endif + +#include "modsecurity/transaction/variable.h" +#include "src/utils.h" + +namespace modsecurity { +namespace transaction { + + +GlobalVariables::GlobalVariables() { + this->reserve(1000); +} + +GlobalVariables::~GlobalVariables() { + this->clear(); +} + +void GlobalVariables::store(std::string key, std::string compartment, std::string value) { + this->emplace(new CollectionKey(key, compartment), value); +} + + +bool GlobalVariables::storeOrUpdateFirst(const std::string &key, + std::string compartment, const std::string &value) { + if (updateFirst(key, compartment, value) == false) { + store(key, compartment, value); + } + return true; +} + + +bool GlobalVariables::updateFirst(const std::string &key, std::string compartment, const std::string &value) { + auto range = this->equal_range(new CollectionKey(key, compartment)); + + for (auto it = range.first; it != range.second; ++it) { + it->second = value; + return true; + } + return false; +} + + +void GlobalVariables::del(const std::string& key, std::string compartment) { + this->erase(new CollectionKey(key, compartment)); +} + + + +void GlobalVariables::resolveSingleMatch(const std::string& var, + std::string compartment, std::vector *l) { + auto range = this->equal_range(new CollectionKey(var, compartment)); + + for (auto it = range.first; it != range.second; ++it) { + l->push_back(new transaction::Variable(var, it->second)); + } +} + +void GlobalVariables::resolveMultiMatches(const std::string& var, + std::string compartment, std::vector *l) { + size_t keySize = var.size(); + l->reserve(15); + + auto range = this->equal_range(new CollectionKey(var, compartment)); + + for (auto it = range.first; it != range.second; ++it) { + l->insert(l->begin(), new transaction::Variable(var, it->second)); + } + + for (const auto& x : *this) { + if (x.first->m_name.size() <= keySize + 1) { + continue; + } + if (x.first->m_name.at(keySize) != ':') { + continue; + } + if (x.first->m_name.compare(0, keySize, var) != 0) { + continue; + } + l->insert(l->begin(), new transaction::Variable(x.first->m_name, x.second)); + } +} + + +void GlobalVariables::resolveRegularExpression(const std::string& var, + std::string compartment, std::vector *l) { + /* Not ready */ +} + + +std::string* GlobalVariables::resolveFirst(const std::string& var, + std::string compartment) { + auto range = equal_range(new CollectionKey(var, compartment)); + + for (auto it = range.first; it != range.second; ++it) { + return &it->second; + } + + return NULL; +} + + +} // namespace transaction +} // namespace modsecurity diff --git a/src/transaction.cc b/src/transaction.cc index 7c59cac6..b50728d2 100644 --- a/src/transaction.cc +++ b/src/transaction.cc @@ -111,7 +111,10 @@ Transaction::Transaction(ModSecurity *ms, Rules *rules, void *logCbData) m_marker(""), m_creationTimeStamp(cpu_seconds()), m_logCbData(logCbData), - m_ms(ms) { + m_ms(ms), + m_collections(&ms->m_global_collection, &ms->m_ip_collection) + { + m_id = std::to_string(this->m_timeStamp) + \ std::to_string(generate_transaction_unique_id()); m_rules->incrementReferenceCount(); diff --git a/test/test-cases/regression/action-initcol.json b/test/test-cases/regression/action-initcol.json index 38b22192..6edaced1 100644 --- a/test/test-cases/regression/action-initcol.json +++ b/test/test-cases/regression/action-initcol.json @@ -4,12 +4,22 @@ "version_min":300000, "title":"Testing initcol action", "expected":{ - "debug_log": ".*" + "debug_log": "Saving variable: IP:auth_attempt with value: 3" }, "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" + }, + "uri":"/?key=value&key=other_value", + "method":"GET" + }, "server":{ "ip":"200.249.12.31", "port":80 @@ -17,7 +27,11 @@ "rules":[ "SecRuleEngine On", "SecDebugLog \/tmp\/modsec_debug.log", - "SecRule &TX:REAL_IP \"@eq 0\" \"id:'900021',phase:1,t:none,initcol:global=global,initcol:ip=%{remote_addr}_%{tx.ua_hash},setvar:tx.real_ip=%{remote_addr},nolog,pass\"" + "SecRule REQUEST_HEADERS:User-Agent \"^(.*)$\" \"id:'900018',phase:1,t:none,t:sha1,t:hexEncode,setvar:tx.ua_hash=%{matched_var},nolog,pass\"", + "SecRule &TX:REAL_IP \"@eq 0\" \"id:'900021',phase:1,t:none,initcol:global=global,initcol:ip=%{remote_addr}_%{tx.ua_hash},setvar:tx.real_ip=%{remote_addr},nolog,pass\"", + "SecRule REQUEST_HEADERS:User-Agent \"^(.*)$\" \"id:'900019',phase:2,t:none,setvar:ip.auth_attempt=+1,nolog,pass\"", + "SecRule REQUEST_HEADERS:User-Agent \"^(.*)$\" \"id:'900020',phase:2,t:none,setvar:ip.auth_attempt=+1,nolog,pass\"", + "SecRule REQUEST_HEADERS:User-Agent \"^(.*)$\" \"id:'900022',phase:2,t:none,setvar:ip.auth_attempt=+1,nolog,pass\"" ] } ] \ No newline at end of file