Conversation
Error Resolution infers a deserializer from an error that a serialized payload makes disappear. That inference needs the baseline error to be an application-level failure to interpret the payload, and needs the error to hold for as long as the test runs. Neither was checked. - Gate the technique on an allowlist of baseline statuses. A redirect is not an error, 404 is routing, 401/403/429 are policy, and 5xx above 500 is upstream infrastructure. - Replace the single control re-send and the separate scrambled-header twin probe with one trial that samples payload, twin and control together over several rounds. Server state that varies over seconds makes consecutive samples of one request correlated, so the arms have to be interleaved to be comparable. An unanswered sample is now inconclusive rather than confirmation. - Track the single-language-family invariant per host rather than per parameter, and cap how many parameters per host may report the technique. - Drop an error string too generic to tell a deserialization failure from a rejected login. The baseline status guards previously sat on the payload loop, so they also disabled Differential Error Analysis. That technique now runs whatever status the baseline carries.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## dev #3427 +/- ##
======================================
+ Coverage 90% 90% +1%
======================================
Files 454 454
Lines 47081 47177 +96
======================================
+ Hits 42316 42425 +109
+ Misses 4765 4752 -13 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Contributor
📊 Performance Benchmark Report
📈 Detailed Results (All Benchmarks)
🎯 Performance Summary✅ No significant performance changes detected (all changes <10%) 🐍 Python Version 3.11.16 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The serial submodule's "Error Resolution" check works like this: put garbage in a parameter, get an error back, then send a real serialized object instead. If the error goes away, maybe the app deserialized it.
For that to mean anything, three things have to be true:
Only the third one was actually checked. This handles the other two.
Changes
Only treat a 500 as an error worth resolving. Any baseline status used to count. A 302 isn't an error, it's the app redirecting you. A 404 means the route didn't match. A 502 or 503 comes from a proxy or CDN having a bad moment, not from the app parsing anything. None of those can be "resolved" by deserializing something. This also replaces two one-off status checks with a single list.
Send all three requests together, twice. We used to re-send the control once to check the error was still there, then send the corrupted twin once, both after the fact. If a server flips between working and failing over a few seconds (rate limiting, a flaky CDN, an origin restarting), sending the same request again tells you nothing, because you're still sampling the same bad moment. Now the payload, the twin and the control all go out together, twice, and it has to come out the same way both times. Also, a request that never comes back used to count as "yep, still broken"; now it throws the candidate away.
Track the one-language rule per host instead of per parameter. A real sink speaks one format. A host that looks like it deserializes Java and PHP and Python isn't deserializing anything. We already threw those out, but only when they turned up on the same parameter, so a host that spread them across several parameters kept all of them. It's now tracked for the whole host, plus a cap on how many parameters on one host can report this at all.
Dropped an error string that was too generic. One of the strings in
SERIALIZATION_ERRORSshows up on any login form that rejects a bad username, so it couldn't tell a broken deserializer from a wrong password.A bug found along the way
The baseline status checks were
continueon the payload loop, so they skipped the whole rest of the loop body, Differential Error Analysis included. Their comments only mentioned Error Resolution, but a real Java error string was unreportable whenever the baseline came back with the wrong status. Differential Error Analysis doesn't care what the baseline status is, it just needs a framework error string in the response that isn't in the baseline, so it now runs either way.What this costs us
We'll miss a sink whose error shows up as a 502 through a proxy, and we'll miss one that behaves differently every time you hit it. Fair trade for a LOW confidence check. The OOB interactsh payloads are the ones that actually prove deserialization, they emit CONFIRMED, and nothing here touches them.
Tests
125 pass on
test_module_lightfuzz.py. Six new tests, one per point above. I checked that each one fails if you revert the source and keep the tests, so they're testing the fix rather than just passing.Two existing tests needed changing: