Skip to content

feat(scan): --cache-deception decorates URL with cacheable shapes for Web Cache Deception (Omer Gil)#28

Merged
bugsyhewitt merged 1 commit into
mainfrom
worker-r31-possession
May 30, 2026
Merged

feat(scan): --cache-deception decorates URL with cacheable shapes for Web Cache Deception (Omer Gil)#28
bugsyhewitt merged 1 commit into
mainfrom
worker-r31-possession

Conversation

@bugsyhewitt

Copy link
Copy Markdown
Owner

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-smuggling or --cache-poisoning. Both are poor architectural fits for possession's pure-mutator + comparative-ladder model:

  • Request smuggling (CL.TE / TE.CL / TE.TE) needs raw TCP, HTTP/1.1 socket control, connection reuse, and timing-based detection. Go's net/http actively prevents CL.TE attacks by canonicalising the headers. Detection isn't comparative (it's about response splitting and timing). Structurally hostile.
  • Cache poisoning detection is inherently stateful: poison the cache with an attacker-influenced response, then fetch the cached response from a different (cold-cache) caller to confirm the leak. That two-step flow doesn't fit possession's single-request mutator + comparative-evaluator pipeline.

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 carries shape, extension, path_from, path_to for the operator's cold-cache confirm step. Findings are class authz-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 through buildRegistry; the mutator is always registered (inert when disabled) so the canonical DefaultRegistry order 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 %2f un-double-encoded wire form), the already-cacheable-extension skip, trailing-slash handling, determinism, sorted emission order, Name() stability, and the not-in-DefaultRegistry contract
  • internal/cli/scan.go — new --cache-deception flag + variable, extended buildRegistry signature with a cacheDeception bool, registered in both wordlist-on and wordlist-off construction paths
  • internal/cli/buildregistry_forbidden_test.go — extended all 24 existing buildRegistry callsites with the new trailing false, 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 entry

Test plan

  • go build ./... — clean
  • go vet ./... — clean
  • go test ./... — all packages pass (mutate, cli, detect, replay, …)
  • go test ./internal/mutate/ -run CacheDeception — all 17 cache-deception tests pass
  • go test ./internal/cli/ -run CacheDeception — 2 new gating tests pass; the other 22 mutator gating tests still pass under the extended buildRegistry signature
  • /tmp/possession scan --help | grep cache-deception — flag is documented and discoverable

Constraint compliance

  • ONE improvement: only the cache-deception mutator. No refactor pass, no sweep, no abstraction churn — buildRegistry grew one positional bool to stay consistent with the other 14 mutator flags rather than refactor to options struct.
  • Tests included: 17 new tests in mutate/ + 2 gating tests in cli/.
  • README updated: full new section in the established style.
  • Architecture intact: pure mutator, deterministic, comparative-ladder detection, off-by-default — matches every other authz-bypass mutator in the suite. DefaultRegistry order unchanged (mutate_test.go order assertion still pins it).
  • v0.1 criteria not broken: full test suite green.
  • Within budget.

🤖 Generated with Claude Code

… 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>
@bugsyhewitt bugsyhewitt merged commit ad8a547 into main May 30, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant