1.5 웹 서비스 요청 및 결과 검증
개요
최근 마이크로서비스 아키텍처(MSA) 확산 등으로 인해, 웹 애플리케이션이 다른 웹 서비스(내부 또는 외부 API)를 호출하여 데이터를 가져오거나 기능을 수행하는 경우가 많아지고 있습니다. 이때 호출 대상 URL이나 요청 파라미터에 외부 입력값이 사용되는 경우, 입력값 검증이 미흡하면 서버 측 요청 위조(SSRF: Server-Side Request Forgery) 공격에 취약해질 수 있습니다.
SSRF는 공격자가 서버로 하여금 공격자가 원하는 임의의 대상(주로 내부망의 다른 서버나 localhost)에 네트워크 요청을 보내도록 조작하는 공격입니다. 이를 통해 방화벽 등으로 보호된 내부 시스템의 정보를 유출하거나, 내부 서비스를 스캔하고 공격하는 발판으로 악용될 수 있습니다.
또한, 웹 서비스 응답 결과를 검증하지 않고 사용하면 예상치 못한 데이터 형식이나 오류로 인해 후속 처리 과정에서 문제가 발생할 수 있습니다. 이 기준은 웹 서비스 요청 시 URL 및 파라미터 검증과 응답 결과 검증에 대한 보안 요구사항을 정의합니다.
보안 대책
- - URL 검증 (Whitelist 방식): 외부 입력값을 사용하여 요청 URL을 생성하는 경우, URL 스키마(http, https 등), 호스트(도메인 또는 IP), 포트 번호 등을 엄격하게 검증합니다. 특히, 허용된 도메인/IP 목록(Whitelist)을 정의하고 이 목록에 포함된 대상에만 요청을 허용하는 것이 가장 안전합니다.
- - 내부 IP/localhost 접근 차단: 서버가 내부망(Private IP 대역)이나 자기 자신(localhost, 127.0.0.1)으로 요청을 보내지 않도록 IP 주소를 검사하고 차단합니다.
- - 간접 요청 방식 사용: 사용자 입력에 따라 동적으로 요청 대상을 변경해야 하는 경우, 사용자가 직접 URL을 입력하는 대신 미리 정의된 식별자(예: 서비스 ID)를 선택하게 하고, 서버 내부에서 실제 URL로 변환하여 요청하는 방식을 고려합니다.
- - 안전한 라이브러리/프레임워크 사용: SSRF 방어 기능이 내장된 HTTP 클라이언트 라이브러리나 프레임워크를 사용하고, 관련 보안 옵션을 활성화합니다.
- - 결과 검증: 웹 서비스 응답이 예상된 상태 코드(예: 200 OK), Content-Type, 데이터 형식(JSON, XML 등)을 따르는지 확인하고, 오류 발생 시 적절히 처리합니다. 응답 내용에 대한 추가적인 유효성 검증(길이, 필수 필드 존재 여부 등)도 수행합니다.
- - 네트워크 수준 통제: 웹 서버가 접근할 필요가 없는 내부망 서비스나 외부 사이트에 대한 아웃바운드 네트워크 접근을 방화벽 등으로 제한합니다.
코드 예시 (Java - SSRF 방지)
취약한 코드:
import java.net.URL;
import java.net.HttpURLConnection;
import java.io.InputStream;
// 사용자 입력(imageUrl)을 검증 없이 URL 객체 생성에 사용
String imageUrl = request.getParameter("imageUrl");
URL url = new URL(imageUrl); // SSRF 공격에 취약
// 예: imageUrl = "http://127.0.0.1:8080/admin" 또는 "file:///etc/passwd"
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
InputStream inputStream = connection.getInputStream();
// ... 이미지 처리 로직 ...
import java.net.URL;
import java.net.HttpURLConnection;
import java.io.InputStream;
import java.net.InetAddress;
import java.util.Set;
String imageUrl = request.getParameter("imageUrl");
URL url = null;
try {
url = new URL(imageUrl);
// 1. 프로토콜 검증 (http, https만 허용)
String protocol = url.getProtocol().toLowerCase();
if (!"http".equals(protocol) && !"https".equals(protocol)) {
throw new SecurityException("Invalid protocol specified.");
}
// 2. 호스트 검증 (허용된 도메인 목록 - Whitelist)
String host = url.getHost();
Set allowedHosts = Set.of("example.com", "api.imageservice.com"); // 허용 목록
if (!allowedHosts.contains(host)) {
// IP 주소 직접 사용 방지 및 내부 IP 확인 로직 추가 가능
InetAddress address = InetAddress.getByName(host);
if (address.isSiteLocalAddress() || address.isLoopbackAddress()) {
throw new SecurityException("Access to internal resources is denied.");
}
// 허용되지 않은 외부 호스트에 대한 정책 결정 (예: 차단)
throw new SecurityException("Host not allowed: " + host);
}
// 3. 포트 검증 (선택 사항, 필요 시)
int port = url.getPort();
if (port != -1 && (port < 1 || port > 65535 || port == 8080 /* 특정 포트 차단 등 */)) {
throw new SecurityException("Invalid port specified.");
}
// 검증 통과 후 요청 수행
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 타임아웃 설정 등 추가 보안 설정 권장
connection.setConnectTimeout(5000); // 5초
connection.setReadTimeout(10000); // 10초
connection.setRequestMethod("GET");
// 4. 응답 결과 검증 (예: Content-Type 확인)
String contentType = connection.getContentType();
if (contentType == null || (!contentType.startsWith("image/jpeg") && !contentType.startsWith("image/png"))) {
throw new SecurityException("Invalid content type received.");
}
InputStream inputStream = connection.getInputStream();
// ... 안전하게 이미지 처리 로직 ...
} catch (MalformedURLException e) {
// 잘못된 URL 형식 처리
} catch (SecurityException | IOException e) {
// 보안 예외 또는 네트워크 오류 처리
System.err.println("Error processing request: " + e.getMessage());
}
Python(requests 라이브러리 사용 시 URL 검증 로직 추가), JavaScript(Node.js의 http/https 모듈 사용 시 유사한 검증 필요), C#, PHP 등에서도 URL 파싱 및 구성 요소(프로토콜, 호스트, 포트) 검증, IP 주소 확인 등의 방법으로 SSRF를 방어해야 합니다.