From cfe01fef65af082dccfc69b2fc78cb07a36ac2b4 Mon Sep 17 00:00:00 2001 From: srdusr <99972264+srdusr@users.noreply.github.com> Date: Wed, 26 Aug 2026 01:06:00 +0200 Subject: Second plugin: paramminer, a Param Miner-style hidden parameter prober For every distinct GET endpoint (deduplicated in-memory so revisiting a URL doesn't rerun the whole wordlist each time), sends a fresh baseline resend plus one probe per candidate from a ~40-entry wordlist of parameter names real backends surprisingly often read even when never part of any observed request (debug, admin, redirect, role, token, and similar). A probe whose response differs from baseline by more than a small threshold (body length, or a different status outright) is a likely hit, tagged paramminer:hit with the parameter name and both response sizes as evidence. Deliberately GET-only with a modest wordlist, not exhaustive POST/JSON-aware probing - same "small honest v1" reasoning as authcheck's single-identity simplification. Same discipline as authcheck: speaks the wire protocol directly, no internal/ipc import, proving PLUGINS.md's documented protocol is actually sufficient on its own. Found and documented two real, non-obvious net/http behaviors while building this: Request.Write ignores the RequestURI field entirely (confirmed directly - a deliberately stale RequestURI still produced the correct output, since Write derives the request line from Request.URL instead) and silently adds a default User-Agent header if the cloned request didn't already have one. Neither affects correctness here since baseline and every probe get identical treatment, but both are worth knowing before reusing this resend pattern elsewhere. Verified live end to end: a real daemon, a real Python origin with a genuinely hidden debug parameter that substantially changes the response, and a control endpoint that's stable regardless of any extra parameter - the hidden-parameter endpoint was correctly tagged with exactly the right parameter name, the stable one correctly left alone, confirmed via tag: search and visually in the TUI with the JSON-array tag payload rendering correctly. --- PLAN.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 4 deletions(-) (limited to 'PLAN.md') diff --git a/PLAN.md b/PLAN.md index 529503d..9a53d0e 100644 --- a/PLAN.md +++ b/PLAN.md @@ -927,7 +927,53 @@ the TUI (tmux, real keystrokes): the `Tags` column badge, `T`'s tag list, and the tag detail view's JSON-colorized data, ANSI-verified, all showing the plugin's actual findings. -Next: the remaining Phase 1 plugins (Param Miner, Backslash Powered -Scanner, Retire.js - no new protocol capability needed, same pattern -`authcheck` already validates), then the live-RPC protocol addition for -JWT Editor/SAML Raider. +### Second plugin shipped: `plugins/paramminer` + +A Param Miner-style hidden parameter prober. For every distinct +`GET` endpoint (deduplicated in-memory by method+scheme+host+path for +the plugin's own runtime, so re-visiting the same URL doesn't re-run +the whole wordlist against it every time), sends a fresh baseline +resend plus one probe per candidate from a hand-picked ~40-entry +wordlist (`debug`, `admin`, `redirect`, `role`, `token`, and similar - +the parameter names real backends most often surprisingly read even +when never part of any observed request), each adding exactly that one +query parameter. A probe whose response differs from baseline by more +than a small absolute-and-relative body-length threshold (or a +different status outright) is a likely hit, tagged `paramminer:hit` +with the parameter name and both response sizes as evidence. +Deliberately `GET`-only with a modest wordlist, not the exhaustive +POST/JSON-aware probing real Param Miner does - same "small honest v1" +reasoning as `authcheck`'s single-identity simplification. + +Two things worth knowing, found while building and verifying this one: +`http.Request.Write` (used to rebuild each probe request after mutating +its query string) silently adds a default `User-Agent` header if the +cloned request didn't already have one and completely ignores the +`Request.RequestURI` field when serializing - confirmed directly +rather than assumed, by writing a request with a deliberately stale +`RequestURI` and observing the output still came out correct because +`Write` derives the request line from `Request.URL` instead. Neither +affects correctness here (baseline and every probe get the identical +treatment, so it can't produce a false diff), but both are worth +knowing before reusing this pattern elsewhere. Also: every probe +becomes its own `source: "repeater"` history row, same as `authcheck`'s +resends - expected (every resend is auditable, the same as a human +using Repeater by hand) but worth calling out, since a wordlist-driven +plugin can visibly fill the history view; `source:proxy` in search +filters the noise back out. Documented in `PLUGINS.md`. + +Verified live end to end: a real daemon, a real Python origin with one +endpoint that has a genuinely hidden `debug` parameter changing its +response substantially (20 bytes -> 58 bytes direct; 164 -> 202 +through the full probe pipeline) and one that's stable regardless of +any extra parameter (the control case) - the hidden-parameter endpoint +was correctly tagged with exactly the right parameter name, the stable +endpoint was correctly left alone, confirmed via `tag:` search and +visually in the TUI with the JSON-array tag data rendering correctly +(the first tag payload to exercise `jsoncolor.go`'s top-level-array +path outside its own unit tests). + +Next: the remaining Phase 1 plugins (Backslash Powered Scanner, +Retire.js - no new protocol capability needed, same pattern `authcheck` +and `paramminer` already validate), then the live-RPC protocol addition +for JWT Editor/SAML Raider. -- cgit v1.2.3