Update SQLi/XSS operators for libinjection v4.0.0 cleaned#3528
Update SQLi/XSS operators for libinjection v4.0.0 cleaned#3528Easton97-Jens wants to merge 16 commits intoowasp-modsecurity:v3/masterfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Updates ModSecurity’s @detectSQLi / @detectXSS operators to support libinjection v4’s injection_result_t return codes, including explicit fail-safe handling for parser errors, and expands regression coverage around detection/false-positive behavior.
Changes:
- Add shared helpers for interpreting libinjection
TRUE/FALSE/ERRORresults. - Update
DetectSQLi/DetectXSSto treatLIBINJECTION_RESULT_ERRORas a match and preserve capture behavior. - Expand regression test cases for multiple XSS/SQLi payloads and benign inputs.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
src/operators/libinjection_utils.h |
Adds shared helpers to map libinjection results to match/no-match semantics and diagnostic strings. |
src/operators/detect_xss.cc |
Switches XSS operator logic to injection_result_t and adds explicit handling for TRUE/FALSE/ERROR. |
src/operators/detect_sqli.cc |
Switches SQLi operator logic to injection_result_t, modernizes fingerprint storage, and handles TRUE/FALSE/ERROR. |
test/test-cases/regression/operator-detectxss.json |
Adds multiple positive and negative XSS regression cases. |
test/test-cases/regression/operator-detectsqli.json |
Adds multiple positive and negative SQLi regression cases (including fingerprint expectations). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…for-modsecurity-operators Add regression coverage for detectSQLi/detectXSS capture semantics
…for-libinjection-result_error Add libinjection adapter for test overrides and improve libinjection error handling
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (t.capture) { | ||
| auto tx0 = transaction.m_collections.m_tx_collection->resolveFirst("0"); | ||
| if (tx0 != nullptr) { | ||
| result.output = *tx0; | ||
| } |
There was a problem hiding this comment.
OperatorTest::eval reads TX.0 from the shared Transaction when capture is enabled. In mtstress mode the same transaction instance is shared across many threads, so concurrent writes/reads of the TX collection can cause data races and flaky/crashing unit tests (especially now that output comparisons are enforced). Consider using a per-thread Transaction in mtstress runs (or disabling capture/output assertions under mtstress) so TX.0 access is thread-safe/deterministic.
There was a problem hiding this comment.
even thought this case (shared transaction objects) is a bit far from the reality, I think it would be nice to find a solution to meet this expectation.
(Moreover, there is no mtstress option at all between test cases.)
Could you take a look at this?
If you think it's not necessarily now, we can skip now, and we might add this later (with new test cases...)
…transaction-usage Use ModSecurityTestContext to create per-thread transactions in multithreaded tests
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 17 out of 17 changed files in this pull request and generated 6 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| void setLibinjectionSQLiOverrideForTesting(DetectSQLiFn fn); | ||
| void setLibinjectionXSSOverrideForTesting(DetectXSSFn fn); | ||
| void clearLibinjectionOverridesForTesting(); |
There was a problem hiding this comment.
The functions setLibinjection*OverrideForTesting()/clearLibinjectionOverridesForTesting() are compiled into the production library and become part of the exported symbol surface, despite being test-only hooks. Consider compiling these APIs only for test builds (e.g., behind a dedicated macro) or otherwise hiding them, so production consumers can’t accidentally (or intentionally) alter libinjection behavior at runtime.
| void setLibinjectionSQLiOverrideForTesting(DetectSQLiFn fn); | |
| void setLibinjectionXSSOverrideForTesting(DetectXSSFn fn); | |
| void clearLibinjectionOverridesForTesting(); | |
| // Test-only hooks to override libinjection behavior. | |
| // These should be enabled only in test builds by defining | |
| // MODSECURITY_ENABLE_LIBINJECTION_TESTING_HOOKS. | |
| #ifdef MODSECURITY_ENABLE_LIBINJECTION_TESTING_HOOKS | |
| void setLibinjectionSQLiOverrideForTesting(DetectSQLiFn fn); | |
| void setLibinjectionXSSOverrideForTesting(DetectXSSFn fn); | |
| void clearLibinjectionOverridesForTesting(); | |
| #endif // MODSECURITY_ENABLE_LIBINJECTION_TESTING_HOOKS |
| [&item, &t, &result, &transaction]() | ||
| [&item, &t, &result, &context]() | ||
| { | ||
| auto transaction = context.create_transaction(); |
There was a problem hiding this comment.
In mtstress mode each thread now creates its own Transaction, but they still share the same ModSecurityTestContext instance. ModSecurityTestContext::create_transaction() passes a pointer to the shared m_server_log (std::stringstream) into each Transaction, and the log callback appends to it; this is not thread-safe and can still cause data races/flaky mtstress runs. Consider giving each thread its own log buffer (or disabling server-log capture during mtstress) to make the stress test deterministic and race-free.
| auto transaction = context.create_transaction(); | |
| // Use a per-thread copy of the test context so that each thread | |
| // has its own server log buffer, avoiding data races on m_server_log. | |
| auto localContext = context; | |
| auto transaction = localContext.create_transaction(); |
| ms_dbg_a(t, 4, | ||
| std::string("libinjection parser error during XSS analysis (") | ||
| + libinjectionResultToString(xss_result) | ||
| + "); treating as match (fail-safe). Input: " | ||
| + input); |
There was a problem hiding this comment.
The new LIBINJECTION_RESULT_ERROR debug message logs the full raw input at debug level 4. To reduce log-forging risk and avoid extremely large/binary log lines, please consider sanitizing/truncating the logged input (e.g., limit length and/or use the existing toHexIfNeeded/limitTo helpers used elsewhere for logging untrusted data).
| std::string("libinjection parser error during SQLi analysis (") | ||
| + libinjectionResultToString(sqli_result) | ||
| + "); treating as match (fail-safe). Input: '" | ||
| + input + "'"); |
There was a problem hiding this comment.
The new LIBINJECTION_RESULT_ERROR debug message includes the full raw input at debug level 4. Consider limiting/sanitizing what is logged (length cap and/or hex-escaping) to avoid log injection and excessive log volume on crafted inputs; other parts of the codebase use limitTo()/toHexIfNeeded() for this purpose.
| + input + "'"); | |
| + limitTo(toHexIfNeeded(input), 1000) + "'"); |
|



what
libinjectionreturn codes (injection_result_t).TRUE,FALSE, andERRORresults fromlibinjection_sqliandlibinjection_xss.LIBINJECTION_RESULT_ERRORas a fail-safe match to avoid missing potentially malicious input.TX.0whencaptureis enabled, even on parser errors.why
libinjectionintroducedinjection_result_t, requiring explicit handling in ModSecurity operators.references
libinjectionAPIs.