설명
웹 서버에서 주어진 URL 등을 바탕으로 요청을 실행할 때, 승인된 목적지인지 충분히 검증하지 않음으로 인해 발생합니다.
잠재적 영향
- 데이터 탈취 (Confidentiality): 공격자는 민감한 데이터에 접근할 수 있습니다.
- 내부 서비스 오용: 접근 가능한 서비스의 기능이나 추가 취약점에 따라 무단 작업이나 코드 실행으로 이어질 수 있습니다.
- 보호 메커니즘 우회 (Access Control): 공격자는 서버를 이용해 방화벽을 우회하거나 내부 네트워크를 스캔할 수 있는 등 보안 보호책을 우회할 수 있습니다.
해결 방법
- 입력 검증: 가능하면 사용자가 URL을 제공하지 못하게 하고, 서버가 관리하는 엔드포인트 ID를 고정 URI에 매핑합니다.
- 네트워크 접근 제어: 서버의 송신 연결을 필요한 목적지로 제한하고, 필요하지 않은 내부·메타데이터 주소 접근을 차단하세요.
- 리다이렉트 정책: HTTP 클라이언트가 허용된 엔드포인트에서 내부 주소나 다른 호스트로 리다이렉트를 따라가지 않도록 명시적으로 비활성화합니다.
예시
변경 전
java
RestTemplate restTemplate = new RestTemplate();
String url = request.getParameter("url");
String result = restTemplate.getForObject(url, String.class);
변경 후
다음 Java 11 이상 예시의 URI는 관리자가 승인한 실제 목적지로 바꾸세요. DNS와 송신 경로도 신뢰할 수 있어야 합니다. 스트리밍 응답 본문을 읽는 전체 시간 제한은 별도로 적용하세요.
java
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
public final class SafeEndpointClient {
private static final Map<String, URI> ENDPOINTS = Map.of(
"profile", URI.create("https://trusted.com/api/profile"),
"status", URI.create("https://example.com/api/status"));
private static final HttpClient CLIENT = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(3))
.followRedirects(HttpClient.Redirect.NEVER)
.build();
public String fetch(HttpServletRequest request) throws IOException, InterruptedException {
URI target = ENDPOINTS.get(request.getParameter("endpoint"));
if (target == null) {
throw new IllegalArgumentException("Unknown endpoint");
}
HttpRequest outbound = HttpRequest.newBuilder(target)
.timeout(Duration.ofSeconds(5))
.GET()
.build();
HttpResponse<InputStream> response =
CLIENT.send(outbound, HttpResponse.BodyHandlers.ofInputStream());
if (response.statusCode() >= 300 && response.statusCode() < 400) {
response.body().close();
throw new IOException("Redirects are not allowed");
}
try (InputStream body = response.body()) {
byte[] data = body.readNBytes(1024 * 1024 + 1);
if (data.length > 1024 * 1024) {
throw new IOException("Response is too large");
}
return new String(data, StandardCharsets.UTF_8);
}
}
}
설명:
- 변경 전: 이 코드는 클라이언트로부터 얻은 URL을 바로 사용하기 때문에 악의적인 사용자 또는 공격자가 서버 내부 네트워크에 접근하거나 보호 메커니즘을 우회할 수 있습니다.
- 변경 후: 외부 입력은 URL이 아니라 서버가 관리하는 엔드포인트 키만 선택합니다. 실제 URI는 상수 매핑에서 가져오고 JDK HTTP 클라이언트의 리다이렉트를 비활성화하므로 사용자가 목적지나 리다이렉트 대상을 지배할 수 없습니다. 연결·요청 시간과 응답 크기도 제한합니다.
관련 CVE
- CVE-2023-32786: Chain: LLM integration framework has prompt injection (CWE-1427) that allows an attacker to force the service to retrieve data from an arbitrary URL, essentially providing SSRF (CWE-918) and potentially injecting content into downstream tasks.
- CVE-2021-26855: Server Side Request Forgery (SSRF) in mail server, as exploited in the wild per CISA KEV.
- CVE-2021-21973: Server Side Request Forgery in cloud platform, as exploited in the wild per CISA KEV.