fix: preserve value in t:base64Decode when input is not valid base64#3533
fix: preserve value in t:base64Decode when input is not valid base64#35330xst3m wants to merge 1 commit intoowasp-modsecurity:v3/masterfrom
Conversation
|
Thanks for the fix — the core logic (checking the mbedtls return code) is correct and the bug analysis is solid. One suggestion: rather than adding a new The only other caller is Leaving the broken
This keeps the API surface smaller and ensures all base64 decoding in the project handles errors properly. |
022dfb7 to
920e1ca
Compare
|
Good point — leaving the broken
|
|
Hi @0xst3m,
do you have any plan when could you add these changes? Also, could you merge the changes from the upstream's v3/master branch? Thank you! |
There was a problem hiding this comment.
Pull request overview
This PR adjusts Base64 decoding behavior so that t:base64Decode no longer destroys the input on invalid Base64, preserving the original value and reporting “no change” instead—preventing downstream transformation pipelines from becoming effectively dead code on non-Base64 input.
Changes:
- Add a boolean-returning Base64 decode helper that validates
mbedtls_base64_decode’s return code before modifying output. - Update
t:base64Decodeto preserve the original value and returnfalsewhen decoding fails. - Update REMOTE_USER parsing to use the new boolean decode API.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
src/variables/remote_user.cc |
Switches Basic auth decoding to a success-checked Base64 decode path. |
src/utils/base64.h |
Changes Base64 decode API surface (adds bool decode overload, removes prior overload). |
src/utils/base64.cc |
Implements the new boolean Base64 decode logic using mbedtls return codes. |
src/actions/transformations/base64_decode.cc |
Preserves value on invalid Base64 input by only assigning decoded output on success. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| static std::string decode(const std::string& data, bool forgiven); | ||
| static std::string decode(const std::string& data); | ||
| static bool decode(const std::string& data, std::string &out); | ||
| static std::string decode_forgiven(const std::string& data); |
There was a problem hiding this comment.
This change removes the Base64::decode(const std::string&) -> std::string overload and replaces it with a bool decode(const std::string&, std::string&). That is an API break for any in-tree or downstream code including this header, and it also conflicts with the PR description claiming the existing decode() API is unchanged. Consider keeping the original overload (possibly implemented in terms of the new boolean-returning function) and/or adding a new tryDecode-style method instead of changing the signature.
There was a problem hiding this comment.
Intentional per reviewer feedback, the broken single-arg overload is removed to prevent future misuse. All 3 callers are updated in this PR.
| bool Base64::decode(const std::string& data, std::string &out) { | ||
| size_t out_len = 0; | ||
| const auto *src = reinterpret_cast<const unsigned char *>(data.c_str()); | ||
| const size_t slen = strlen(data.c_str()); |
There was a problem hiding this comment.
Base64::decode(const std::string&, std::string&) uses strlen(data.c_str()) for the input length. Since data is a std::string, this will truncate at the first embedded NUL and can mis-handle binary data; it’s also unnecessary work. Prefer using data.size() (and data.data() if you don’t need a NUL terminator) for the length passed to mbedtls.
| const size_t slen = strlen(data.c_str()); | |
| const size_t slen = data.size(); |
There was a problem hiding this comment.
Kept strlen intentionally to preserve the original decode() behavior. Existing test case with embedded NUL depends on truncation at first NUL.
| std::string decoded; | ||
|
|
||
| if (Utils::Base64::decode(base64, decoded)) { | ||
| base64 = std::move(decoded); | ||
| } | ||
|
|
||
| if (const auto pos{base64.find(":")}; pos != std::string::npos) { | ||
| transaction->m_variableRemoteUser.assign(std::string(base64, 0, pos)); |
There was a problem hiding this comment.
In RemoteUser parsing, a failed Base64 decode now leaves base64 as the raw post-"Basic " header value, and the subsequent find(":") will treat inputs like Authorization: Basic user:pass (not base64 per RFC 7617) as valid credentials and set REMOTE_USER to user. To avoid spoofing/behavior regression, only attempt the ":" split/assignment when the Base64 decode succeeded (or clear/skip processing on decode failure).
|
@0xst3m please take a look at the Copilot notices too. |
920e1ca to
2ac76b0
Compare
Replace broken decode(string) with decode(string, string&) that checks the mbedtls return value. On invalid input, the original value is now preserved and transform returns false. Updated all callers including remote_user.cc.
2ac76b0 to
620b9ac
Compare
|
|
All done, the changes were already applied and I've rebased onto the latest v3/master. The PR now includes:
Ready for review. |



What
When
t:base64Decodeencounters non-base64 input, the transformation now preserves the original value unchanged and returnsfalse(no change), instead of silently replacing it with an empty string and returningtrue.Why
The
base64Helper()function insrc/utils/base64.cccallsmbedtls_base64_decodebut ignores its error code return value. It only checks theout_lenoutput parameter, which stays0when mbedtls returnsMBEDTLS_ERR_BASE64_INVALID_CHARACTER(-0x002C). This causesBase64Decode::transform()to unconditionally replace the variable's value with an empty string for any non-base64 input.Impact:
t:base64Decodein a pipeline run on an empty string — effectively dead code for non-base64 inputt:base64Decodefrom rules 934130/934131 as a workaround (coreruleset/coreruleset#3376)How
Added
Base64::tryDecode()which checks thembedtls_base64_decodereturn value before proceeding:MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL→ valid base64, proceed with decodefalse(value preserved)This is a whitelist approach — only the known-good return code proceeds. Any current or future error codes from mbedtls (including the new
MBEDTLS_ERR_ERROR_CORRUPTION_DETECTEDin mbedtls v4.x) are automatically handled.Files changed:
src/utils/base64.htryDecode()method declarationsrc/utils/base64.cctryDecode()implementationsrc/actions/transformations/base64_decode.cctryDecode(), preserve value on failureBackward compatibility:
Base64::decode()API is unchanged —remote_user.ccis not affectedReferences
Proposed test additions
For secrules-language-tests
transformations/base64Decode.json:{ "ret" : 0, "input" : "not;valid;base64", "type" : "tfn", "name" : "base64Decode", "output" : "not;valid;base64" }, { "ret" : 0, "input" : "hello world!", "type" : "tfn", "name" : "base64Decode", "output" : "hello world!" }