Skip to content
Open
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
10 changes: 8 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,16 @@ jobs:
- {label: "wo ssdeep", opt: "--without-ssdeep" }
- {label: "with lmdb", opt: "--with-lmdb" }
- {label: "with pcre", opt: "--with-pcre" }
- {label: "with log message escape", opt: "--enable-log-message-escape" }
exclude:
- platform: {label: "x32"}
configure: {label: "wo geoip"}
- platform: {label: "x32"}
configure: {label: "wo ssdeep"}
- platform: {label: "x32"}
configure: {label: "with log message escape"}
- compiler: {label: "clang"}
configure: {label: "with log message escape"}
steps:
- name: Detect latest Lua dev package
id: detect_lua
Expand Down Expand Up @@ -99,7 +104,7 @@ jobs:
CC: ${{ matrix.compiler.cc }}
CXX: ${{ matrix.compiler.cxx }}
run: ./configure ${{ matrix.platform.configure }} ${{ matrix.configure.opt }} --enable-assertions=yes
- uses: ammaraskar/gcc-problem-matcher@master
- uses: ammaraskar/gcc-problem-matcher@cb2e3f949c41b7818628f35f8484a9b0acf90cf9
- name: make
run: make -j `nproc`
- name: check
Expand All @@ -122,6 +127,7 @@ jobs:
- {label: "wo maxmind", opt: "-DWITH_MAXMIND=OFF" }
- {label: "wo libxml", opt: "-DWITH_LIBXML2=OFF" }
- {label: "with lmdb", opt: "-DWITH_LMDB=ON" }
- {label: "with log message escape", opt: "-DLOG_MESSAGE_ESCAPE=ON" }
steps:
- uses: actions/checkout@v6
with:
Expand All @@ -131,7 +137,7 @@ jobs:
run: |
pip3 install conan --upgrade
conan profile detect
- uses: ammaraskar/msvc-problem-matcher@master
- uses: ammaraskar/msvc-problem-matcher@47189a176388253d16c6f0b79eee6274512b761a
- name: Build ${{ matrix.configuration }} ${{ matrix.platform.arch }} ${{ matrix.configure.label }}
shell: cmd
run: vcbuild.bat ${{ matrix.configuration }} ${{ matrix.platform.arch }} NO_ASAN "${{ matrix.configure.opt }}"
Expand Down
12 changes: 9 additions & 3 deletions .github/workflows/ci_new.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ jobs:
- { label: "with lmdb", opt: "--with-lmdb" }
- { label: "with pcre2 (default)", opt: "" }
- { label: "with pcre", opt: "--with-pcre" }
- { label: "with log message escape", opt: "--enable-log-message-escape" }

exclude:
- compiler: { label: "clang" }
configure: { label: "with log message escape" }

steps:
- uses: actions/checkout@v6
Expand Down Expand Up @@ -106,7 +111,7 @@ jobs:
CXX: ${{ matrix.compiler.cxx }}
run: ./configure ${{ matrix.platform.configure }} ${{ matrix.configure.opt }} --enable-assertions=yes

- uses: ammaraskar/gcc-problem-matcher@master
- uses: ammaraskar/gcc-problem-matcher@cb2e3f949c41b7818628f35f8484a9b0acf90cf9

- name: Compile
run: make -j "$(nproc)"
Expand Down Expand Up @@ -162,7 +167,7 @@ jobs:
- name: Configure
run: ./configure ${{ matrix.configure.opt }} --enable-assertions=yes

- uses: ammaraskar/gcc-problem-matcher@master
- uses: ammaraskar/gcc-problem-matcher@cb2e3f949c41b7818628f35f8484a9b0acf90cf9

- name: Compile
run: make -j "$(sysctl -n hw.logicalcpu)"
Expand All @@ -187,6 +192,7 @@ jobs:
- { label: "without maxmind", opt: "-DWITH_MAXMIND=OFF" }
- { label: "without libxml", opt: "-DWITH_LIBXML2=OFF" }
- { label: "with lmdb", opt: "-DWITH_LMDB=ON" }
- { label: "with log message escape", opt: "-DLOG_MESSAGE_ESCAPE=ON" }

