오픈 리다이렉트 취약점이 발생하는 원인과 정상 도메인이 피싱 링크에 악용되는 과정, 취약한 Java 코드 및 안전한 URL 검증 방법을 알아봅니다.
웹사이트는 로그인 완료, 결제 처리, 회원가입과 같은 작업이 끝난 뒤 사용자를 다른 페이지로 이동시킨다. 원래 보던 페이지로 돌아가거나 관련 서비스로 연결하기 위해 이동할 주소를 URL 파라미터로 전달하기도 한다.
이때 사용자가 입력한 주소를 제대로 확인하지 않고 리다이렉트에 사용하면 공격자가 원하는 외부 사이트로 방문자를 이동시킬 수 있다. 이러한 보안 문제를 오픈 리다이렉트 취약점이라고 한다.
리다이렉트란?
리다이렉트는 사용자가 요청한 페이지에서 다른 주소로 이동하도록 안내하는 HTTP 기능이다. 서버는 주로 301, 302, 303, 307 또는 308 상태 코드와 Location 응답 헤더를 사용해 이동할 주소를 전달한다.
로그인이 필요한 페이지에 접속했을 때 로그인 화면으로 이동한 뒤 인증이 끝나면 원래 페이지로 돌아오는 기능을 예로 들 수 있다. 이 경우 다음과 같이 이동할 주소가 파라미터에 포함될 수 있다.
/login?next=/mypage
next 값이 /mypage처럼 서비스 내부 경로로 제한돼 있다면 정상적인 기능이다. 하지만 전체 외부 URL을 입력해도 서버가 그대로 이동시킨다면 공격자는 정상 사이트를 거쳐 피싱 사이트로 연결되는 주소를 만들 수 있다.
오픈 리다이렉트 취약점이란?
오픈 리다이렉트는 외부 사용자가 웹 애플리케이션의 이동 목적지를 자유롭게 지정할 수 있는 취약점이다. ‘Unvalidated Redirects and Forwards’ 또는 URL 리다이렉션 취약점이라고도 부른다.
공격자는 취약한 웹사이트의 도메인 뒤에 자신이 만든 피싱 페이지 주소를 파라미터로 추가한다. 사용자가 링크의 앞부분만 확인하면 신뢰할 수 있는 공식 사이트의 주소처럼 보일 수 있다.
링크를 클릭하면 먼저 정상 사이트에 접속하지만 서버의 리다이렉트 응답에 따라 곧바로 외부 피싱 페이지로 이동한다. 이동 과정이 빠르게 진행되기 때문에 사용자는 정상 사이트가 피싱 화면을 제공한 것으로 오해할 수 있다.
오픈 리다이렉트의 핵심은 외부 입력값이 사용자의 최종 이동 주소를 결정한다는 점이다.
오픈 리다이렉트가 발생하는 원인
가장 흔한 원인은 next, returnUrl, redirect, url과 같은 파라미터를 검증하지 않고 Location 헤더에 사용하는 것이다.
개발자는 로그인 이전에 보던 페이지로 돌아가게 하려고 이동 주소를 파라미터로 받는다. 하지만 내부 경로만 허용하지 않고 https://로 시작하는 외부 주소까지 처리하면 공격자가 목적지를 변경할 수 있다.
특정 도메인 문자열로 시작하는지만 확인하는 방법도 위험하다. 허용한 도메인과 이름이 비슷한 다른 도메인이나 URL의 사용자 정보 영역을 이용하면 문자열 검사를 통과하면서 실제로는 다른 호스트로 이동할 가능성이 있기 때문이다.
주소에 회사 도메인이 포함돼 있는지만 검사하는 방식도 안전하지 않다. URL의 경로와 파라미터에 정상 도메인 문자열을 넣는 것은 어렵지 않기 때문에 표준 URL 파서를 이용해 실제 호스트를 분리한 뒤 비교해야 한다.
인코딩된 URL을 검사한 후 다시 디코딩하는 코드도 주의해야 한다. 검증할 때 확인한 값과 실제 리다이렉트에 사용되는 값이 달라지면 허용하지 않은 외부 주소가 처리될 수 있다.
오픈 리다이렉트에 취약한 Java 코드
다음은 사용자가 입력한 주소로 이동시키는 Spring 기반 코드다.
@GetMapping("/redirect")
public RedirectView redirect(
@RequestParam String next) {
return new RedirectView(next);
}
코드는 next 값을 검증하지 않고 RedirectView에 전달한다. 사용자가 서비스 내부의 /mypage를 입력하면 정상적으로 마이페이지로 이동한다.
그러나 외부 사이트의 전체 URL을 입력해도 그대로 처리될 수 있다. 공격자는 이 기능을 이용해 정상 도메인으로 시작하지만 최종적으로는 외부 피싱 사이트로 이동하는 링크를 만들 수 있다.
비슷한 문제는 다음과 같이 sendRedirect()를 사용하는 코드에서도 발생할 수 있다.
response.sendRedirect(
request.getParameter("next")
);
리다이렉트 API 자체가 취약한 것은 아니다. 이동 주소를 사용자가 자유롭게 결정하도록 만든 애플리케이션 구조가 문제다.
정상 도메인이 피싱에 악용되는 과정
공격자는 먼저 이동 주소를 파라미터로 받는 기능을 찾는다. 로그인, 로그아웃, 이벤트 페이지, 광고 링크와 외부 서비스 연결 기능이 주로 확인 대상이 된다.
이후 리다이렉트 파라미터에 피싱 페이지 주소를 넣어 링크를 만든다. 링크 앞부분에는 정상 기업이나 기관의 도메인이 표시되므로 사용자는 비교적 쉽게 신뢰할 수 있다.
사용자가 링크를 클릭하면 정상 사이트는 공격자가 지정한 주소를 Location 헤더에 넣어 응답한다. 브라우저는 해당 응답을 받고 외부 피싱 페이지로 이동한다.
피싱 페이지는 정상 로그인 화면과 비슷하게 만들어질 수 있다. 사용자가 이동 후 주소창을 다시 확인하지 않고 아이디와 비밀번호를 입력하면 계정 정보가 공격자에게 전달될 가능성이 있다.
오픈 리다이렉트는 서버 내부 데이터가 직접 유출되는 취약점은 아닐 수 있다. 그러나 정상 사이트의 신뢰도를 공격에 이용할 수 있다는 점에서 피싱과 악성파일 유포에 자주 결합될 수 있다.
다른 취약점과 결합될 때의 위험
오픈 리다이렉트는 OAuth와 같은 인증 절차에서 더 큰 문제로 이어질 수 있다. 인증 완료 후 돌아갈 redirect_uri를 검증하는 과정에서 취약한 리다이렉트 주소를 신뢰하면 인증 코드나 토큰이 의도하지 않은 사이트로 전달될 가능성이 생긴다.
외부 URL을 차단하는 보안 기능이 정상 도메인만 허용하는 경우에도 오픈 리다이렉트가 우회 경로로 사용될 수 있다. 보안 시스템은 처음 접속하는 주소가 신뢰된 도메인이므로 허용하지만 사용자는 이후 외부 주소로 이동할 수 있기 때문이다.
이메일과 문자 메시지의 URL 검사에서도 비슷한 문제가 생길 수 있다. 링크에 표시된 첫 번째 도메인만 확인하면 정상 주소로 분류될 수 있지만 실제 도착지는 공격자가 만든 사이트일 수 있다.
따라서 오픈 리다이렉트는 단순히 다른 페이지로 이동하는 문제로만 판단해서는 안 된다. 인증 흐름과 URL 허용 정책을 우회하는 수단으로 사용될 가능성도 점검해야 한다.
허용 목록을 이용한 안전한 Java 코드
가장 안전한 방법은 사용자가 전체 이동 주소를 직접 입력하지 못하게 하는 것이다. 서버에서 허용할 페이지와 실제 경로를 미리 연결하고 사용자는 페이지 식별자만 전달하도록 구성할 수 있다.
private static final Map<String, String>
ALLOWED_PAGES = Map.of(
"home", "/",
"mypage", "/mypage",
"support", "/support"
);
@GetMapping("/redirect")
public RedirectView redirect(
@RequestParam String page) {
String target =
ALLOWED_PAGES.getOrDefault(
page,
"/"
);
return new RedirectView(target);
}
사용자가 mypage를 전달하면 서버는 미리 등록된 /mypage로 이동시킨다. 입력값에 전체 URL을 넣더라도 허용 목록에 없으므로 기본 페이지인 /로 이동한다.
실제 경로는 서버에서 결정하고 사용자는 정해진 식별자만 선택하기 때문에 외부 피싱 주소가 Location 헤더에 들어갈 가능성을 줄일 수 있다.
파라미터가 허용 목록에 없을 때 조용히 기본 페이지로 이동시키는 대신 잘못된 요청으로 처리할 수도 있다. 서비스 흐름과 사용자 경험에 따라 적절한 방식을 선택하면 된다.
내부 상대경로만 허용하는 방법
여러 내부 페이지로 이동해야 해 모든 경로를 허용 목록에 등록하기 어렵다면 서비스 내부의 상대경로만 허용할 수 있다.
입력값이 / 하나로 시작하는지 확인하고 //로 시작하는 주소는 거부해야 한다. //example.com과 같은 주소는 프로토콜 상대 URL로 해석돼 외부 사이트로 이동할 수 있기 때문이다.
역슬래시와 반복 인코딩, 사용자 정보 영역처럼 브라우저나 중간 장비에 따라 다르게 해석될 수 있는 값도 주의해야 한다. 문자열 검사만으로 처리하기보다 표준 URI 파서를 사용하고 정규화한 결과를 기준으로 검증해야 한다.
검증 후에는 입력값을 다시 디코딩하거나 문자열을 추가하지 않아야 한다. 검증한 값과 실제 리다이렉트에 사용되는 값이 동일해야 한다.
외부 도메인을 허용해야 하는 경우
업무상 협력사나 별도 서비스로 이동해야 한다면 허용할 프로토콜, 호스트와 포트를 정확하게 지정해야 한다. 가능하면 HTTPS만 허용하고 호스트 이름은 전체 문자열이 정확히 일치하는지 확인한다.
다음과 같이 문자열의 앞부분만 비교하는 방식은 피해야 한다.
if (next.startsWith(
"https://trusted.example")) {
// 이동 허용
}
이 방식은 정상 도메인과 비슷하게 만들어진 다른 호스트를 잘못 허용할 수 있다. URI 클래스를 이용해 주소를 분리한 뒤 scheme, host, port를 각각 비교해야 한다.
하위 도메인을 허용해야 한다면 단순히 특정 문자열로 끝나는지 검사하지 말고 점을 포함한 도메인 경계를 확인해야 한다. 허용 대상이 많지 않다면 정확한 호스트 목록을 관리하는 편이 안전하다.
URL에 사용자 정보 영역이 포함돼 있거나 예상하지 못한 포트가 사용된 경우에는 요청을 거부하는 것이 좋다.
중간 안내 페이지는 보조 수단
외부 사이트 이동이 서비스의 필수 기능이라면 “외부 사이트로 이동합니다”라는 안내 페이지를 보여줄 수 있다. 사용자가 최종 도메인을 확인한 후 직접 이동 버튼을 누르게 하는 방식이다.
안내 페이지에는 전체 URL보다 실제 호스트 이름을 눈에 잘 띄게 표시하는 것이 좋다. 의심스러운 주소를 신고하거나 이동을 취소할 수 있는 기능도 도움이 된다.
다만 안내 화면만 추가하고 아무 외부 주소나 허용하면 근본적인 취약점은 남는다. 허용 목록과 URL 검증을 우선 적용하고 안내 페이지는 사용자의 실수를 줄이는 보조 수단으로 사용해야 한다.
로그와 모니터링 설정
리다이렉트 기능에서 허용되지 않은 외부 도메인, 예상하지 못한 프로토콜과 반복적인 인코딩이 포함된 요청을 기록해야 한다.
동일한 IP에서 여러 외부 주소를 대입하거나 특정 리다이렉트 링크가 짧은 시간에 반복적으로 사용된다면 피싱 캠페인에 악용되고 있는지 확인할 필요가 있다.
URL 파라미터 전체를 로그에 남길 때는 개인정보나 인증 코드가 포함돼 있지 않은지 주의해야 한다. OAuth와 로그인 흐름에서는 민감한 파라미터를 마스킹하고 목적지 호스트와 차단 사유 중심으로 기록하는 것이 좋다.
오픈 리다이렉트 취약점 점검 항목
소스코드 점검에서는 sendRedirect(), RedirectView, Location 헤더 설정과 프레임워크의 리다이렉트 반환문을 확인한다. 이동 주소가 URL 파라미터, 쿠키, 요청 헤더 또는 데이터베이스 값으로부터 들어오는지 추적해야 한다.
리다이렉트 주소가 서버의 허용 목록에서 선택되는지, 사용자가 전체 URL을 지정할 수 있는지 확인한다. 외부 URL이 필요한 경우에는 프로토콜, 호스트와 포트를 표준 URL 파서로 분리해 정확하게 비교하는지 점검해야 한다.
문자열의 시작이나 포함 여부만 검사하는 코드가 없는지 확인하고, URL을 검증한 후 다시 디코딩하는 과정이 없는지도 살펴본다.
OAuth와 SSO 인증 과정에서는 등록된 콜백 주소가 정확히 일치하는지 확인해야 한다. 허용된 콜백 주소 안에 또 다른 오픈 리다이렉트 기능이 존재하지 않는지도 함께 점검할 필요가 있다.
자주 묻는 질문
오픈 리다이렉트는 서버 해킹으로 이어지나요?
오픈 리다이렉트만으로 서버에서 코드가 실행되는 것은 일반적이지 않다. 하지만 정상 도메인의 신뢰를 이용한 피싱, 악성파일 유포와 인증 절차 우회에 악용될 수 있어 방치해서는 안 된다.
내부 상대경로만 허용하면 안전한가요?
위험을 크게 줄일 수 있지만 //로 시작하는 프로토콜 상대 URL과 인코딩된 주소를 주의해야 한다. 가능하면 서버에서 허용한 경로 식별자를 사용하는 방식이 가장 안전하다.
정상 도메인으로 시작하는지 확인하면 되나요?
충분하지 않다. 정상 도메인과 비슷한 이름의 다른 도메인이 검사를 통과할 수 있다. URL을 파싱해 실제 호스트를 추출한 뒤 허용 목록과 정확하게 비교해야 한다.
오픈 리다이렉트와 SSRF는 어떤 차이가 있나요?
오픈 리다이렉트는 사용자의 브라우저를 다른 주소로 이동시키는 취약점이다. SSRF는 웹 애플리케이션 서버가 공격자가 지정한 주소로 요청을 보내는 취약점이다. 요청을 보내는 주체가 서로 다르다.
마무리
오픈 리다이렉트 취약점은 사용자가 입력한 주소를 검증하지 않고 리다이렉트 목적지로 사용할 때 발생한다. 공격자는 정상 사이트의 도메인이 포함된 링크를 만들어 사용자를 외부 피싱 페이지로 이동시킬 수 있다.
가장 안전한 대응 방법은 사용자가 전체 URL을 입력하지 못하게 하고, 서버에 등록된 페이지 식별자와 실제 경로를 연결하는 것이다. 외부 이동이 필요하다면 HTTPS 프로토콜과 정확한 호스트 및 포트를 허용 목록으로 관리해야 한다.
오픈 리다이렉트 방어의 핵심은 외부 입력값이 사용자의 최종 이동 주소를 자유롭게 결정하지 못하게 하는 것이다. 허용 목록, 표준 URL 파싱, 인증 콜백 검증과 로그 모니터링을 함께 적용해야 피싱 주소로 악용되는 위험을 줄일 수 있다.
핵심 키워드: 오픈 리다이렉트, Open Redirect, URL 리다이렉션 취약점, 피싱 주소, Location 헤더, 리다이렉트 검증, OAuth 리다이렉트, 오픈 리다이렉트 대응 방법