Skip to content

V3/dev/action expirevar #3001

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 [email protected].
*
*/

#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 [email protected].
*
*/

#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 [email protected].
*
*/

#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