steps:
- uses: actions/checkout@v6
Expand All @@ -199,7 +205,7 @@ jobs:
pip3 install conan --upgrade
conan profile detect

- uses: ammaraskar/msvc-problem-matcher@master
- uses: ammaraskar/msvc-problem-matcher@47189a176388253d16c6f0b79eee6274512b761a

- name: Build project
shell: cmd
Expand Down
29 changes: 29 additions & 0 deletions build/win32/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ option(WITH_LUA "Include LUA support" ON)
option(WITH_LIBXML2 "Include LibXML2 support" ON)
option(WITH_MAXMIND "Include MaxMind support" ON)
option(WITH_CURL "Include CURL support" ON)
option(LOG_MESSAGE_ESCAPE
"Escape quotes and backslashes in textual log message fields" OFF)

option(USE_ASAN "Build with Address Sanitizer" OFF)

Expand Down Expand Up @@ -126,6 +128,7 @@ enable_feature(HAVE_LUA ${WITH_LUA})
enable_feature(HAVE_LIBXML2 ${WITH_LIBXML2})
enable_feature(HAVE_MAXMIND ${WITH_MAXMIND})
enable_feature(HAVE_CURL ${WITH_CURL})
enable_feature(MSC_LOG_MESSAGE_ESCAPE ${LOG_MESSAGE_ESCAPE})

include(${CMAKE_CURRENT_LIST_DIR}/ConfigureChecks.cmake)

Expand Down Expand Up @@ -218,7 +221,33 @@ enable_testing()
file(READ ${BASE_DIR}/test/test-suite.in TEST_FILES_RAW)
string(REPLACE "\n" ";" TEST_FILES ${TEST_FILES_RAW})

set(SKIP_CONDITIONAL_TEST FALSE)
foreach(TEST_FILE ${TEST_FILES})
string(STRIP "${TEST_FILE}" TEST_FILE)

# Select the appropriate branch of the Automake conditional shared by
# the Autotools and CMake test suites.
if(TEST_FILE STREQUAL "if LOG_MESSAGE_ESCAPE")
if(LOG_MESSAGE_ESCAPE)
set(SKIP_CONDITIONAL_TEST FALSE)
else()
set(SKIP_CONDITIONAL_TEST TRUE)
endif()
continue()
elseif(TEST_FILE STREQUAL "else")
if(SKIP_CONDITIONAL_TEST)
set(SKIP_CONDITIONAL_TEST FALSE)
else()
set(SKIP_CONDITIONAL_TEST TRUE)
endif()
continue()
elseif(TEST_FILE STREQUAL "endif")
set(SKIP_CONDITIONAL_TEST FALSE)
continue()
elseif(SKIP_CONDITIONAL_TEST)
continue()
endif()

# ignore comment lines
string(FIND ${TEST_FILE} "#" is_comment)
if(NOT is_comment EQUAL 0)
Expand Down
5 changes: 4 additions & 1 deletion build/win32/config.h.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@
/* Define if libcurl is available */
#cmakedefine HAVE_CURL

/* Define to escape quotes and backslashes in textual log message fields */
#cmakedefine MSC_LOG_MESSAGE_ESCAPE 1

/* Name of package */
#define PACKAGE "@PACKAGE_NAME@"

Expand All @@ -89,4 +92,4 @@
#cmakedefine STDC_HEADERS
#endif

#endif // ndef MODSECURITY_CONFIG_H
#endif // ndef MODSECURITY_CONFIG_H
15 changes: 15 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -276,11 +276,19 @@ AC_SUBST([MSC_GIT_VERSION])

MSC_ARG_ENABLE_BOOL([assertions], [Turn on assertions feature: undefine NDEBUG], [false], [assertions])
MSC_ARG_ENABLE_BOOL([debug-logs], [Turn off the SecDebugLog feature], [true], [debugLogs])
MSC_ARG_ENABLE_BOOL([log-message-escape],
[Escape quotes and backslashes in textual log message fields],
[false], [logMessageEscape])
if test "$debugLogs" != "true"; then
MODSEC_NO_LOGS="-DNO_LOGS=1"
AC_SUBST(MODSEC_NO_LOGS)
fi

