Skip to content

Commit

Permalink
Merge pull request #3001 from SpiderLabs/v3/dev/action_expirevar
Browse files Browse the repository at this point in the history
V3/dev/action expirevar
  • Loading branch information
martinhsv authored Oct 23, 2023
2 parents 135d1fa + c63b5be commit 2fcd373
Show file tree
Hide file tree
Showing 24 changed files with 5,526 additions and 5,077 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-20.04]
os: [ubuntu-22.04]
platform: [x32, x64]
compiler: [gcc, clang]
configure:
Expand All @@ -24,7 +24,6 @@ jobs:
steps:
- name: Setup Dependencies
run: |
sudo add-apt-repository --yes ppa:maxmind/ppa
sudo apt-get update -y -qq
sudo apt-get install -y libfuzzy-dev libyajl-dev libgeoip-dev liblua5.2-dev liblmdb-dev cppcheck libmaxminddb-dev libcurl4-openssl-dev libpcre2-dev pcre2-utils
- uses: actions/checkout@v2
Expand Down
1 change: 1 addition & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ TESTS+=test/test-cases/regression/action-ctl_rule_remove_target_by_id.json
TESTS+=test/test-cases/regression/action-ctl_rule_remove_target_by_tag.json
TESTS+=test/test-cases/regression/action-disruptive.json
TESTS+=test/test-cases/regression/action-exec.json
TESTS+=test/test-cases/regression/action-expirevar.json
TESTS+=test/test-cases/regression/action-id.json
TESTS+=test/test-cases/regression/action-initcol.json
TESTS+=test/test-cases/regression/action-msg.json
Expand Down
20 changes: 18 additions & 2 deletions headers/modsecurity/collection/collection.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* ModSecurity, http://www.modsecurity.org/
* Copyright (c) 2015 - 2021 Trustwave Holdings, Inc. (http://www.trustwave.com/)
* Copyright (c) 2015 - 2023 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
Expand All @@ -16,7 +16,6 @@

#ifdef __cplusplus
#include <string>
#include <iostream>
#include <unordered_map>
#include <list>
#include <vector>
Expand Down Expand Up @@ -56,6 +55,8 @@ class Collection {

virtual void del(const std::string& key) = 0;

virtual void setExpiry(const std::string& key, int32_t expiry_seconds) = 0;

virtual std::unique_ptr<std::string> resolveFirst(
const std::string& var) = 0;

Expand Down Expand Up @@ -129,6 +130,21 @@ class Collection {
}


/* setExpiry */
virtual void setExpiry(const std::string& key, std::string compartment,
int32_t expiry_seconds) {
std::string nkey = compartment + "::" + key;
setExpiry(nkey, expiry_seconds);
}


virtual void setExpiry(const std::string& key, std::string compartment,
std::string compartment2, int32_t expiry_seconds) {
std::string nkey = compartment + "::" + compartment2 + "::" + key;
setExpiry(nkey, expiry_seconds);
}


/* resolveFirst */
virtual std::unique_ptr<std::string> resolveFirst(const std::string& var,
std::string compartment) {
Expand Down
2 changes: 2 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ ACTIONS = \
actions/disruptive/redirect.cc \
actions/disruptive/pass.cc \
actions/exec.cc \
actions/expire_var.cc \
actions/init_col.cc \
actions/log.cc \
actions/log_data.cc \
Expand Down Expand Up @@ -259,6 +260,7 @@ UTILS = \

COLLECTION = \
collection/collections.cc \
collection/backend/collection_data.cc \
collection/backend/in_memory-per_process.cc \
collection/backend/lmdb.cc

Expand Down
95 changes: 95 additions & 0 deletions src/actions/expire_var.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* ModSecurity, http://www.modsecurity.org/
* Copyright (c) 2023 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 "src/actions/expire_var.h"

#include <string>

#include "modsecurity/rules_set.h"
#include "modsecurity/transaction.h"
#include "modsecurity/rule.h"
#include "src/utils/string.h"
#include "src/variables/global.h"
#include "src/variables/ip.h"
#include "src/variables/resource.h"
#include "src/variables/session.h"
#include "src/variables/user.h"
#include "src/variables/variable.h"

namespace modsecurity {
namespace actions {


bool ExpireVar::init(std::string *error) {
return true;
}


bool ExpireVar::evaluate(RuleWithActions *rule, Transaction *t) {

std::string expireExpressionExpanded(m_string->evaluate(t));

std::string fully_qualified_var;
int expirySeconds = 0;
size_t posEquals = expireExpressionExpanded.find("=");
if (posEquals == std::string::npos) {
fully_qualified_var = expireExpressionExpanded;
} else {
fully_qualified_var = expireExpressionExpanded.substr(0, posEquals);
std::string expiry = expireExpressionExpanded.substr(posEquals+1);
if (expiry.find_first_not_of("0123456789") == std::string::npos) {
expirySeconds = atoi(expiry.c_str());
} else {
ms_dbg_a(t, 5, "Non-numeric expiry seconds found in expirevar expression.");
return true;
}
}

size_t posDot = fully_qualified_var.find(".");
if (posDot == std::string::npos) {
ms_dbg_a(t, 5, "No collection found in expirevar expression.");
return true;
}

std::string collection = fully_qualified_var.substr(0, posDot);
std::string variable_name = fully_qualified_var.substr(posDot+1);
std::unique_ptr<RunTimeString> runTimeString(new RunTimeString());
runTimeString->appendText(fully_qualified_var);

if (collection == "ip") {
std::unique_ptr<modsecurity::variables::Ip_DynamicElement> ip_dynamicElement(new modsecurity::variables::Ip_DynamicElement(std::move(runTimeString)));
ip_dynamicElement->setExpiry(t, variable_name, expirySeconds);
} else if (collection == "global") {
std::unique_ptr<modsecurity::variables::Global_DynamicElement> global_dynamicElement(new modsecurity::variables::Global_DynamicElement(std::move(runTimeString)));
global_dynamicElement->setExpiry(t, variable_name, expirySeconds);
} else if (collection == "resource") {
std::unique_ptr<modsecurity::variables::Resource_DynamicElement> resource_dynamicElement(new modsecurity::variables::Resource_DynamicElement(std::move(runTimeString)));
resource_dynamicElement->setExpiry(t, variable_name, expirySeconds);
} else if (collection == "session") {
std::unique_ptr<modsecurity::variables::Session_DynamicElement> session_dynamicElement(new modsecurity::variables::Session_DynamicElement(std::move(runTimeString)));
session_dynamicElement->setExpiry(t, variable_name, expirySeconds);
} else if (collection == "user") {
std::unique_ptr<modsecurity::variables::User_DynamicElement> user_dynamicElement(new modsecurity::variables::User_DynamicElement(std::move(runTimeString)));
user_dynamicElement->setExpiry(t, variable_name, expirySeconds);
} else {
ms_dbg_a(t, 5, "Invalid collection found in expirevar expression: collection must be `ip', `global', `resource', `user' or `session'");
}
ms_dbg_a(t, 9, "Setting variable `" + variable_name + "' to expire in " + std::to_string(expirySeconds) + " seconds.");

return true;
}

} // namespace actions
} // namespace modsecurity
54 changes: 54 additions & 0 deletions src/actions/expire_var.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* ModSecurity, http://www.modsecurity.org/
* Copyright (c) 2023 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 <memory>
#include <string>
#include <utility>

#include "modsecurity/actions/action.h"
#include "src/run_time_string.h"

#ifndef SRC_ACTIONS_EXPIRE_VAR_H_
#define SRC_ACTIONS_EXPIRE_VAR_H_

class Transaction;

namespace modsecurity {
class Transaction;
class RuleWithOperator;

namespace actions {

class ExpireVar : public Action {
public:
explicit ExpireVar(const std::string &action) : Action(action) { }

explicit ExpireVar(std::unique_ptr<RunTimeString> z)
: Action("expirevar", RunTimeOnlyIfMatchKind),
m_string(std::move(z)) { }

bool evaluate(RuleWithActions *rule, Transaction *transaction) override;
bool init(std::string *error) override;

private:

std::unique_ptr<RunTimeString> m_string;
};

} // namespace actions
} // namespace modsecurity


#endif // SRC_ACTIONS_EXPIRE_VAR_H_
140 changes: 140 additions & 0 deletions src/collection/backend/collection_data.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
* ModSecurity, http://www.modsecurity.org/
* Copyright (c) 2023 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 "src/collection/backend/collection_data.h"


namespace modsecurity {
namespace collection {
namespace backend {


bool CollectionData::isExpired() const {
if (m_hasExpiryTime == false) {
return false;
}
auto now = std::chrono::system_clock::now();
return (now >= m_expiryTime);
}


void CollectionData::setExpiry(int32_t seconds_until_expiry) {
m_expiryTime = std::chrono::system_clock::now() + std::chrono::seconds(seconds_until_expiry);
m_hasExpiryTime = true;
}

std::string CollectionData::getSerialized() const {
std::string serialized;
if (hasValue()) {
serialized.reserve(30 + 10 + getValue().size());
} else {
serialized.reserve(16+10);
}

serialized.assign("{");

if (hasExpiry()) {
serialized.append("\"__expire_\":");
uint64_t expiryEpochSeconds = std::chrono::duration_cast<std::chrono::seconds>(m_expiryTime.time_since_epoch()).count();
serialized.append(std::to_string(expiryEpochSeconds));
if (hasValue()) {
serialized.append(",");
}
}
if (hasValue()) {
serialized.append("\"__value_\":\"");
serialized.append(getValue());
serialized.append("\"");
}

serialized.append("}");

return serialized;
}

void CollectionData::setFromSerialized(const char* serializedData, size_t length) {
const static std::string expiryPrefix("\"__expire_\":");
const static std::string valuePrefix("\"__value_\":\"");
m_hasValue = false;
m_hasExpiryTime = false;

std::string serializedString(serializedData, length);
if ((serializedString.find("{") == 0) && (serializedString.substr(serializedString.length()-1) == "}")) {
size_t currentPos = 1;
uint64_t expiryEpochSeconds = 0;
bool invalidSerializedFormat = false;
bool doneParsing = false;

// Extract the expiry time, if it exists
if (serializedString.find(expiryPrefix, currentPos) == currentPos) {
currentPos += expiryPrefix.length();
std::string expiryDigits = serializedString.substr(currentPos, 10);
if (expiryDigits.find_first_not_of("0123456789") == std::string::npos) {
expiryEpochSeconds = strtoll(expiryDigits.c_str(), NULL, 10);
} else {
invalidSerializedFormat = true;
}
currentPos += 10;
}

if ((!invalidSerializedFormat) && (expiryEpochSeconds > 0)) {
if (serializedString.find(",", currentPos) == currentPos) {
currentPos++;
} else if (currentPos == serializedString.length()-1) {
doneParsing = true;
} else {
invalidSerializedFormat = true;
}
}

if ((!invalidSerializedFormat) && (!doneParsing)) {
// Extract the value
if ((serializedString.find(valuePrefix, currentPos) == currentPos)) {
currentPos += valuePrefix.length();
size_t expectedCloseQuotePos = serializedString.length() - 2;
if ((serializedString.substr(expectedCloseQuotePos, 1) == "\"") && (expectedCloseQuotePos >= currentPos)) {
m_value = serializedString.substr(currentPos);
m_value.resize(m_value.length()-2);
m_hasValue = true;
} else {
invalidSerializedFormat = true;
}
} else {
invalidSerializedFormat = true;
}
}

// Set the object's expiry time, if we found one
if ((!invalidSerializedFormat) && (expiryEpochSeconds > 0)) {
std::chrono::seconds expiryDuration(expiryEpochSeconds);
std::chrono::system_clock::time_point expiryTimePoint(expiryDuration);
m_expiryTime = expiryTimePoint;
m_hasExpiryTime = true;
}
if (!invalidSerializedFormat) {
return;
}
}

// this is the residual case; the entire string is a simple value (not JSON-ish encoded)
// the foreseen case here is lmdb content from prior to the serialization support
m_value.assign(serializedData, length);
m_hasValue = true;
return;
}

} // namespace backend
} // namespace collection
} // namespace modsecurity
Loading

0 comments on commit 2fcd373

Please sign in to comment.