소프트웨어 개발보안 가이드 분석(2021) : 서버사이드 요청 위조

서버사이드 요청 위조란?

https://owasp.org/www-community/attacks/Server_Side_Request_Forgery

서버사이드 요청 위조(Server-Side Request Forgery, SSRF)는 웹 애플리케이션에서 발생하는 보안 취약점 중 하나로, 공격자가 악의적으로 웹 서버의 입장에서 다른 서버에 대한 요청을 위조하는 공격입니다. 이를 통해 공격자는 내부 네트워크 리소스에 접근하거나 외부 서버에 대한 공격을 수행할 수 있습니다.

SSRF의 공격 매커니즘

서버사이드 요청 위조(SSRF)
정상 요청: http://samplesite.com/connect?url=http://210.182.34.150
비정상 요청: http://samplesite.com/connect?url=http://192.168.0.100/admin

서버사이드 요청 위조 공격은 주로 웹 애플리케이션에서 사용자의 입력을 받아 외부 서버에 요청을 보내는 과정에서 발생할 수 있습니다. 사용자의 입력값이 적절히 검증되지 않고 직접 사용될 때, 공격자는 이를 이용해 서버 내부의 리소스에 접근하거나 외부 서버에 대한 공격을 수행할 수 있습니다.

취약한 웹 어플리케이션의 예

다음은 SSRF 취약점을 가진 취약한 웹 어플리케이션의 예시입니다.

protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    String url = request.getParameter("url");
    URL targetUrl = new URL(url);
    HttpURLConnection connection = (HttpURLConnection) targetUrl.openConnection();
    // 외부 URL에 대한 요청을 실행하고 결과를 응답으로 반환
    // (실제로는 보안 검사 등의 추가적인 로직이 필요하나 간단한 예시를 위해 생략)
    InputStream inputStream = connection.getInputStream();
    // 응답 데이터를 클라이언트에게 전송
    // (실제로는 응답 데이터를 검증하여 보안 문제가 발생하지 않도록 처리해야 함)
    IOUtils.copy(inputStream, response.getOutputStream());
}

위 코드에서는 사용자가 입력한 URL을 받아 외부 서버에 요청을 보내고, 응답을 클라이언트에게 반환합니다. 이 때 입력값이 적절히 검증되지 않고 그대로 요청에 사용되기 때문에 SSRF 공격에 취약합니다. 이러한 코드에서 공격자가 내부 서비스의 URL을 알아내어 요청을 보낼 경우, 내부 시스템 정보 및 리소스가 노출될 위험이 있습니다.

시큐어코딩 적용 방법

서버사이드 요청 위조 공격을 방지하기 위한 시큐어코딩 적용 방법으로는 다음과 같은 방법들이 있습니다.

  1. 입력값 검증: 사용자로부터 받은 URL에 대해 화이트리스트 방식을 적용하여 검증합니다. 화이트리스트에 없는 도메인이나 IP 주소에 대한 요청은 차단합니다.
  2. URL 파싱 및 재구성: 사용자 입력 URL을 파싱하여 스키마, 호스트, 포트 등을 검증한 후, 신뢰할 수 있는 구성 요소만을 사용하여 새로운 URL을 재구성합니다.
  3. 내부 리소스 접근 제한: 서버가 요청할 수 있는 리소스를 제한합니다. 예를 들어, 내부 IP 주소나 특정 포트에 대한 요청을 차단할 수 있습니다.

다음은 시큐어코딩을 적용하여 서버사이드 요청 위조 공격을 방지하는 예시 코드입니다. 이 코드는 Java Servlet을 사용하는 웹 애플리케이션에서 사용자가 입력한 URL을 받아 안전하게 요청을 보내고 응답을 반환합니다.

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

public class SafeRequestServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String url = request.getParameter("url");
        
        // 입력값 검증 및 화이트리스트 필터링
        if (isValidUrl(url)) {
            URL targetUrl = new URL(url);
            
            // 보안 강화: 외부 호스트에 대한 접근 제한 (선택 사항)
            if (!isAllowedHost(targetUrl.getHost())) {
                response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                return;
            }
            
            HttpURLConnection connection = (HttpURLConnection) targetUrl.openConnection();
            
            // 보안 강화: 외부로의 요청을 수행하기 전에 메서드 검증 (선택 사항)
            connection.setRequestMethod("GET");
            
            // 외부 URL에 대한 요청을 실행하고 결과를 응답으로 반환
            // (실제로는 보안 검사 등의 추가적인 로직이 필요하나 간단한 예시를 위해 생략)
            
            // 응답 데이터를 클라이언트에게 전송
            // (실제로는 응답 데이터를 검증하여 보안 문제가 발생하지 않도록 처리해야 함)
        } else {
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
        }
    }
    
    // 입력값 검증 및 화이트리스트 필터링을 위한 메서드
    private boolean isValidUrl(String url) {
        // 간단히 URL이 HTTP 또는 HTTPS 프로토콜을 사용하는 예시로,
        // 실제로는 보다 더 엄격한 검증 로직을 구현해야 합니다.
        return url != null && (url.startsWith("http://") || url.startsWith("https://"));
    }
    
    // 보안 강화: 허용된 호스트만 요청을 허용하는 화이트리스트 필터링 (선택 사항)
    private boolean isAllowedHost(String host) {
        // 허용된 호스트 목록 추가합니다.
        // 실제로는 데이터베이스나 환경설정 파일에 호스트 목록을 저장하고 이를 조회하는 것을 권장합니다.
        return "trustedhost.com".equals(host);
    }
}

위 코드에서는 isValidUrl 메서드를 통해 입력된 URL이 HTTP 또는 HTTPS 프로토콜을 사용하는지 확인하여 사용자가 임의의 프로토콜을 사용하여 내부 네트워크에 접근하는 것을 방지하고, isAllowedHost 메서드를 통해 특정 호스트에 대해서만 요청을 허용 즉, 화이트리스트에 등록된 호스트만 요청을 제한하여, 내부 네트워크 리소스나 민감한 외부 서비스에 대한 무단 접근을 방지합니다. 또한 connection.setRequestMethod("GET")를 통해 요청 메서드를 GET으로 제한하여 서버가 예상치 못한 메서드를 통해 요청을 받는 것을 방지하며, 특히 변경이나 삭제 등을 수행할 수 있는 메서드의 사용을 제한하여 서버사이드 요청 위조 공격에 방어할 수 있습니다.

  • 이전 소프트웨어 개발보안 가이드 분석(2021) : 크로스사이트 요청 위조
  • 다음 소프트웨어 개발보안 가이드 분석(2021) : HTTP 응답 분할