if test "$logMessageEscape" = "true"; then
AC_DEFINE([MSC_LOG_MESSAGE_ESCAPE], [1],
[Define to escape quotes and backslashes in textual log message fields])
fi

MSC_ARG_ENABLE_BOOL([afl-fuzz], [Turn on the afl fuzzer compilation utilities], [false], [aflFuzzer])
MSC_ARG_ENABLE_BOOL([examples], [Turn on the examples compilation (default option)], [true], [buildExamples])
MSC_ARG_ENABLE_BOOL([parser-generation], [Enables parser generation during the build], [false], [buildParser])
Expand Down Expand Up @@ -333,6 +341,7 @@ fi
AM_CONDITIONAL([EXAMPLES], [test $buildExamples = true])
AM_CONDITIONAL([BUILD_PARSER], [test $buildParser = true])
AM_CONDITIONAL([USE_MUTEX_ON_PM], [test $mutexPm = true])
AM_CONDITIONAL([LOG_MESSAGE_ESCAPE], [test $logMessageEscape = true])


# General link options
Expand Down Expand Up @@ -497,6 +506,12 @@ else
echo " + SecDebugLog ....disabled"
fi

if test "$logMessageEscape" = "true"; then
echo " + Log message escape ....enabled"
else
echo " + Log message escape ....disabled"
fi

if test "$aflFuzzer" = "true"; then
echo " + afl fuzzer ....enabled"
echo " ($FUZZ_CPPCFLAGS)"
Expand Down
11 changes: 11 additions & 0 deletions src/operators/operator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,17 @@ bool Operator::evaluateInternal(Transaction *transaction,
}


