Back to API Design Best Practices
Q19Question 19 of 22

What is an HTTP Parameter Pollution (HPP) attack and how do you remediate it

Key Concepts
  • HPP exploits the lack of a universal rule for which occurrence of a duplicate parameter wins in HTTP.
  • Java Servlet getParameter returns the first value, Spring may bind the first, but a gateway in another language may take the last.
  • The attacker crafts a request so the security layer reads the safe value while the business layer reads the dangerous one.
  • The strongest fix is rejecting duplicate single-valued parameters outright with a 400 Bad Request.
  • Canonicalization (first-wins or last-wins, documented and uniform) removes ambiguity if duplicates must be accepted.
  • Strict schema validation with typed DTOs and enums prevents polluted values from reaching the service layer.

Definition

HPP is sending the same parameter twice so your security layer and your business layer read different values and the attacker slips through the gap.
  • HTTP Parameter Pollution (HPP) – an attack where the same parameter name is supplied more than once in a request, e.g. ?role=user&role=admin.
  • No universal rule – HTTP does not define which occurrence wins; Java Servlet takes the first, some gateways take the last, Spring can return an array.
  • Exploit – the security filter reads one occurrence while the business layer reads another, bypassing validation or authorization.
  • Remediation – reject duplicates with 400, canonicalize to one deterministic value, or validate against a strict typed schema.

Rajesh fills a bank withdrawal slip but writes the amount twice: 5,000 at the top and 500,000 at the bottom. The security guard at the door glances at the top and sees a small amount, so he waves Rajesh through. But the cashier at the counter reads the bottom line and pays out 500,000. Two people looked at the same form and saw different numbers. HPP is exactly this: the same field sent twice so the guard and the cashier disagree.

Understanding the Concepts

What is HTTP Parameter Pollution?

When a URL contains ?role=user&role=admin, there is no universal rule in the HTTP specification for which value wins. Each technology makes its own choice. This inconsistency is the vulnerability.

DIAGRAM
// The ambiguous request:
GET /api/v1/transfer?accountType=savings&accountType=current

// Java Servlet getParameter("accountType") → "savings" (first)
// Java Servlet getParameterValues("accountType") → ["savings", "current"]
// Spring @RequestParam String accountType → "savings" (first)
// Spring @RequestParam List<String> accountType → ["savings", "current"]
// Some gateways → "current" (last)

How does the attack work?

The attacker relies on the security layer and the business layer reading different occurrences. The security check sees the safe value and allows the request. The business logic reads the dangerous value and acts on it.

JAVA
// Scenario: gateway reads LAST value, backend reads FIRST value
// Attacker sends: ?role=admin&role=user

// Gateway authorization check (reads last): "user" → allowed
// Backend service (reads first): "admin" → executes with admin privileges!

How do you reject duplicate parameters?

The strongest fix is to reject any single-valued parameter that appears more than once. Return 400 Bad Request before any business logic runs.

JAVA
package com.bank.filter;

import jakarta.servlet.*;
import jakarta.servlet.http.*;
import java.io.IOException;
import java.util.Map;

public class HppProtectionFilter implements Filter {

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        for (Map.Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
            if (entry.getValue().length > 1) {
                response.setStatus(400);
                response.getWriter().write(
                        "{\"error\":\"Duplicate parameter not allowed: "
                        + entry.getKey() + "\"}");
                return;
            }
        }
        chain.doFilter(req, res);
    }
}

How does canonicalization work?

If you must accept duplicates (for example, a multi-select filter), collapse them to a single deterministic value. Pick first-wins or last-wins, document it, and make every layer use the same rule.

JAVA
// Canonicalize to first-wins for single-valued params:
String role = request.getParameterValues("role")[0]; // always first
// Document: "For single-valued parameters, this API uses first-wins semantics."

The danger is not duplicates themselves; it is different layers choosing differently.

How does strict schema validation help?

Validate the request against a strict typed DTO. When Spring binds @RequestParam AccountType accountType to an enum, a polluted parameter fails binding cleanly.

JAVA
package com.bank.dto;

public enum AccountType { SAVINGS, CURRENT }

