feat(scan): --cache-deception decorates URL with cacheable shapes for Web Cache Deception (Omer Gil)#28
Merged
Merged
Conversation
… Web Cache Deception (Omer Gil) Adds a new HTTP authz-bypass mutator targeting the Web Cache Deception class (Omer Gil, BlackHat 2017; refreshed in the 2024-2026 BlackHat Cache-Confusion / CDN-Confusion research). The URL is decorated with cacheable file-extension shapes a fronting CDN/edge cache stores by default while the application router strips, ignores, or normalises away the decoration and still returns the caller's personal response; the cache then serves that personal response under a public-looking key to any later caller. Four disjoint shapes (sorted-name emission order), each cross-producted with the cacheable extension set (css/gif/ico/jpg/js/png/svg): - path-suffix: /api/me -> /api/me/possession.css - path-extension: /api/me -> /api/me.css - semicolon-suffix: /api/me -> /api/me;.css - encoded-suffix: /api/me -> /api/me%2fpossession.css Every variant keeps the caller's own credentials (Identity == nil) - this is NOT an identity swap; the same caller's same fetch is decorated with a cacheable URL shape so the comparative ladder can flag the candidate finding when the response remains owner-shaped. Endpoints already at a cacheable extension are skipped; trailing-slash paths skip path-extension and semicolon-suffix (no terminal segment); the encoded-suffix decoded form is normalised to avoid double-slashes. The mutation Detail carries shape, extension, path_from, path_to for the operator's cold-cache confirm step. Findings: authz-bypass, HIGH. Off by default - the decorated variants reach the caller's own personal endpoints by design and observably warm an upstream cache at the decorated URL on the caller's behalf. Wired through buildRegistry; the mutator is always registered (inert when disabled) so the canonical DefaultRegistry order and the order test stay unchanged. Worker decision: POST_V01.md suggested assessing --request-smuggling or --cache-poisoning. Request smuggling needs raw TCP, HTTP/1.1 socket control, and timing-based detection that Go's net/http actively prevents - structurally hostile to possession's pure-mutator + comparative-ladder architecture. Cache poisoning detection is inherently stateful (poison + cold-cache follow-up) and doesn't fit the single-request mutator model. Web Cache Deception is the authz-relevant half of the same family that DOES fit cleanly: a deterministic single-request URL mutation whose detection rides the existing comparative ladder unchanged. Pivot documented here and in the PR description. Covered by 17 new tests across internal/mutate/cache_deception_test.go and internal/cli/buildregistry_forbidden_test.go: disabled-by-default contract, nil/empty/degenerate input safety, credentials-preserved contract, full cross-product cell coverage, per-shape Path/RawPath invariants (including the %2f un-double-encoded wire form), the already-cacheable-extension skip, trailing-slash handling, determinism, sorted emission order, the Name() stability contract, the not-in-DefaultRegistry contract, and end-to-end gating through buildRegistry on both wordlist-on and wordlist-off paths. 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 a new HTTP authz-bypass mutator targeting the canonical Web Cache Deception class (Omer Gil, BlackHat 2017; refreshed in 2024-2026 BlackHat Cache-Confusion / CDN-Confusion research). The URL is decorated with cacheable file-extension shapes a fronting CDN / edge cache stores by default, while the application router strips, ignores, or normalises away the decoration and still returns the caller's personal response. The cache stores that personal response under a public-looking key, exposing it to every later caller — including the unauthenticated internet.
Pivot rationale (POST_V01.md was stale on this front)
The lap goal asked me to assess
--request-smugglingor--cache-poisoning. Both are poor architectural fits for possession's pure-mutator + comparative-ladder model:net/httpactively prevents CL.TE attacks by canonicalising the headers. Detection isn't comparative (it's about response splitting and timing). Structurally hostile.Web Cache Deception is the authz-relevant half of the same family that DOES fit cleanly: a deterministic single-request URL mutation whose detection rides the existing comparative ladder unchanged. Pivot is documented in the commit message, the code comment block, and this PR body.
Technique surface
Four disjoint shapes (sorted-name emission order), each cross-producted with the cacheable extension set (
css,gif,ico,jpg,js,png,svg):path-suffix—/api/me→/api/me/possession.css(Omer-Gil original)path-extension—/api/me→/api/me.css(framework extension stripping)semicolon-suffix—/api/me→/api/me;.css(Tomcat / Spring matrix-parameter)encoded-suffix—/api/me→/api/me%2fpossession.css(gateway/router URL-normalisation desync)Every variant keeps the caller's own credentials (
Identity == nil) — the same caller's same fetch is decorated with a cacheable URL shape so the comparative ladder can flag the candidate finding when the response remains owner-shaped. The mutation Detail carriesshape,extension,path_from,path_tofor the operator's cold-cache confirm step. Findings are classauthz-bypass(ASVS V8.3.x, severity HIGH).Off by default: the decorated variants reach the caller's own personal endpoints by design and observably warm the upstream cache at the decorated URL on the caller's behalf — opt in via
--cache-deception. Wired throughbuildRegistry; the mutator is always registered (inert when disabled) so the canonicalDefaultRegistryorder and the order test stay unchanged.Files
internal/mutate/cache_deception.go— the mutator (227 LOC including the doc comment block that mirrors the other authz-bypass mutators' style)internal/mutate/cache_deception_test.go— 17 tests covering disabled-by-default, nil/empty/degenerate safety, credentials-preserved invariant, full cross-product cell coverage, per-shape Path/RawPath invariants (including the%2fun-double-encoded wire form), the already-cacheable-extension skip, trailing-slash handling, determinism, sorted emission order,Name()stability, and the not-in-DefaultRegistry contractinternal/cli/scan.go— new--cache-deceptionflag + variable, extendedbuildRegistrysignature with acacheDeceptionbool, registered in both wordlist-on and wordlist-off construction pathsinternal/cli/buildregistry_forbidden_test.go— extended all 24 existingbuildRegistrycallsites with the new trailingfalse, plus 2 new gating tests for--cache-deception(off and on, both construction paths)README.md— full mutator section in the established style (where-it-fits paragraph, technique list, off-by-default rationale)CHANGELOG.md— Unreleased entryTest plan
go build ./...— cleango vet ./...— cleango test ./...— all packages pass (mutate, cli, detect, replay, …)go test ./internal/mutate/ -run CacheDeception— all 17 cache-deception tests passgo test ./internal/cli/ -run CacheDeception— 2 new gating tests pass; the other 22 mutator gating tests still pass under the extendedbuildRegistrysignature/tmp/possession scan --help | grep cache-deception— flag is documented and discoverableConstraint compliance
buildRegistrygrew one positional bool to stay consistent with the other 14 mutator flags rather than refactor to options struct.mutate/+ 2 gating tests incli/.DefaultRegistryorder unchanged (mutate_test.go order assertion still pins it).🤖 Generated with Claude Code