Fix predictable marker collision in EncodingPatternPreservation - #914
Fix predictable marker collision in EncodingPatternPreservation#914Socialpranker wants to merge 1 commit into
Conversation
EncodingPatternPreservation.captureAndReplaceMatches() previously used a fixed, publicly-known default marker (this class's simple name, "EncodingPatternPreservation") to temporarily stand in for matched content while the rest of the string is encoded. restoreOriginalContent() restores captured content in FIFO order using replaceFirst(marker, ...), matching the marker as plain literal text. If the input already contains that literal string ahead of the real matched content (e.g. CSSCodec.encode() on "EncodingPatternPreservation background:rgb(1,2,3)"), replaceFirst matches the attacker-supplied text instead of the real placeholder, silently desynchronizing every subsequent restoration and corrupting the encoded output. Fix: derive the default marker per-instance from a random UUID (with hyphens stripped, since a hyphen would itself be altered by an encoding pass such as CSSCodec's before restoration), so it cannot be predicted or embedded by input content. The public setReplacementMarker() API is unchanged. Added regression tests covering marker unpredictability and the literal-marker-in-input desync scenario.
c80d785 to
cd45dd2
Compare
|
Filed the underlying bug as #915 with repro details, per the issue → PR workflow described in CONTRIBUTING-TO-ESAPI.txt. |
|
Friendly ping — open since 14 July, with the underlying bug filed separately as #915 per CONTRIBUTING-TO-ESAPI.txt. It replaces the fixed, publicly-known default marker in No rush — just noting it's ready when there's time. Happy to adjust the approach if you'd prefer a different fix for #915. |
|
Following up on this and #915 — coming up on a month since filing, wanted to check it's not stuck somewhere. |
|
Looking at the code submitted, I think that the solution provided solves the issue identified. The only issue I see and I'm not sure about is if this utility is ever used to write data to a location outside of the ESAPI JVM (EG: written to a flat-file between sessions) then the new instance will not be able to restore data from the previous instance since the markers are now guaranteed not to match. I do think that what is provided does correct the problem as identified; however, I would like to get @xeno6696 to weigh in on the case above before merging. If there are any clients who do write data between sessions, then this addition would break backwards compatibility and potentially cause loss of data. |
|
I agree with @jeremiahjstacey that the fix here matches the identified problem, but I'm mulling over the corner case he brought up. I think it's worth calling out in the release notes, but the method So yeah, worth calling out, but I wouldn't think to push this any deeper. Created a "release-notes" label to flag any content that we want to ensure gets rolled into notes for the next release. |
xeno6696
left a comment
There was a problem hiding this comment.
Per agreement with @jeremiahjstacey, if you could just add the identified corner-case to the JavaDoc comment for the Codec I think release-notes should pick it up without additional fanfare.
There was a problem hiding this comment.
Thanks for the extra testing to boot, this was a high-quality first PR.
Summary
EncodingPatternPreservation(used byCSSCodecto protectrgb(...)triplets from CSS-escaping duringencodeForCSS) previously used a fixed, publicly-known default marker: this class's own simple name,"EncodingPatternPreservation".captureAndReplaceMatches()temporarily swaps matched content for this marker;restoreOriginalContent()restores it afterwards usingreplaceFirst(marker, ...), matching the marker as plain literal text, in FIFO order.Because the marker is a known constant, input that already contains that literal string ahead of real matched content causes
replaceFirstto match the attacker-supplied text instead of the real placeholder. Every subsequent restoration in the same call is then shifted by one, silently corrupting the output — restored content ends up in the wrong position (or is left as a raw, unrestored marker string).Minimal repro:
I want to be precise about severity: I checked whether this is exploitable as an injection/XSS primitive and don't believe it is — the only regex currently fed through this class (
CSSCodec's RGB-triplet pattern) can only ever capture digits,%, commas, whitespace andrgb(/), so a desynchronized restore can't smuggle unexpected characters throughencodeForCSS(). This is an output-integrity bug (silently wrong/corrupted encoder output), not a bypass of the encoding itself.Fix
Derive the default marker per-instance from a random UUID instead of a fixed literal, so it can't be predicted or pre-supplied in input. Hyphens are stripped from the UUID because a hyphen would itself be altered by an encoding pass (e.g.
CSSCodec.encode()backslash-escapes non-immune characters) beforerestoreOriginalContent()runs, which would break the exact-match lookup — I hit this while writing the fix and confirmed it against the existingCSSCodecTestsuite. The publicsetReplacementMarker()API is unchanged for callers who want to supply their own marker.Test plan
testDefaultMarkerIsUnpredictablePerInstance— two instances never produce the same default marker.testInputContainingLiteralClassNameDoesNotDesyncRestoration— regression test reproducing the exact scenario above.mvn -Dtest=EncodingPatternPreservationTest,CSSCodecTest test— 11/11 pass.mvn test— pass, no other suite affected.