// Controller with strict enum binding:
@GetMapping("/api/v1/transfer")
public TransferResponse transfer(
        @RequestParam AccountType accountType,  // enum binding
        @RequestParam @DecimalMin("0.01") BigDecimal amount) {
    // If ?accountType=savings&accountType=current is sent,
    // Spring fails binding to the single enum → 400 Bad Request
    return transferService.execute(accountType, amount);
}

What about HPP in forwarded requests?

HPP also appears in query strings forwarded to downstream services. If your service builds a downstream URL by concatenating user input, an attacker can inject &admin=true through a polluted parameter.

JAVA
// Wrong – concatenating user input into a downstream URL:
String url = "http://internal/api?type=" + request.getParameter("type");
// Attacker sends: ?type=regular&admin=true
// Downstream URL becomes: http://internal/api?type=regular&admin=true

// Correct – whitelist and URL-encode:
String type = validateAgainstWhitelist(request.getParameter("type"));
String url = "http://internal/api?type=" + URLEncoder.encode(type, StandardCharsets.UTF_8);

What are common mistakes?

The most common mistake is not checking for duplicate parameters at all. The second mistake is having the gateway and the backend use different first-vs-last semantics without realizing it. The third mistake is allowing multi-valued binding for parameters that should be single-valued.

JAVA
// Wrong – accepts a list for a single-valued param:
@RequestParam List<String> role  // ["user", "admin"] – which one wins?

// Correct – accepts a single value:
@RequestParam String role  // Spring takes the first; filter rejects duplicates

How do you test for HPP vulnerabilities?

Send requests with duplicate parameters and verify the API rejects them or handles them consistently.

BASH
# Test: send duplicate accountType parameter
curl "https://api.bank.com/api/v1/transfer?accountType=savings&accountType=current&amount=50000"
# Expected: 400 Bad Request with error message

What should a senior engineer know?

Ambiguity is the vulnerability. Any time two components can interpret the same input differently, an attacker will engineer that difference. The remediation is to eliminate the ambiguity: reject duplicates, validate at the boundary with typed schemas, and make sure the gateway and the service speak with one voice about every parameter. In a banking API where accountType determines transfer limits, this is not an academic concern; it is a real authorization bypass vector.

Visual Overview

DIAGRAM
  HPP Attack Flow:

  Attacker sends: ?role=user&role=admin

  ┌───────────────────┐    reads LAST → "user" → OK
  │  API Gateway       │
  └────────┬──────────┘
           │
           ▼
  ┌───────────────────┐    reads FIRST → "admin" → executes!
  │  Backend Service   │
  └───────────────────┘

  Fix: reject duplicates at the first layer

  ┌───────────────────┐    detects duplicate → 400 Bad Request
  │  HPP Filter        │
  └───────────────────┘    (never reaches business logic)

Key Takeaway

HTTP Parameter Pollution is an attack where the same parameter is supplied more than once in a request, exploiting the fact that there is no universal rule for which occurrence wins. Java Servlet's getParameter returns the first value, Spring may bind the first, and a gateway in another language may take the last. When the security layer reads one occurrence and the business layer reads another, the attacker bypasses authorization.

The strongest remediation is to reject duplicates outright: if a single-valued parameter appears more than once, return 400 before any business logic runs. If duplicates must be accepted, canonicalize to a single deterministic value and document whether first-wins or last-wins, making every layer use the same rule. On top of that, validate with typed DTOs and enums so polluted values fail binding.

At National Bank of India, the transfer API was vulnerable because the gateway read the last accountType value and the backend read the first. A customer sent accountType=savings&accountType=current and bypassed the savings transfer limit by fooling the gateway into treating it as a savings account while the backend processed it as current. The fix was an HPP filter that rejects any single-valued parameter sent twice, combined with strict enum binding.

The principle is that ambiguity is the vulnerability. Any time two components can interpret the same input differently, an attacker will engineer that difference. Eliminating the ambiguity by rejecting duplicates and validating at the boundary is the fix.

Remember one sentence: if a parameter that should appear once appears twice, reject it, because the gap between how two layers read it is the vulnerability.

---

Share
Use ← → arrow keys to navigate