feat(scan): --content-type-confusion relabels Content-Type to bypass parser-dispatch authz#27
Merged
Merged
Conversation
…parser-dispatch authz
Where --parameter-pollution attacks which copy of a duplicated parameter each
layer reads, --host-header attacks which host the access-control layer believes
the request targets, and --xxe attacks how the XML parser resolves entities,
--content-type-confusion attacks which body parser each layer of the stack
chooses. The body bytes are kept byte-identical; only the Content-Type header is
mutated. The bug being tested: a fronting WAF/gateway short-circuits its body
inspection rules ("this is text/plain, no JSON to validate") while the handler
still parses the body as JSON, or the handler is wired to multiple parsers
(JSON / XML / form) with different authz middleware and is coerced onto the
alternate path.
Every variant keeps the caller's credentials (Identity == nil) and the original
body bytes; only the Content-Type header (and the captured ContentType field)
change. Three deterministic technique sets, one per body shape:
- JSON-shaped bodies (declared *json* or sniffed `{` / `[`):
as-form, as-text, as-xml, strip-type (drop the header).
- XML-shaped bodies (declared *xml* or sniffed `<?xml` / leading tag):
as-json, as-text.
- urlencoded form bodies: as-json only (the highest-signal mismatch; form
bodies are too easy to mis-sniff for a wider set).
The media-type comparison is parameter-insensitive
(application/json; charset=utf-8 == application/json) so a no-op relabel
(declared type already matches target) is skipped. Binary, multipart, and
empty bodies produce no variants. Findings are class authz-bypass
(ASVS V8.3.x, severity HIGH).
Off by default; threaded through buildRegistry as a 16th bool, mirroring the
pattern of --origin-spoof, --parameter-pollution, --header-injection,
--cookie-tampering, --host-header, --forbidden-bypass, --method-override,
--csrf-header, --ws-hijack, --xxe, and --mass-assign. Registered (inert when
disabled) so the DefaultRegistry order is unchanged.
Includes 14 new unit tests covering the disabled default, nil/empty/unrecognised
inputs, credential+body preservation, the exact technique set per body shape,
header drop semantics for strip-type, no-op skipping with and without charset
parameters, body-sniffed classification when Content-Type is absent, and
deterministic output order; plus two buildRegistry gating tests (default and
wordlist construction paths). Full suite (-race) passes: 510 tests across 14
packages, ok.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.
Summary
Adds
--content-type-confusion, a new access-control bypass mutator that keeps the request body byte-identical and relabels only theContent-Typeheader. The bug being tested: a fronting WAF / API gateway short-circuits its body-inspection rules ("this is text/plain, no JSON to validate") while the handler still parses the body as JSON, or the handler is wired to multiple parsers (JSON / XML / form) with different authz middleware and is coerced onto the alternate path.Where
--parameter-pollutionattacks which copy of a duplicated parameter each layer reads,--host-headerattacks which host the access-control layer believes the request targets, and--xxeattacks how the XML parser resolves entities,--content-type-confusionattacks which body parser each layer of the stack chooses — the canonical Content-Type confusion / parser-sniffing family.What's shipped
internal/mutate/content_type_confusion.go— pure / deterministic mutator. Three technique sets keyed on body shape:*json*or sniffed{/[) →as-form,as-text,as-xml,strip-type(drop the header entirely).*xml*or sniffed<?xml/ leading tag) →as-json,as-text.as-jsononly (forms are too easy to mis-sniff for a wider set).Identity == nil) and the body bytes — onlyContent-Typechanges. Classauthz-bypass(ASVS V8.3.x, severity HIGH).application/json; charset=utf-8==application/json) so the comparative ladder always sees a real probe.buildRegistryas a 16th gating bool; registered (inert when disabled) so the canonicalDefaultRegistryorder is unchanged. Off by default.Test plan
go test ./internal/mutate/ -run ContentType -race -count=1— all 14 new unit tests pass (disabled default, nil/empty/unrecognised inputs, credential+body preservation, exact technique set per body shape, strip-type header drop semantics, no-op skipping with and without charset parameters, body-sniffing when Content-Type is absent, deterministic order, name).go test ./internal/cli/ -run ContentType -race -count=1— both buildRegistry gating tests pass (default and wordlist construction paths).go test ./... -race -count=1— full suite green: 510 tests across 14 packages, ok.go vet ./...— clean.go build ./...— clean.POST_V01 candidate: Content-Type confusion mutator.
🤖 Generated with Claude Code
Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com