Skip to content

Commit

Permalink
Merge pull request #2736 from brandonpayton/add-regex-match-limits-an…
Browse files Browse the repository at this point in the history
…d-error-reporting

Add isolated PCRE match limits as a layer of ReDoS defense
  • Loading branch information
martinhsv committed May 9, 2023
2 parents 62bbd7b + d875738 commit 09a135b
Show file tree
Hide file tree
Showing 19 changed files with 7,831 additions and 7,360 deletions.
2 changes: 2 additions & 0 deletions headers/modsecurity/rules_set_properties.h
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ class RulesSetProperties {
from->m_responseBodyLimitAction,
PropertyNotSetBodyLimitAction);

to->m_pcreMatchLimit.merge(&from->m_pcreMatchLimit);
to->m_uploadFileLimit.merge(&from->m_uploadFileLimit);
to->m_uploadFileMode.merge(&from->m_uploadFileMode);
to->m_uploadDirectory.merge(&from->m_uploadDirectory);
Expand Down Expand Up @@ -470,6 +471,7 @@ class RulesSetProperties {
ConfigDouble m_requestBodyLimit;
ConfigDouble m_requestBodyNoFilesLimit;
ConfigDouble m_responseBodyLimit;
ConfigInt m_pcreMatchLimit;
ConfigInt m_uploadFileLimit;
ConfigInt m_uploadFileMode;
DebugLog *m_debugLog;
Expand Down
4 changes: 4 additions & 0 deletions headers/modsecurity/transaction.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ class TransactionAnchoredVariables {
m_variableInboundDataError(t, "INBOUND_DATA_ERROR"),
m_variableMatchedVar(t, "MATCHED_VAR"),
m_variableMatchedVarName(t, "MATCHED_VAR_NAME"),
m_variableMscPcreError(t, "MSC_PCRE_ERROR"),
m_variableMscPcreLimitsExceeded(t, "MSC_PCRE_LIMITS_EXCEEDED"),
m_variableMultipartBoundaryQuoted(t, "MULTIPART_BOUNDARY_QUOTED"),
m_variableMultipartBoundaryWhiteSpace(t,
"MULTIPART_BOUNDARY_WHITESPACE"),
Expand Down Expand Up @@ -219,6 +221,8 @@ class TransactionAnchoredVariables {
AnchoredVariable m_variableInboundDataError;
AnchoredVariable m_variableMatchedVar;
AnchoredVariable m_variableMatchedVarName;
AnchoredVariable m_variableMscPcreError;
AnchoredVariable m_variableMscPcreLimitsExceeded;
AnchoredVariable m_variableMultipartBoundaryQuoted;
AnchoredVariable m_variableMultipartBoundaryWhiteSpace;
AnchoredVariable m_variableMultipartCrlfLFLines;
Expand Down
28 changes: 26 additions & 2 deletions src/operators/rx.cc
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,36 @@ bool Rx::evaluate(Transaction *transaction, RuleWithActions *rule,
re = m_re;
}

std::vector<Utils::SMatchCapture> captures;
if (re->hasError()) {
ms_dbg_a(transaction, 3, "Error with regular expression: \"" + re->pattern + "\"");
return false;
}
re->searchOneMatch(input, captures);

Utils::RegexResult regex_result;
std::vector<Utils::SMatchCapture> captures;

if (transaction && transaction->m_rules->m_pcreMatchLimit.m_set) {
unsigned long match_limit = transaction->m_rules->m_pcreMatchLimit.m_value;
regex_result = re->searchOneMatch(input, captures, match_limit);
} else {
regex_result = re->searchOneMatch(input, captures);
}

// FIXME: DRY regex error reporting. This logic is currently duplicated in other operators.
if (regex_result != Utils::RegexResult::Ok) {
transaction->m_variableMscPcreError.set("1", transaction->m_variableOffset);

std::string regex_error_str = "OTHER";
if (regex_result == Utils::RegexResult::ErrorMatchLimit) {
regex_error_str = "MATCH_LIMIT";
transaction->m_variableMscPcreLimitsExceeded.set("1", transaction->m_variableOffset);
}

ms_dbg_a(transaction, 1, "rx: regex error '" + regex_error_str + "' for pattern '" + re->pattern + "'");


return false;
}

if (rule && rule->hasCaptureAction() && transaction) {
for (const Utils::SMatchCapture& capture : captures) {
Expand Down
24 changes: 23 additions & 1 deletion src/operators/rx_global.cc
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,30 @@ bool RxGlobal::evaluate(Transaction *transaction, RuleWithActions *rule,
re = m_re;
}

Utils::RegexResult regex_result;
std::vector<Utils::SMatchCapture> captures;
re->searchGlobal(input, captures);
if (transaction && transaction->m_rules->m_pcreMatchLimit.m_set) {
unsigned long match_limit = transaction->m_rules->m_pcreMatchLimit.m_value;
regex_result = re->searchGlobal(input, captures, match_limit);
} else {
regex_result = re->searchGlobal(input, captures);
}

// FIXME: DRY regex error reporting. This logic is currently duplicated in other operators.
if (regex_result != Utils::RegexResult::Ok) {
transaction->m_variableMscPcreError.set("1", transaction->m_variableOffset);

std::string regex_error_str = "OTHER";
if (regex_result == Utils::RegexResult::ErrorMatchLimit) {
regex_error_str = "MATCH_LIMIT";
transaction->m_variableMscPcreLimitsExceeded.set("1", transaction->m_variableOffset);
}

ms_dbg_a(transaction, 1, "rxGlobal: regex error '" + regex_error_str + "' for pattern '" + re->pattern + "'");

return false;
}


if (rule && rule->hasCaptureAction() && transaction) {
for (const Utils::SMatchCapture& capture : captures) {
Expand Down
2 changes: 1 addition & 1 deletion src/parser/location.hh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// A Bison parser, made by GNU Bison 3.7.6.
// A Bison parser, made by GNU Bison 3.8.2.

// Locations for Bison parsers in C++

Expand Down
2 changes: 1 addition & 1 deletion src/parser/position.hh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// A Bison parser, made by GNU Bison 3.7.6.
// A Bison parser, made by GNU Bison 3.8.2.

// Starting with Bison 3.2, this file is useless: the structure it
// used to define is now defined in "location.hh".
Expand Down

0 comments on commit 09a135b

Please sign in to comment.