/**
* Returns the operator's custom match message, or builds the legacy message.
* Keep this representation independent of textual log escaping: it is also
* serialized as the JSON audit log's match value. RuleMessage::log applies
* optional quote and backslash escaping when producing textual output.
*
* @param t Transaction used to evaluate macro-expanded operator parameters.
* @param key Matched variable name, including its collection.
* @param value Matched variable value before transformations.
* @return Custom or generated match description in the legacy representation.
*/
std::string Operator::resolveMatchMessage(Transaction *t,
std::string key, std::string value) {
std::string ret = m_match_message;
Expand Down
71 changes: 62 additions & 9 deletions src/rule_message.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,37 @@
#include "modsecurity/rules_set.h"
#include "modsecurity/modsecurity.h"
#include "modsecurity/transaction.h"
#include "src/config.h"
#include "src/utils/string.h"

#ifdef MSC_LOG_MESSAGE_ESCAPE
constexpr bool kEscapeLogMessage = true;
#else
constexpr bool kEscapeLogMessage = false;
#endif

namespace modsecurity {


/**
* Builds the bracketed rule and transaction fields for a textual log message.
* With MSC_LOG_MESSAGE_ESCAPE, request-derived values escape quotes and
* backslashes before insertion so the field delimiters remain unchanged.
* URI and data limits are applied before escaping their values.
*
* @param rm Rule match and transaction whose details are formatted.
* @return Textual fields, including the leading space before the first field.
*/
std::string RuleMessage::_details(const RuleMessage &rm) {
std::string msg;

msg.append(" [file \"" + rm.m_rule.getFileName() + "\"]");
msg.append(" [line \"" + std::to_string(rm.m_rule.getLineNumber()) + "\"]");
msg.append(" [id \"" + std::to_string(rm.m_rule.m_ruleId) + "\"]");
msg.append(" [rev \"" + utils::string::toHexIfNeeded(rm.m_rule.m_rev, true) + "\"]");
msg.append(" [msg \"" + rm.m_message + "\"]");
msg.append(" [msg \"" +
utils::string::toHexIfNeeded(
rm.m_message, kEscapeLogMessage) + "\"]");
msg.append(" [data \"" + utils::string::toHexIfNeeded(utils::string::limitTo(200, rm.m_data), true) + "\"]");
msg.append(" [severity \"" +
std::to_string(rm.m_severity) + "\"]");
Expand All @@ -42,27 +60,56 @@ std::string RuleMessage::_details(const RuleMessage &rm) {
msg.append(" [tag \"" + utils::string::toHexIfNeeded(a, true) + "\"]");
}

msg.append(" [hostname \"" + rm.m_transaction.m_requestHostName \
+ "\"]");
msg.append(" [uri \"" + utils::string::limitTo(200, rm.m_transaction.m_uri_no_query_string_decoded) + "\"]");
msg.append(" [unique_id \"" + rm.m_transaction.m_id + "\"]");
msg.append(" [hostname \"" + utils::string::toHexIfNeeded(
rm.m_transaction.m_requestHostName, kEscapeLogMessage) + "\"]");
msg.append(" [uri \"" + utils::string::toHexIfNeeded(
utils::string::limitTo(200,
rm.m_transaction.m_uri_no_query_string_decoded),
kEscapeLogMessage) + "\"]");
msg.append(" [unique_id \"" + utils::string::toHexIfNeeded(
rm.m_transaction.m_id, kEscapeLogMessage) + "\"]");
msg.append(" [ref \"" + utils::string::limitTo(200, rm.m_reference) + "\"]");

return msg;
}


/**
* Builds the server-address, URI and transaction-ID fields for an error log.
* MSC_LOG_MESSAGE_ESCAPE enables quote and backslash escaping within values;
* the URI is limited before escaping to avoid cutting an escape sequence.
*
* @param rm Rule match providing the transaction details.
* @return Bracketed error-log tail with no leading space.
*/
std::string RuleMessage::_errorLogTail(const RuleMessage &rm) {
std::string msg;

msg.append("[hostname \"" + rm.m_transaction.m_serverIpAddress + "\"]");
msg.append(" [uri \"" + utils::string::limitTo(200, rm.m_transaction.m_uri_no_query_string_decoded) + "\"]");
msg.append(" [unique_id \"" + rm.m_transaction.m_id + "\"]");
msg.append("[hostname \"" + utils::string::toHexIfNeeded(
rm.m_transaction.m_serverIpAddress, kEscapeLogMessage) + "\"]");
msg.append(" [uri \"" + utils::string::toHexIfNeeded(
utils::string::limitTo(200,
rm.m_transaction.m_uri_no_query_string_decoded),
kEscapeLogMessage) + "\"]");
msg.append(" [unique_id \"" + utils::string::toHexIfNeeded(
rm.m_transaction.m_id, kEscapeLogMessage) + "\"]");

return msg;
}


/**
* Formats a rule match for textual logs without changing its stored contents.
* With MSC_LOG_MESSAGE_ESCAPE, the legacy match description is escaped as a
* whole, including backslashes in existing control-character representations.
* No limit is applied after escaping, so generated escapes remain complete.
* Bracketed fields are formatted separately to preserve their delimiters.
*
* @param rm Rule match to format; its contents remain available for JSON logs.
* @param props LogMessageInfo flags selecting optional client and tail fields.
* @param code Response status, or -1 to leave a status placeholder.
* @return Textual log entry with non-printable characters escaped.
*/
std::string RuleMessage::log(const RuleMessage &rm, int props, int code) {
std::string msg("");
msg.reserve(2048);
Expand All @@ -84,7 +131,13 @@ std::string RuleMessage::log(const RuleMessage &rm, int props, int code) {
msg.append("ModSecurity: Warning. ");
}

msg.append(rm.m_match);
// Escape the stored match only for textual output. Its legacy contents
// must remain unchanged for JSON audit logs and connector callbacks.
if (kEscapeLogMessage) {
msg.append(utils::string::toHexIfNeeded(rm.m_match, true));
} else {
msg.append(rm.m_match);
}
msg.append(_details(rm));

if (props & ErrorLogTailLogMessageInfo) {
Expand Down
2 changes: 1 addition & 1 deletion test/test-cases/regression/issue-1528.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
},
"expected": {
"debug_log": "Rule returned 1",
"error_log": "Matched \"Operator `Rx' with parameter `\\^attack\\$'",
"error_log": "Operator `Rx' with parameter `\\^attack\\$'",
"http_code": 200
},
"rules": [
Expand Down
Loading
Loading