Hidden Parameter Discovery: Arjun, Param Miner, and Mass Assignment for Pentesters
Every web application accepts more input than its documentation, its JavaScript, or its API spec admits. Backend code routinely reads request parameters that are never referenced in the UI: debug flags left in from development, legacy fields kept for backward compatibility, internal toggles, and model attributes that a framework binds automatically. These hidden parameters are some of the highest-value findings in a web assessment because they sit behind the application's stated attack surface — the developer assumed nobody would ever send them, so they are frequently unguarded.
This guide covers parameter discovery end to end for authorized testing: how the major tools (Arjun and Burp's Param Miner) actually work, how to build wordlists that find things generic lists miss, how to confirm a parameter is real rather than a false positive, and how to chain a discovered parameter into a concrete impact like mass assignment. Everything here assumes you have written permission to test the target.
Why Hidden Parameters Exist
A parameter is "hidden" only in the sense that the client never advertises it. The server still processes it. Common sources:
- Framework auto-binding — Rails, Laravel, Spring, and Express body parsers map every key in the request body to a model or controller argument. If a field like
roleexists on the model but not on the form, the server still accepts it. - Dead and debug code — parameters such as
debug,test,admin,source, orcallbackthat gate verbose output, skip a check, or change rendering. - Internal/SSO fields — values the application sets server-to-server (
email_verified,account_status,price,discount) that the client is trusted not to touch. - Cache and routing parameters — unkeyed inputs that influence the response but are excluded from the cache key, which is the basis for web cache poisoning.
The discovery problem is therefore an oracle problem: you send a candidate parameter, and you need a reliable signal that the server noticed it. That signal is what separates a real tool from a noisy one.
How Discovery Tools Work: The Reflection and Diff Oracle
Both Arjun and Param Miner rely on the same core idea. Send a baseline request, then send the same request with one or more candidate parameters injected with a unique, random value. If anything about the response changes in a way that correlates with your input, the parameter is probably handled by the backend.
Two oracles dominate:
- Reflection — your unique value appears somewhere in the response body or headers. This is the strongest signal: it proves the parameter reached code that echoes it.
- Behavioral diff — the parameter does not reflect, but its presence changes the response: status code, length, word count, response time, an error message, a header, or a redirect target. This catches the most interesting parameters (the ones that change logic, not just output).
To stay fast, the tools batch many candidate parameters into a single request, then perform a binary-search "bucketing" when a batch shows a change: split the batch in half, resend, and recurse on whichever half still triggers the difference. This turns thousands of candidates into a handful of requests. The catch is collisions — too many parameters per request can hit server limits or cause length-based false positives — so both tools auto-tune the chunk size based on the target's behavior.
Arjun: Fast Standalone Discovery
Arjun is a command-line scanner that tests GET, POST (form and JSON), and header parameters. It ships with a curated ~26k wordlist derived from real-world parameter names and handles rate limiting, custom headers, and stable-vs-unstable target detection.
# Basic GET discovery
arjun -u https://target.example/api/profile
# JSON body, authenticated, with a custom wordlist
arjun -u https://target.example/api/users/42 \
-m JSON \
--headers "Authorization: Bearer $TOKEN" \
-w custom-params.txt \
-oT arjun-out.txt
# Throttle for fragile targets: 1 thread, delay between requests
arjun -u https://target.example/search -m GET -t 1 -d 2
# Probe headers (X-Forwarded-Host, X-Original-URL, etc.)
arjun -u https://target.example/ -m headers
Arjun's stability check matters: before fuzzing it sends repeated identical requests to learn the target's natural response variance. If a page changes length on every load (CSRF tokens, timestamps, ads), Arjun adjusts its diffing so that random noise is not reported as a hit. Always run an authenticated and an unauthenticated pass — different code paths expose different parameters, and a parameter that only works with a valid session is often the more dangerous one.
Param Miner: Discovery Inside Burp
Param Miner (a Burp Suite extension from PortSwigger) integrates discovery with your proxy history, so you fuzz the exact request you captured — cookies, headers, and all. Right-click a request and choose "Guess params" to launch GET/body/header/cookie discovery. Its standout features:
- Bundled high-signal wordlists built from observed parameter names across many sites.
- Cache-poisoning awareness — it can detect unkeyed inputs (parameters that affect the response but are excluded from the cache key), which is the foundation of web cache poisoning.
- Header and cookie guessing, including the classic
X-Forwarded-Host,X-Original-URL, andX-Rewrite-URLoverrides that can bypass access controls. - Learn-from-observed-words — it mines your in-scope traffic for candidate names, which often beats any static list because the names match the target's own conventions.
Param Miner reports findings to the Issues panel with the evidence (the reflecting value or the diffed response). Because it runs through Burp, you can immediately send a confirmed parameter to Repeater and start manual exploitation.
Building Better Wordlists
The default lists are a fine first pass, but the parameters with real impact are usually application-specific. A wordlist tuned to the target finds what generic lists never will.
- Scrape the target itself. Extract every parameter name already visible in JavaScript bundles, HTML forms, sitemaps, API responses, and old endpoints found via the Wayback Machine. Tools like
linkfinder,gau, and simple grep over JS files surface names likeisInternalorfeatureFlagthat a backend likely reuses elsewhere. - Mutate what you find. If you see
userId, also tryuser_id,uid,userID, andid. Case and separator variants matter because different frameworks normalize differently. You can generate these mutations and assemble a clean, deduplicated list with the Password & Wordlist Generator. - Add intent-based terms. For privilege escalation:
admin,is_admin,role,group,permissions,verified,approved. For business logic:price,amount,discount,currency,quantity,status. For info leakage:debug,test,verbose,source,format,callback.
# Pull candidate names from JS, sort, dedupe
cat *.js | grep -oE '[\"\\'\''][a-zA-Z_][a-zA-Z0-9_]+[\"\\'\'']\\s*:' \
| tr -d '\":'\'' ' | sort -u > from-js.txt
# Merge curated + scraped lists
cat arjun-default.txt from-js.txt | sort -u > custom-params.txt
From Discovery to Impact: Mass Assignment
Finding a parameter is not a finding by itself — you must show what it does. The most common high-impact outcome is mass assignment (OWASP API "Broken Object Property Level Authorization"), where a framework binds your extra parameter straight onto a database model.
Suppose discovery on a profile-update endpoint reveals that role is accepted even though the form only submits name and email:
PATCH /api/users/42 HTTP/1.1
Host: target.example
Authorization: Bearer eyJ...
Content-Type: application/json
{"name":"Alice","email":"[email protected]","role":"admin"}
If the response reflects "role":"admin" or a follow-up GET shows the elevated role, you have a privilege escalation, not just a discovered parameter. The same pattern overrides balance, price, verified, or email_confirmed depending on the model. For the full set of framework-specific bypasses — Rails strong parameters, Laravel $fillable/$guarded, and Mongoose schema injection — see the deep dive on mass assignment exploitation.
To confirm a behavioral-only parameter that does not reflect, capture the baseline and the modified responses and diff them precisely. Parsing the raw requests with the HTTP Request Parser to extract every field, then comparing the two responses with the Response Diff Analyzer, makes a single changed byte or header obvious — that delta is your proof the parameter is live.
Avoiding False Positives
Discovery tools generate noise, and a report full of phantom parameters destroys credibility. Before you log anything:
- Reproduce manually. Resend the request with and without the parameter in Repeater. The difference must be deterministic across several attempts.
- Watch for dynamic baselines. Anti-CSRF tokens, timestamps, A/B test variants, and rotating ads change response length on their own. Diff on content semantics, not just byte count.
- Rule out length collisions. A long parameter name can change response size simply by being echoed in an error. Use a unique random value and look for that value, not just a size delta.
- Test both methods. A parameter ignored in the query string may be honored in the JSON body, and vice versa. Confirm where it actually takes effect.
Defenses
For defenders reviewing these findings, the fixes are well established:
- Whitelist, never blacklist. Bind only explicitly allowed fields — Rails strong parameters (
permit(:name, :email)), Laravel$fillable, an explicit DTO or schema. Blacklists rot the moment a new sensitive field is added. - Separate read and write models. Don't bind request bodies directly to ORM entities. Map into a dedicated input object that contains only user-editable fields, then copy validated values onto the model.
- Authorize at the property level. Even allowed fields should be checked against the caller's privileges — a user editing their own profile must not be able to set
roleregardless of how the binding works. - Remove debug and legacy parameters. Strip development toggles before production and delete handlers for parameters no longer in use. If a parameter must remain, gate it behind authentication and authorization.
- Reject unknown fields. Configure parsers to fail on unexpected keys (strict schema validation) so an injected parameter produces a 400 instead of silent acceptance.
Parameter discovery rewards methodology over raw tool output. Run authenticated and unauthenticated passes, feed the tools wordlists scraped from the target's own code, confirm every hit by hand, and always demonstrate concrete impact rather than reporting a bare parameter name. A single hidden role or price field, properly proven, is often the most valuable line in the whole engagement.
Level up your security testing
Install the CLI
npx payload-playgroundExplore All Tools
Encoding, hashing, JWT & more
Browse Cheat Sheets
Quick-reference payload guides