오픈 리다이렉트 (Open Redirect)

사용자 입력으로 이동할 URL을 정하면 피싱이나 인증 흐름 우회로 이어질 수 있습니다.

설명

오픈 리다이렉트는 애플리케이션이 사용자가 보낸 URL을 검증하지 않고 redirect, sendRedirect 같은 이동 응답에 사용할 때 발생합니다. 사용자는 정상 도메인으로 시작하는 링크를 보게 되지만, 실제로는 외부 피싱 사이트나 악성 다운로드 페이지로 이동할 수 있습니다.

로그인 후 이동 경로, OAuth 콜백 후 returnUrl, 결제 완료 후 이동 페이지처럼 리다이렉트가 자연스러운 기능에서 자주 생깁니다.

잠재적 영향

  • 피싱 위험: 신뢰할 수 있는 서비스 주소를 이용해 사용자를 가짜 로그인 페이지로 보낼 수 있습니다.
  • 인증 흐름 악용: OAuth나 SSO 흐름에서 검증되지 않은 리다이렉트가 토큰 노출이나 세션 탈취로 이어질 수 있습니다.
  • 악성 콘텐츠 유도: 사용자를 악성 파일 다운로드 페이지나 취약한 외부 사이트로 보낼 수 있습니다.
  • 보안 정책 우회: 내부에서 허용된 도메인을 거쳐 외부 URL로 이동하게 만들어 필터링을 우회할 수 있습니다.

해결 방법

  • 리다이렉트 대상은 서버가 관리하는 허용 목록의 경로나 도메인으로 제한합니다.
  • 가능하면 URL 전체를 입력받지 말고, next=profile처럼 고정된 식별자를 받아 서버에서 실제 경로로 매핑합니다.
  • 외부 URL이 꼭 필요하다면 스킴, 호스트, 포트, 정규화 결과를 모두 확인하고 https만 허용합니다.
  • startsWith, contains 같은 문자열 검사만으로 신뢰하지 않습니다. https://trusted.example.com.evil.example처럼 우회될 수 있습니다.
  • 허용되지 않은 값은 기본 안전 경로로 보내거나 요청을 거부합니다.

예시

Spring 컨트롤러의 일부이며, 필요한 import와 애플리케이션의 내부 경로 정의는 생략했습니다.

변경 전

java
@GetMapping("/login/success")
public void loginSuccess(
    @RequestParam("next") String next,
    HttpServletResponse response
) throws IOException {
    response.sendRedirect(next);
}

변경 후

java
private static final Map<String, String> ALLOWED_REDIRECTS = Map.of(
    "profile", "/account/profile",
    "orders", "/account/orders"
);

@GetMapping("/login/success")
public void loginSuccess(
    @RequestParam(value = "next", defaultValue = "profile") String next,
    HttpServletResponse response
) throws IOException {
    String redirectPath = ALLOWED_REDIRECTS.getOrDefault(next, "/account/profile");
    response.sendRedirect(redirectPath);
}

설명:

  • 변경 전: 요청 파라미터의 URL을 그대로 리다이렉트에 사용합니다. 사용자가 정상 서비스 링크를 클릭했더라도 외부 사이트로 이동할 수 있습니다.
  • 변경 후: 사용자가 보낸 값은 실제 URL이 아니라 서버가 아는 식별자입니다. 허용 목록에 없는 값은 기본 안전 경로로 처리합니다.

참조