설명
사용자 입력을 쿼리 문자열에 그대로 연결하면 구분자나 중복 매개변수가 추가되어 HTTP Parameter Pollution (HPP) 이 발생할 수 있습니다. 매개변수 해석 방식에 따라 요청의 의미가 달라집니다.
Server-Side Request Forgery (SSRF) 는 공격자가 서버의 요청 대상을 제어하는 별개의 문제입니다. 고정된 호스트의 쿼리를 변경하는 것만으로 요청 대상이 바뀌지는 않지만, 수신 API가 해당 매개변수를 다른 요청의 URL로 사용하면 SSRF로 이어질 수 있습니다.
잠재적 영향
- 공격자가 기존 요청 파라미터를 덮어써 권한이 없는 기능을 수행하도록 유도할 수 있습니다.
- SSRF로 이어지면 내부 API 또는 클라우드 메타데이터 서비스(
http://169.254.169.254/)에 접근할 위험이 있습니다. - 그 결과 내부 네트워크의 민감한 정보가 유출될 수 있습니다.
해결 방법
- 사용자 입력을 URL 또는 URL 매개변수에 직접 포함하지 않도록 적용하세요.
- Lookup Table을 사용하여 미리 정의된 값만 허용하도록 하세요.
- 쿼리 매개변수 값에 맞는 인코딩(
java.net.URLEncoder.encode)을 적용하세요.- 선택지가 정해져 있으면 허용 목록을 사용하세요. 값을 디코딩한 뒤 다른 URL에 그대로 다시 연결하지 마세요. 인코딩만으로 요청 대상의 신뢰성을 검증할 수는 없습니다.
- 사용자 입력을 통한 전체 URL 요청을 허용하지 않도록 적용하세요.
예시
변경 전
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"); // 사용자 입력 사용
// 취약한 URL 조합 (공격 파라미터 예제: 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)) {
// 응답 처리
}
}
}
}
변경 후
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;
}
// 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)) {
// 응답 처리
}
}
// 다른 방법: 쿼리 값만 인코딩합니다. 디코딩 후 다른 URL에 그대로 연결하지 마세요.
String encodedString = java.net.URLEncoder.encode(userInput, StandardCharsets.UTF_8);
final HttpGet httpget = new HttpGet("https://example.com/getId?key=" + encodedString);
}
}
설명:
- 변경 전: 입력의
&url=...이 중복 매개변수를 추가할 수 있습니다. SSRF 여부는 수신 API가 이 값을 요청 대상으로 사용하는지에 달려 있습니다. - 변경 후: 허용 목록의 값으로 요청을 구성합니다. 이어지는 별도 예시는 쿼리 값 인코딩 방법을 보여 주며 요청을 실행하지는 않습니다.
관련 CVE
- CVE-2022-36069: Git 명령 생성 시 선택 인수를 주입할 수 있었던 Python 의존성 관리 도구 사례입니다. HTTP HPP와는 다른 명령행 인수 주입 사례입니다.
- CVE-2024-4084: SSRF in mintplex-labs/anything-llm