Description
Concatenating user input into a query string can introduce delimiters or duplicate parameters, causing HTTP Parameter Pollution (HPP). The request's meaning depends on how the recipient interprets those parameters.
Server-Side Request Forgery (SSRF) is a separate problem in which an attacker controls a server's request destination. Changing the query of a URL with a fixed host does not itself change that destination, but it can lead to SSRF if the receiving API uses the parameter as the URL for another request.
Potential impact
- An attacker may override existing parameters and trigger unauthorized actions.
- If this leads to SSRF, internal APIs or cloud metadata services (
http://169.254.169.254/) may become accessible. - Sensitive information on the internal network may then be exposed.
Remediation
- Do not concatenate raw user input into URLs or their parameters.
- Use a lookup table when only predefined values are allowed.
- Encode query parameter values appropriately with
java.net.URLEncoder.encode.- Use an allow-list for a fixed set of choices. Do not decode a value and concatenate it unescaped into another URL. Encoding alone does not establish that a destination is trusted.
- Do not allow arbitrary full request URLs supplied by users.
Examples
Before
java
import java.io.IOException;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.HttpClients;
public class UnsafeHttpRequestServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String userInput = request.getParameter("url"); // Use user input
// Unsafe URL concatenation (example input: 1&url=169.254.169.254)
String targetUrl = "https://example.com/api?url=api.example.com&query=" + userInput;
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpGet httpGet = new HttpGet(targetUrl);
try (CloseableHttpResponse clientResponse = httpClient.execute(httpGet)) {
// Process the response
}
}
}
}
After
java
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.HttpClients;
public class SafeHttpRequestServlet extends HttpServlet {
private static final Map<String, String> allowedValues = new HashMap<>();
static {
allowedValues.put("item1", "value1");
allowedValues.put("item2", "value2");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String userInput = request.getParameter("key");
if (userInput == null) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
// Pass only an allowed value from the lookup table
String safeValue = allowedValues.getOrDefault(userInput, "value1");
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpGet httpGet = new HttpGet("https://example.com/api?query=" + safeValue);
try (CloseableHttpResponse clientResponse = httpClient.execute(httpGet)) {
// Process the response
}
}
// Alternative: encode the query value only. Do not concatenate the decoded value into another URL.
String encodedString = java.net.URLEncoder.encode(userInput, StandardCharsets.UTF_8);
final HttpGet httpget = new HttpGet("https://example.com/getId?key=" + encodedString);
}
}
Explanation:
- Before: An input containing
&url=...can add a duplicate parameter. Whether this becomes SSRF depends on whether the receiving API uses that value as a request destination. - After: The request uses a value from an allow-list. The separate example that follows demonstrates query-value encoding and does not execute a request.
Related CVEs
- CVE-2022-36069: A Python dependency management tool allowed optional argument injection when constructing Git commands. This is a command-line argument injection example, distinct from HTTP HPP.
- CVE-2024-4084: SSRF in mintplex-labs/anything-llm