소프트웨어 개발보안 가이드 분석(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 외부개체 참조