소프트웨어 개발보안 가이드 분석(2021) : 신뢰되지 않은 URL 주소로 자동접속 연결

사용자로부터 입력되는 값을 외부사이트의 주소로 사용하여 자동으로 연결하는 서버 프로그램은 피싱(Phishing) 공격에 노출되는 취약점을 가질 수 있다. 일반적으로 클라이언트에서 전송된 URL 주소로 연결하기 때문에 안전하다고 생각할 수 있으나, 공격자는 해당 폼의 요청을 변조함으로써 사용자가 위험한 URL로 접속할 수 있도록 공격할 수 있다.

소프트웨어 개발보안가이드(2021), 한국인터넷진흥원

일반적으로 "URL Redirection to Untrusted Site" 또는 "Open Redirect"으로 알려진 이 취약점은 국내 뿐만 아니라 국외에서도 현재 CWE-601로 등록되어 있습니다. 웹 애플리케이션이 외부 사이트에 대한 링크를 지정하는 사용자 제어 입력을 수락하고 해당 링크를 사용하여 리다이렉트를 수행하는 약점으로, 공격자는 이 동작을 악용하여 사용자를 악성 사이트로 리디렉션하여 피싱 공격이나 기타 악의적인 활동을 용이하게 할 수 있습니다.이 취약점을 이용하면 공격자는 사용자를 악의적인 사이트로 유도하여 피싱 공격을 시도할 수 있습니다. 따라서, 웹 애플리케이션 개발 시 사용자 입력에 대한 검증이 매우 중요합니다.

Open Redirect는 HTTP 파라미터의 URL을 조작하여 사용자를 악의적인 사이트로 리다이렉트시키고 신뢰성을 가장해 피싱 공격을 수행할 수 있습니다(cwe.mitre.org).

이번 포스팅에서는 취약한 리다이렉션 코드 예제와 이를 방지하기 위한 안전한 코드 작성 방법을 살펴보려고 합니다.

Open Redirect 공격 유형

Open Redirect 공격은 사용자가 신뢰할 수 있다고 믿는 원래의 웹사이트를 통해 악의적인 사이트로 유도함으로써, 피싱 시도의 신뢰성을 높이는 특징이 있습니다.

이 그림은 Open Redirect 공격의 과정을 나타냅니다. 사용자가 신뢰하는 웹사이트(예를 들어 insecure.com)에 방문했을 때, 해당 사이트가 악의적으로 조작된 링크로 인해 사용자를 해커의 웹사이트(예를 들어 hacker.com)로 자동으로 리다이렉트하는 공격 시나리오를 보여줍니다.

사용자는 insecure.com 웹사이트에 접속합니다.

insecure.com 내부에 삽입된 악성 링크(예: <a href="http://insecure.com/redir.do?url=hacker.com">)에 의해 사용자는 해커의 웹사이트로 리다이렉트될 수 있습니다.

사용자는 서버 이름이 실제 신뢰할 수 있는 사이트와 동일하게 보이기 때문에 속아 넘어가 해커의 웹사이트로 리다이렉트되며, 이 웹사이트에서 피싱 공격을 당할 위험이 있습니다.

취약한 리다이렉션 코드 예제

다음은 사용자로부터 입력받은 URL로 리다이렉션을 수행하는 간단한 Java 서블릿 코드입니다. 이 코드는 입력값 검증을 하지 않기 때문에 취약합니다.

public class UnsafeRedirectServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
        throws ServletException, IOException {
        String redirectUrl = request.getParameter("url");
        response.sendRedirect(redirectUrl);
    }
}

공격자는 이 취약점을 이용해 다음과 같은 형태로 사용자를 속일 수 있습니다:

http://yourapp.com/UnsafeRedirectServlet?url=http://malicious.com

이 링크를 클릭한 사용자는 malicious.com으로 리다이렉션되어, 공격자가 만든 가짜 로그인 페이지 등에서 민감한 정보를 입력할 위험이 있습니다.

안전한 리다이렉션 코드 작성 방법

사용자가 요청한 URL이 신뢰할 수 있는 목록에 있는지 확인하는 검증 과정을 추가한 안전한 리다이렉션 코드는 다음과 같습니다.

public class SafeRedirectServlet extends HttpServlet {

    private static final Set<String> TRUSTED_URLS = new HashSet<>(Arrays.asList(
        "http://www.example.com",
        "https://www.example.org",
        // 기타 신뢰할 수 있는 URL을 여기에 추가
    ));

    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
        throws ServletException, IOException {
        String requestedUrl = request.getParameter("url");

        if (isUrlTrusted(requestedUrl)) {
            response.sendRedirect(requestedUrl);
        } else {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "요청된 URL은 허용되지 않습니다.");
        }
    }

    private boolean isUrlTrusted(String url) {
        try {
            URL parsedUrl = new URL(url);
            String host = parsedUrl.getHost().toLowerCase();
            String protocol = parsedUrl.getProtocol().toLowerCase();
            String baseUrl = protocol + "://" + host;

            return TRUSTED_URLS.contains(baseUrl);
        } catch (MalformedURLException e) {
            log("잘못된 URL 형식: " + url, e);
            return false;
        }
    }
}

이 코드는 사용자로부터 받은 URL을 신뢰할 수 있는 목록과 비교하여 검증합니다. URL이 신뢰할 수 없다면 에러를 반환하여 리다이렉션을 방지합니다.

  • doGet 메서드는 HTTP GET 요청을 처리할 때 호출되며, 요청 파라미터에서 'url'을 추출하여 isUrlTrusted 메서드를 통해 검증합니다.
  • isUrlTrusted 메서드는 URL의 형식이 올바른지 확인하고, 호스트와 프로토콜을 추출하여 신뢰할 수 있는 URL 목록과 비교합니다. 만약 URL이 목록에 없거나, 형식이 잘못된 경우 오류 메시지와 함께 클라이언트에게 400 Bad Request 응답을 보냅니다.

  • 이전 소프트웨어 개발보안 가이드 분석(2021) : 위험한 형식 파일 업로드
  • 다음 소프트웨어 개발보안 가이드 분석(2021) : 부적절한 XML 외부개체 참조