From 85384febb7f327d174d88c5c0484c0d34344136b Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Thu, 13 Nov 2025 11:49:32 +0100 Subject: [PATCH 1/2] Encode possible binary characters from user input in audit logs --- src/operators/operator.cc | 4 +- src/transaction.cc | 4 +- test/test-cases/regression/auditlog.json | 59 ++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 4 deletions(-) diff --git a/src/operators/operator.cc b/src/operators/operator.cc index 674e82d3..935add62 100644 --- a/src/operators/operator.cc +++ b/src/operators/operator.cc @@ -112,7 +112,7 @@ std::string Operator::resolveMatchMessage(Transaction *t, if (m_couldContainsMacro == false) { ret = "Matched \"Operator `" + m_op + "' with parameter `" + utils::string::limitTo(200, m_param) + - "' against variable `" + key + "' (Value: `" + + "' against variable `" + utils::string::toHexIfNeeded(key) + "' (Value: `" + utils::string::limitTo(100, utils::string::toHexIfNeeded(value)) + \ "' )"; @@ -120,7 +120,7 @@ std::string Operator::resolveMatchMessage(Transaction *t, std::string p(m_string->evaluate(t)); ret = "Matched \"Operator `" + m_op + "' with parameter `" + utils::string::limitTo(200, p) + - "' against variable `" + key + "' (Value: `" + + "' against variable `" + utils::string::toHexIfNeeded(key) + "' (Value: `" + utils::string::limitTo(100, utils::string::toHexIfNeeded(value)) + "' )"; diff --git a/src/transaction.cc b/src/transaction.cc index 6e874561..6bbf17b4 100644 --- a/src/transaction.cc +++ b/src/transaction.cc @@ -1610,7 +1610,7 @@ std::string Transaction::toJSON(int parts) { if (parts & audit_log::AuditLog::CAuditLogPart) { // FIXME: check for the binary content size. - LOGFY_ADD("body", this->m_requestBody.str()); + LOGFY_ADD("body", utils::string::toHexIfNeeded(this->m_requestBody.str())); } /* request headers */ @@ -1712,7 +1712,7 @@ std::string Transaction::toJSON(int parts) { LOGFY_ADD("ruleId", std::to_string(a.m_rule.m_ruleId)); LOGFY_ADD("file", a.m_rule.getFileName()); LOGFY_ADD("lineNumber", std::to_string(a.m_rule.getLineNumber())); - LOGFY_ADD("data", a.m_data); + LOGFY_ADD("data", utils::string::toHexIfNeeded(a.m_data)); LOGFY_ADD("severity", std::to_string(a.m_severity)); LOGFY_ADD("ver", a.m_rule.m_ver); LOGFY_ADD("rev", a.m_rule.m_rev); diff --git a/test/test-cases/regression/auditlog.json b/test/test-cases/regression/auditlog.json index 58307df6..03c58732 100644 --- a/test/test-cases/regression/auditlog.json +++ b/test/test-cases/regression/auditlog.json @@ -536,5 +536,64 @@ "SecAuditLogType Serial", "SecAuditLogRelevantStatus \"^(?:5|4(?!04))\"" ] + }, + { + "enabled": 1, + "version_min": 300000, + "version_max": 0, + "title": "auditlog : Binary char from input", + "client": { + "ip": "200.249.12.31", + "port": 2313 + }, + "server": { + "ip": "200.249.12.31", + "port": 80 + }, + "request": { + "headers": { + "Host": "www.modsecurity.org", + "User-Agent": "Mozilla\/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko\/20091102 Firefox\/3.5.5 (.NET CLR 3.5.30729)", + "Accept": "text\/html,application\/xhtml+xml,application\/xml;q=0.9,*\/*;q=0.8", + "Accept-Language": "en-us,en;q=0.5", + "Accept-Encoding": "gzip,deflate", + "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7", + "Keep-Alive": "300", + "Connection": "keep-alive", + "Pragma": "no-cache", + "Cache-Control": "no-cache" + }, + "uri": "\/?%ADd+allow%3d1+%ADd+auto", + "method": "GET", + "http_version": 1.1, + "body": "" + }, + "response": { + "headers": { + "Content-Type": "plain\/text\n\r" + }, + "body": [ + "test" + ] + }, + "expected": { + "audit_log": "\"match\":\"Matched \\\\\"Operator `ValidateUtf8Encoding' with parameter `' against variable `ARGS_NAMES:\\\\\\\\xadd allow=1 \\\\\\\\xadd auto' \\(Value: `\\\\\\\\xadd allow=1 \\\\\\\\xadd auto' \\)\"", + "debug_log": "", + "error_log": "", + "http_code": 403 + }, + "rules": [ + "SecRuleEngine On", + "SecRule REQUEST_FILENAME|ARGS|ARGS_NAMES \"@validateUtf8Encoding\" \"id:920250,phase:2,deny,t:none,msg:'UTF8 Encoding Abuse Attack Attempt',logdata:'%{MATCHED_VAR}'", + "SecAuditEngine RelevantOnly", + "SecAuditLogParts ABHJZ", + "SecAuditLogFormat JSON", + "SecAuditLogStorageDir /tmp/test", + "SecAuditLog /tmp/audit_test_prefix.log", + "SecAuditLogDirMode 0766", + "SecAuditLogFileMode 0600", + "SecAuditLogType Serial", + "SecAuditLogRelevantStatus \"^(?:5|4(?!04))\"" + ] } ] From 102275fd01007ff18ca570a0f4de80d82e0a6435 Mon Sep 17 00:00:00 2001 From: Ervin Hegedus Date: Thu, 13 Nov 2025 23:09:14 +0100 Subject: [PATCH 2/2] Encode possible binary characters in headers; add more tests --- src/transaction.cc | 2 +- test/test-cases/regression/auditlog.json | 129 ++++++++++++++++++++++- 2 files changed, 129 insertions(+), 2 deletions(-) diff --git a/src/transaction.cc b/src/transaction.cc index 6bbf17b4..edb02f30 100644 --- a/src/transaction.cc +++ b/src/transaction.cc @@ -1622,7 +1622,7 @@ std::string Transaction::toJSON(int parts) { m_variableRequestHeaders.resolve(&l); for (auto &h : l) { - LOGFY_ADD(h->getKey().c_str(), h->getValue()); + LOGFY_ADD(utils::string::toHexIfNeeded(h->getKey().c_str()).c_str(), utils::string::toHexIfNeeded(h->getValue())); delete h; } diff --git a/test/test-cases/regression/auditlog.json b/test/test-cases/regression/auditlog.json index 03c58732..417f9950 100644 --- a/test/test-cases/regression/auditlog.json +++ b/test/test-cases/regression/auditlog.json @@ -541,7 +541,7 @@ "enabled": 1, "version_min": 300000, "version_max": 0, - "title": "auditlog : Binary char from input", + "title": "auditlog : Binary char from input, check message", "client": { "ip": "200.249.12.31", "port": 2313 @@ -595,5 +595,132 @@ "SecAuditLogType Serial", "SecAuditLogRelevantStatus \"^(?:5|4(?!04))\"" ] + }, + { + "enabled": 1, + "version_min": 300000, + "version_max": 0, + "title": "auditlog : Binary char from input, check body", + "client": { + "ip": "200.249.12.31", + "port": 2313 + }, + "server": { + "ip": "200.249.12.31", + "port": 80 + }, + "request": { + "headers": { + "Host": "www.modsecurity.org", + "User-Agent": "Mozilla\/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko\/20091102 Firefox\/3.5.5 (.NET CLR 3.5.30729)", + "Accept": "text\/html,application\/xhtml+xml,application\/xml;q=0.9,*\/*;q=0.8", + "Accept-Language": "en-us,en;q=0.5", + "Accept-Encoding": "gzip,deflate", + "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7", + "Keep-Alive": "300", + "Connection": "keep-alive", + "Pragma": "no-cache", + "Cache-Control": "no-cache", + "Content-Type": "application/x-www-form-urlencoded", + "Content-Length": "3" + }, + "uri": "\/?attack=true", + "method": "POST", + "http_version": 1.1, + "body": [ + "\u00ad=\u00ad" + ] + }, + "response": { + "headers": { + "Content-Type": "plain\/text\n\r" + }, + "body": [ + "test" + ] + }, + "expected": { + "audit_log": "\"body\":\"\\\\\\\\xc2\\\\\\\\xad=\\\\\\\\xc2\\\\\\\\xad\\\\\\\\x0a", + "debug_log": "", + "error_log": "", + "http_code": 403 + }, + "rules": [ + "SecRuleEngine On", + "SecRule ARGS_NAMES \"@rx attack\" \"id:1,phase:2,deny,t:none", + "SecAuditEngine RelevantOnly", + "SecAuditLogParts ABCHJZ", + "SecAuditLogFormat JSON", + "SecAuditLogStorageDir /tmp/test", + "SecAuditLog /tmp/audit_test_prefix.log", + "SecAuditLogDirMode 0766", + "SecAuditLogFileMode 0600", + "SecAuditLogType Serial", + "SecAuditLogRelevantStatus \"^(?:5|4(?!04))\"" + ] + }, + { + "enabled": 1, + "version_min": 300000, + "version_max": 0, + "title": "auditlog : Binary char from input, check header", + "client": { + "ip": "200.249.12.31", + "port": 2313 + }, + "server": { + "ip": "200.249.12.31", + "port": 80 + }, + "request": { + "headers": { + "Host": "www.modsecurity.org", + "User-Agent": "Mozilla\/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5) Gecko\/20091102 Firefox\/3.5.5 (.NET CLR 3.5.30729)", + "Accept": "text\/html,application\/xhtml+xml,application\/xml;q=0.9,*\/*;q=0.8", + "Accept-Language": "en-us,en;q=0.5", + "Accept-Encoding": "gzip,deflate", + "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.7", + "Keep-Alive": "300", + "Connection": "keep-alive", + "Pragma": "no-cache", + "Cache-Control": "no-cache", + "Content-Type": "application/x-www-form-urlencoded", + "Content-Length": "3", + "X-\u00ad-custom": "Some \u00ad value" + }, + "uri": "\/?attack=true", + "method": "POST", + "http_version": 1.1, + "body": [ + "\u00ad=\u00ad" + ] + }, + "response": { + "headers": { + "Content-Type": "plain\/text\n\r" + }, + "body": [ + "test" + ] + }, + "expected": { + "audit_log": "\"X-\\\\\\\\xc2\\\\\\\\xad-custom\":\"Some \\\\\\\\xc2\\\\\\\\xad value\"", + "debug_log": "", + "error_log": "", + "http_code": 403 + }, + "rules": [ + "SecRuleEngine On", + "SecRule ARGS|ARGS_NAMES \"@rx attack\" \"id:1,phase:2,deny,t:none", + "SecAuditEngine RelevantOnly", + "SecAuditLogParts ABHJZ", + "SecAuditLogFormat JSON", + "SecAuditLogStorageDir /tmp/test", + "SecAuditLog /tmp/audit_test_prefix.log", + "SecAuditLogDirMode 0766", + "SecAuditLogFileMode 0600", + "SecAuditLogType Serial", + "SecAuditLogRelevantStatus \"^(?:5|4(?!04))\"" + ] } ]