diff --git a/headers/modsecurity/modsecurity.h b/headers/modsecurity/modsecurity.h index 14e8de2c8b..0f31107ceb 100644 --- a/headers/modsecurity/modsecurity.h +++ b/headers/modsecurity/modsecurity.h @@ -229,6 +229,9 @@ namespace modsecurity { namespace actions { class Action; } +namespace intervention { +class LogPayloadAccess; +} class RuleWithOperator; #ifdef __cplusplus @@ -292,6 +295,23 @@ class ModSecurity { */ void setServerLogCb(ModSecLogCb cb, int properties); + /** + * Controls whether disruptive interventions include the human-readable + * ModSecurityIntervention::log payload. + * + * The payload is enabled by default. Connectors that do not consume it + * may disable it before processing transactions to avoid formatting and + * copying unused text. This setting does not affect disruptive actions, + * status codes, redirect URLs, server log callbacks, debug logs, or audit + * logs. + * + * This setting applies to the ModSecurity instance and every transaction + * created from it; it is not a per-transaction or per-rule option. + * Configure it before publishing the instance to worker threads. Changing + * it while transactions are being processed is not thread-safe. + */ + void setInterventionLogPayloadEnabled(bool enabled); + void serverLog(void *data, const RuleMessage &rm); const std::string& getConnectorInformation() const; @@ -306,6 +326,14 @@ class ModSecurity { collection::Collection *m_user_collection; private: + friend class intervention::LogPayloadAccess; + // Reserved internal bit; it must not be exposed as a LogProperty. + static constexpr int InterventionLogPayloadDisabledMask = 0x40000000; + static_assert((InterventionLogPayloadDisabledMask + & (TextLogProperty | RuleMessageLogProperty + | IncludeFullHighlightLogProperty)) == 0, + "intervention log payload state must not overlap public " + "log properties"); std::string m_connector; std::string m_whoami; ModSecLogCb m_logCb; @@ -327,6 +355,23 @@ const char *msc_who_am_i(ModSecurity *msc); void msc_set_connector_info(ModSecurity *msc, const char *connector); /** @ingroup ModSecurity_C_API */ void msc_set_log_cb(ModSecurity *msc, ModSecLogCb cb); +/** + * @ingroup ModSecurity_C_API + * + * Controls whether disruptive interventions include the human-readable + * ModSecurityIntervention::log payload. A zero value disables the payload; + * any nonzero value enables it. The payload is enabled by default. + * + * This setting applies to the ModSecurity instance and every transaction + * created from it; it is not a per-transaction or per-rule option. Configure + * it before publishing the instance to worker threads. It does not affect + * disruptive actions, status codes, redirect URLs, server log callbacks, + * debug logs, or audit logs. + * + * @param msc A non-NULL ModSecurity instance. + * @param enabled Zero to disable the payload, nonzero to enable it. + */ +void msc_set_intervention_log_payload_enabled(ModSecurity *msc, int enabled); /** @ingroup ModSecurity_C_API */ void msc_cleanup(ModSecurity *msc); diff --git a/src/Makefile.am b/src/Makefile.am index 7154215633..21dd2876f4 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -256,6 +256,7 @@ libmodsecurity_la_SOURCES = \ parser/seclang-parser.cc \ parser/seclang-scanner.cc \ parser/driver.cc \ + intervention_log.h \ transaction.cc \ anchored_set_variable.cc \ anchored_variable.cc \ diff --git a/src/actions/disruptive/deny.cc b/src/actions/disruptive/deny.cc index 038c8e3d8d..4aee909681 100644 --- a/src/actions/disruptive/deny.cc +++ b/src/actions/disruptive/deny.cc @@ -22,6 +22,7 @@ #include #include "modsecurity/transaction.h" +#include "src/intervention_log.h" namespace modsecurity { namespace actions { @@ -37,10 +38,8 @@ bool Deny::evaluate(RuleWithActions *rule, Transaction *transaction, } transaction->m_it.disruptive = true; - intervention::freeLog(&transaction->m_it); ruleMessage.m_isDisruptive = true; - transaction->m_it.log = strdup( - ruleMessage.log(RuleMessage::LogMessageInfo::ClientLogMessageInfo).c_str()); + intervention::setLogPayload(transaction, ruleMessage); return true; } diff --git a/src/actions/disruptive/drop.cc b/src/actions/disruptive/drop.cc index 3dc44b199f..646012a2bd 100644 --- a/src/actions/disruptive/drop.cc +++ b/src/actions/disruptive/drop.cc @@ -26,6 +26,7 @@ #include "modsecurity/rule.h" #include "src/utils/string.h" #include "modsecurity/modsecurity.h" +#include "src/intervention_log.h" namespace modsecurity { namespace actions { @@ -42,10 +43,8 @@ bool Drop::evaluate(RuleWithActions *rule, Transaction *transaction, } transaction->m_it.disruptive = true; - intervention::freeLog(&transaction->m_it); ruleMessage.m_isDisruptive = true; - transaction->m_it.log = strdup( - ruleMessage.log(RuleMessage::LogMessageInfo::ClientLogMessageInfo).c_str()); + intervention::setLogPayload(transaction, ruleMessage); return true; } diff --git a/src/actions/disruptive/redirect.cc b/src/actions/disruptive/redirect.cc index ee4f2de886..a799e4df0a 100644 --- a/src/actions/disruptive/redirect.cc +++ b/src/actions/disruptive/redirect.cc @@ -21,6 +21,7 @@ #include #include "modsecurity/transaction.h" +#include "src/intervention_log.h" #include "src/utils/string.h" namespace modsecurity { @@ -46,10 +47,8 @@ bool Redirect::evaluate(RuleWithActions *rule, Transaction *transaction, intervention::freeUrl(&transaction->m_it); transaction->m_it.url = strdup(m_urlExpanded.c_str()); transaction->m_it.disruptive = true; - intervention::freeLog(&transaction->m_it); ruleMessage.m_isDisruptive = true; - transaction->m_it.log = strdup( - ruleMessage.log(RuleMessage::LogMessageInfo::ClientLogMessageInfo).c_str()); + intervention::setLogPayload(transaction, ruleMessage); return true; } diff --git a/src/intervention_log.h b/src/intervention_log.h new file mode 100644 index 0000000000..e11040701d --- /dev/null +++ b/src/intervention_log.h @@ -0,0 +1,63 @@ +/* + * ModSecurity, http://www.modsecurity.org/ + * Copyright (c) 2026 OWASP ModSecurity Project + * + * 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 OWASP directly using + * the email address modsecurity@owasp.org. + * + */ + +#ifndef SRC_INTERVENTION_LOG_H_ +#define SRC_INTERVENTION_LOG_H_ + +#include + +#include "modsecurity/modsecurity.h" +#include "modsecurity/rule_message.h" +#include "modsecurity/transaction.h" + +namespace modsecurity { + +namespace intervention { + +class LogPayloadAccess { + public: + static bool isEnabled(const ModSecurity *modsecurity) { + return (modsecurity->m_logProperties + & ModSecurity::InterventionLogPayloadDisabledMask) == 0; + } +}; + + +static inline void setLogPayload(Transaction *transaction, + const RuleMessage &message) { + freeLog(&transaction->m_it); + if (!LogPayloadAccess::isEnabled(transaction->m_ms)) { + return; + } + + transaction->m_it.log = strdup( + message.log(RuleMessage::LogMessageInfo::ClientLogMessageInfo).c_str()); +} + + +static inline void setLogPayload(Transaction *transaction, + const char *message) { + freeLog(&transaction->m_it); + if (!LogPayloadAccess::isEnabled(transaction->m_ms)) { + return; + } + + transaction->m_it.log = strdup(message); +} + +} // namespace intervention +} // namespace modsecurity + +#endif // SRC_INTERVENTION_LOG_H_ diff --git a/src/modsecurity.cc b/src/modsecurity.cc index 487efa4af5..d569899ee1 100644 --- a/src/modsecurity.cc +++ b/src/modsecurity.cc @@ -393,9 +393,20 @@ void ModSecurity::setServerLogCb(ModSecLogCb cb) { void ModSecurity::setServerLogCb(ModSecLogCb cb, int properties) { // cppcheck-suppress funcArgNamesDifferentUnnamed - this is a false positive m_logCb = (ModSecLogCb) cb; - m_logProperties = properties; + m_logProperties = (m_logProperties & InterventionLogPayloadDisabledMask) + | (properties & ~InterventionLogPayloadDisabledMask); } + +void ModSecurity::setInterventionLogPayloadEnabled(bool enabled) { + if (enabled) { + m_logProperties &= ~InterventionLogPayloadDisabledMask; + } else { + m_logProperties |= InterventionLogPayloadDisabledMask; + } +} + + /** * @name msc_set_log_cb * @brief Set the log callback functiond @@ -412,6 +423,25 @@ extern "C" void msc_set_log_cb(ModSecurity *msc, ModSecLogCb cb) { msc->setServerLogCb(cb); } + +/** + * @name msc_set_intervention_log_payload_enabled + * @brief Control generation of the intervention log payload. + * + * Connectors that do not consume ModSecurityIntervention::log may disable it + * to avoid formatting and copying unused text. The setting applies to the + * ModSecurity instance and every transaction created from it. Configure it + * before publishing the instance to worker threads. + * + * @param msc A non-NULL ModSecurity instance. + * @param enabled Zero to disable the payload, nonzero to enable it. + * + */ +extern "C" void msc_set_intervention_log_payload_enabled(ModSecurity *msc, + int enabled) { + msc->setInterventionLogPayloadEnabled(enabled != 0); +} + /** * @name msc_set_connector_info * @brief Set information about the connector that is using the library. diff --git a/src/transaction.cc b/src/transaction.cc index 8a83e12f39..179c45c838 100644 --- a/src/transaction.cc +++ b/src/transaction.cc @@ -34,6 +34,7 @@ #include "modsecurity/actions/action.h" #include "src/actions/disruptive/deny.h" +#include "src/intervention_log.h" #include "modsecurity/intervention.h" #include "modsecurity/modsecurity.h" #include "src/request_body_processor/multipart.h" @@ -953,8 +954,8 @@ int Transaction::appendRequestBody(const unsigned char *buf, size_t len) { "request"); if (getRuleEngineState() == RulesSet::EnabledRuleEngine) { intervention::free(&m_it); - m_it.log = strdup("Request body limit is marked to " \ - "reject the request"); + intervention::setLogPayload(this, + "Request body limit is marked to reject the request"); m_it.status = 403; m_it.disruptive = true; } else { @@ -1212,8 +1213,8 @@ int Transaction::appendResponseBody(const unsigned char *buf, size_t len) { "request"); if (getRuleEngineState() == RulesSet::EnabledRuleEngine) { intervention::free(&m_it); - m_it.log = strdup("Response body limit is marked to reject " \ - "the request"); + intervention::setLogPayload(this, + "Response body limit is marked to reject the request"); m_it.status = 403; m_it.disruptive = true; } else { diff --git a/test/common/modsecurity_test_context.h b/test/common/modsecurity_test_context.h index cd99e23ab9..edec9b9bd9 100644 --- a/test/common/modsecurity_test_context.h +++ b/test/common/modsecurity_test_context.h @@ -25,6 +25,10 @@ namespace modsecurity_test { &m_server_log); } + void reset_server_log_callback() { + m_modsec.setServerLogCb(logCb); + } + modsecurity::ModSecurity m_modsec; modsecurity::RulesSet m_modsec_rules; std::stringstream m_server_log; diff --git a/test/common/modsecurity_test_results.h b/test/common/modsecurity_test_results.h index 3c03b29275..c4cec39d0e 100644 --- a/test/common/modsecurity_test_results.h +++ b/test/common/modsecurity_test_results.h @@ -27,6 +27,7 @@ template class ModSecurityTestResults : public std::vector { std::string log_raw_debug_log; int status = 0; std::string location; + bool intervention_log_payload_present = false; }; } // namespace modsecurity_test diff --git a/test/regression/regression.cc b/test/regression/regression.cc index 5b1ca514e8..accffc52f5 100644 --- a/test/regression/regression.cc +++ b/test/regression/regression.cc @@ -105,6 +105,7 @@ void actions(ModSecurityTestResults *r, it.url = nullptr; } if (it.log != nullptr) { + r->intervention_log_payload_present = true; *serverLog << it.log; free(it.log); it.log = nullptr; @@ -162,6 +163,24 @@ void perform_unit_test(const ModSecurityTest &test, modsecurity_test::ModSecurityTestContext context("ModSecurity-regression v0.0.1-alpha" \ " (ModSecurity regression test utility)"); + if (t->intervention_log_payload_enabled.has_value()) { + const int enabled = t->intervention_log_payload_enabled.value(); + if (t->intervention_log_payload_api == "c") { + if (enabled != 0) { + modsecurity::msc_set_intervention_log_payload_enabled( + &context.m_modsec, 0); + } + modsecurity::msc_set_intervention_log_payload_enabled( + &context.m_modsec, enabled); + } else { + if (enabled != 0) { + context.m_modsec.setInterventionLogPayloadEnabled(false); + } + context.m_modsec.setInterventionLogPayloadEnabled(enabled != 0); + } + context.reset_server_log_callback(); + } + bool found = true; if (t->resource.empty() == false) { found = (std::find(resources.begin(), resources.end(), t->resource) @@ -354,6 +373,29 @@ void perform_unit_test(const ModSecurityTest &test, testRes->reason << KWHT << "Expecting: " << RESET \ << t->error_log + ""; testRes->passed = false; + } else if (!t->redirect_url.empty() + && r.location != t->redirect_url) { + if (test.m_automake_output) { + std::cout << ":test-result: FAIL " << filename \ + << ":" << t->name << std::endl; + } else { + std::cout << KRED << "failed!" << RESET << std::endl; + } + testRes->reason << "Redirect URL mismatch. expecting: " + << t->redirect_url << " got: " << r.location << std::endl; + testRes->passed = false; + } else if (t->intervention_log_payload_present.has_value() + && static_cast(r.intervention_log_payload_present) + != t->intervention_log_payload_present.value()) { + if (test.m_automake_output) { + std::cout << ":test-result: FAIL " << filename \ + << ":" << t->name << std::endl; + } else { + std::cout << KRED << "failed!" << RESET << std::endl; + } + testRes->reason << "Intervention log payload presence mismatch." + << std::endl; + testRes->passed = false; } else if (!t->audit_log.empty() && !contains(getAuditLogContent(modsec_transaction.m_rules->m_auditLog->m_path1), t->audit_log)) { if (test.m_automake_output) { std::cout << ":test-result: FAIL " << filename \ diff --git a/test/regression/regression_test.cc b/test/regression/regression_test.cc index 18f61b64dc..57b75b628b 100644 --- a/test/regression/regression_test.cc +++ b/test/regression/regression_test.cc @@ -119,6 +119,10 @@ std::unique_ptr RegressionTest::from_yajl_node(const yajl_val &n set_string_from_yajl(u->url, "url", key, val); set_string_from_yajl(u->resource, "resource", key, val); set_opt_int_from_yajl(u->github_issue, "github_issue", key, val); + set_opt_int_from_yajl(u->intervention_log_payload_enabled, + "intervention_log_payload_enabled", key, val); + set_string_from_yajl(u->intervention_log_payload_api, + "intervention_log_payload_api", key, val); if (strcmp(key, "client") == 0) { u->update_client_from_yajl_node(val); } @@ -211,6 +215,8 @@ void RegressionTest::update_expected_from_yajl_node(const yajl_val &val) { set_string_from_yajl(error_log, "error_log", key2, val2); set_int_from_yajl(http_code, "http_code", key2, val2); set_string_from_yajl(redirect_url, "redirect_url", key2, val2); + set_opt_int_from_yajl(intervention_log_payload_present, + "intervention_log_payload_present", key2, val2); set_string_from_yajl(parser_error, "parser_error", key2, val2); } } @@ -385,6 +391,10 @@ std::string RegressionTests::toJSON() const { gen_key_str_if_non_empty(g, "url", t->url); gen_key_str_if_non_empty(g, "resource", t->resource); gen_key_opt_int(g, "github_issue", t->github_issue); + gen_key_opt_int(g, "intervention_log_payload_enabled", + t->intervention_log_payload_enabled); + gen_key_str_if_non_empty(g, "intervention_log_payload_api", + t->intervention_log_payload_api); gen_string_view(g, "client"); yajl_gen_map_open(g); @@ -435,6 +445,8 @@ std::string RegressionTests::toJSON() const { gen_key_str_if_non_empty(g, "error_log", t->error_log); gen_key_int(g, "http_code", t->http_code); gen_key_str_if_non_empty(g, "redirect_url", t->redirect_url); + gen_key_opt_int(g, "intervention_log_payload_present", + t->intervention_log_payload_present); gen_key_str_if_non_empty(g, "parser_error", t->parser_error); yajl_gen_map_close(g); diff --git a/test/regression/regression_test.h b/test/regression/regression_test.h index 2446bc0923..977d1e0a07 100644 --- a/test/regression/regression_test.h +++ b/test/regression/regression_test.h @@ -46,6 +46,8 @@ class RegressionTest { int version_min = 0; std::optional version_max; std::optional github_issue; + std::optional intervention_log_payload_enabled; + std::string intervention_log_payload_api; std::vector> request_headers; std::vector> response_headers; @@ -77,6 +79,7 @@ class RegressionTest { int http_code = 0; std::string redirect_url; + std::optional intervention_log_payload_present; // fields for formatting JSON diff --git a/test/test-cases/regression/intervention-log.json b/test/test-cases/regression/intervention-log.json new file mode 100644 index 0000000000..c2c03149bd --- /dev/null +++ b/test/test-cases/regression/intervention-log.json @@ -0,0 +1,433 @@ +[ + { + "enabled": 1, + "version_min": 300000, + "title": "Intervention log payload is enabled by default", + "client": { + "ip": "192.0.2.1", + "port": 12345 + }, + "server": { + "ip": "192.0.2.2", + "port": 80 + }, + "request": { + "headers": { + "Host": "example.test", + "Content-Length": "0" + }, + "uri": "/", + "method": "GET", + "http_version": 1.1, + "body": [ + "" + ] + }, + "response": { + "headers": { + "Content-Type": "text/plain", + "Content-Length": "0" + }, + "body": [ + "" + ], + "protocol": "HTTP/1.1" + }, + "expected": { + "audit_log": "default intervention payload marker", + "debug_log": "Running action deny", + "error_log": "Access denied with code 451 \\(phase 1\\).*default intervention payload marker", + "http_code": 451, + "intervention_log_payload_present": 1 + }, + "rules": [ + "SecRuleEngine On", + "SecAuditEngine On", + "SecAuditLogParts ABHZ", + "SecAuditLogType Serial", + "SecAuditLog /tmp/modsecurity-intervention-log-test.log", + "SecAction \"id:100001,phase:1,deny,status:451,log,auditlog,msg:'default intervention payload marker'\"" + ] + }, + { + "enabled": 1, + "version_min": 300000, + "title": "C API treats any nonzero value as payload enabled", + "intervention_log_payload_enabled": 7, + "intervention_log_payload_api": "c", + "client": { + "ip": "192.0.2.1", + "port": 12345 + }, + "server": { + "ip": "192.0.2.2", + "port": 80 + }, + "request": { + "headers": { + "Host": "example.test", + "Content-Length": "0" + }, + "uri": "/", + "method": "GET", + "http_version": 1.1, + "body": [ + "" + ] + }, + "response": { + "headers": { + "Content-Type": "text/plain", + "Content-Length": "0" + }, + "body": [ + "" + ], + "protocol": "HTTP/1.1" + }, + "expected": { + "http_code": 454, + "intervention_log_payload_present": 1, + "error_log": "Access denied with code 454 \\(phase 1\\).*C API nonzero payload marker" + }, + "rules": [ + "SecRuleEngine On", + "SecAction \"id:100015,phase:1,deny,status:454,nolog,msg:'C API nonzero payload marker'\"" + ] + }, + { + "enabled": 1, + "version_min": 300000, + "title": "C++ API can re-enable the intervention log payload", + "intervention_log_payload_enabled": 1, + "intervention_log_payload_api": "cpp", + "client": { + "ip": "192.0.2.1", + "port": 12345 + }, + "server": { + "ip": "192.0.2.2", + "port": 80 + }, + "request": { + "headers": { + "Host": "example.test", + "Content-Length": "0" + }, + "uri": "/", + "method": "GET", + "http_version": 1.1, + "body": [ + "" + ] + }, + "response": { + "headers": { + "Content-Type": "text/plain", + "Content-Length": "0" + }, + "body": [ + "" + ], + "protocol": "HTTP/1.1" + }, + "expected": { + "http_code": 453, + "intervention_log_payload_present": 1, + "error_log": "re-enabled intervention payload marker" + }, + "rules": [ + "SecRuleEngine On", + "SecAction \"id:100014,phase:1,deny,status:453,nolog,msg:'re-enabled intervention payload marker'\"" + ] + }, + { + "enabled": 1, + "version_min": 300000, + "title": "C++ API omits only the intervention log payload", + "intervention_log_payload_enabled": 0, + "intervention_log_payload_api": "cpp", + "client": { + "ip": "192.0.2.1", + "port": 12345 + }, + "server": { + "ip": "192.0.2.2", + "port": 80 + }, + "request": { + "headers": { + "Host": "example.test", + "Content-Length": "0" + }, + "uri": "/", + "method": "GET", + "http_version": 1.1, + "body": [ + "" + ] + }, + "response": { + "headers": { + "Content-Type": "text/plain", + "Content-Length": "0" + }, + "body": [ + "" + ], + "protocol": "HTTP/1.1" + }, + "expected": { + "audit_log": "C\\+\\+ intervention payload marker", + "debug_log": "Running action deny", + "error_log": "C\\+\\+ server callback marker", + "http_code": 451, + "intervention_log_payload_present": 0 + }, + "rules": [ + "SecRuleEngine On", + "SecAuditEngine On", + "SecAuditLogParts ABHZ", + "SecAuditLogType Serial", + "SecAuditLog /tmp/modsecurity-intervention-log-test.log", + "SecAction \"id:100012,phase:1,pass,log,msg:'C++ server callback marker'\"", + "SecAction \"id:100002,phase:1,deny,status:451,log,auditlog,msg:'C++ intervention payload marker'\"" + ] + }, + { + "enabled": 1, + "version_min": 300000, + "title": "C API omits only the intervention log payload", + "intervention_log_payload_enabled": 0, + "intervention_log_payload_api": "c", + "client": { + "ip": "192.0.2.1", + "port": 12345 + }, + "server": { + "ip": "192.0.2.2", + "port": 80 + }, + "request": { + "headers": { + "Host": "example.test", + "Content-Length": "0" + }, + "uri": "/", + "method": "GET", + "http_version": 1.1, + "body": [ + "" + ] + }, + "response": { + "headers": { + "Content-Type": "text/plain", + "Content-Length": "0" + }, + "body": [ + "" + ], + "protocol": "HTTP/1.1" + }, + "expected": { + "debug_log": "Running action deny", + "error_log": "C API server callback marker", + "http_code": 452, + "intervention_log_payload_present": 0 + }, + "rules": [ + "SecRuleEngine On", + "SecAction \"id:100013,phase:1,pass,log,msg:'C API server callback marker'\"", + "SecAction \"id:100003,phase:1,deny,status:452,log,msg:'C API intervention payload marker'\"" + ] + }, + { + "enabled": 1, + "version_min": 300000, + "title": "Disabled intervention log payload preserves drop", + "intervention_log_payload_enabled": 0, + "intervention_log_payload_api": "cpp", + "client": { + "ip": "192.0.2.1", + "port": 12345 + }, + "server": { + "ip": "192.0.2.2", + "port": 80 + }, + "request": { + "headers": { + "Host": "example.test", + "Content-Length": "0" + }, + "uri": "/", + "method": "GET", + "http_version": 1.1, + "body": [ + "" + ] + }, + "response": { + "headers": { + "Content-Type": "text/plain", + "Content-Length": "0" + }, + "body": [ + "" + ], + "protocol": "HTTP/1.1" + }, + "expected": { + "debug_log": "Running action drop", + "http_code": 403, + "intervention_log_payload_present": 0 + }, + "rules": [ + "SecRuleEngine On", + "SecAction \"id:100004,phase:1,drop,nolog\"" + ] + }, + { + "enabled": 1, + "version_min": 300000, + "title": "Disabled intervention log payload preserves redirect", + "intervention_log_payload_enabled": 0, + "intervention_log_payload_api": "cpp", + "client": { + "ip": "192.0.2.1", + "port": 12345 + }, + "server": { + "ip": "192.0.2.2", + "port": 80 + }, + "request": { + "headers": { + "Host": "example.test", + "Content-Length": "0" + }, + "uri": "/", + "method": "GET", + "http_version": 1.1, + "body": [ + "" + ] + }, + "response": { + "headers": { + "Content-Type": "text/plain", + "Content-Length": "0" + }, + "body": [ + "" + ], + "protocol": "HTTP/1.1" + }, + "expected": { + "http_code": 302, + "redirect_url": "https://example.test/blocked", + "intervention_log_payload_present": 0 + }, + "rules": [ + "SecRuleEngine On", + "SecAction \"id:100005,phase:1,redirect:'https://example.test/blocked',nolog\"" + ] + }, + { + "enabled": 1, + "version_min": 300000, + "title": "Disabled intervention log payload preserves request body-limit rejection", + "intervention_log_payload_enabled": 0, + "intervention_log_payload_api": "cpp", + "client": { + "ip": "192.0.2.1", + "port": 12345 + }, + "server": { + "ip": "192.0.2.2", + "port": 80 + }, + "request": { + "headers": { + "Host": "example.test", + "Content-Type": "application/x-www-form-urlencoded", + "Content-Length": "6" + }, + "uri": "/", + "method": "POST", + "http_version": 1.1, + "body": [ + "abcdef" + ] + }, + "response": { + "headers": { + "Content-Type": "text/plain", + "Content-Length": "0" + }, + "body": [ + "" + ], + "protocol": "HTTP/1.1" + }, + "expected": { + "http_code": 403, + "intervention_log_payload_present": 0 + }, + "rules": [ + "SecRuleEngine On", + "SecRequestBodyAccess On", + "SecRequestBodyLimit 5", + "SecRequestBodyLimitAction Reject" + ] + }, + { + "enabled": 1, + "version_min": 300000, + "title": "Disabled intervention log payload preserves response body-limit rejection", + "intervention_log_payload_enabled": 0, + "intervention_log_payload_api": "cpp", + "client": { + "ip": "192.0.2.1", + "port": 12345 + }, + "server": { + "ip": "192.0.2.2", + "port": 80 + }, + "request": { + "headers": { + "Host": "example.test", + "Content-Length": "0" + }, + "uri": "/", + "method": "GET", + "http_version": 1.1, + "body": [ + "" + ] + }, + "response": { + "headers": { + "Content-Type": "text/plain", + "Content-Length": "6" + }, + "body": [ + "abcdef" + ], + "protocol": "HTTP/1.1" + }, + "expected": { + "http_code": 403, + "intervention_log_payload_present": 0 + }, + "rules": [ + "SecRuleEngine On", + "SecResponseBodyAccess On", + "SecResponseBodyMimeType text/plain", + "SecResponseBodyLimit 5", + "SecResponseBodyLimitAction Reject" + ] + } +] diff --git a/test/test-suite.in b/test/test-suite.in index ebda49fb81..4331829b30 100644 --- a/test/test-suite.in +++ b/test/test-suite.in @@ -77,6 +77,7 @@ TESTS+=test/test-cases/regression/issue-3340.json TESTS+=test/test-cases/regression/issue-394.json TESTS+=test/test-cases/regression/issue-849.json TESTS+=test/test-cases/regression/issue-960.json +TESTS+=test/test-cases/regression/intervention-log.json TESTS+=test/test-cases/regression/misc.json TESTS+=test/test-cases/regression/misc-variable-under-quotes.json TESTS+=test/test-cases/regression/offset-variable.json