로그인 복귀 경로 정규화 후 외부 출처 전환 차단

이 글의 목차 6개 항목

문제와 원인

/safe/..//outside.example는 입력 단계의 단일 슬래시 검사를 통과했다. URL 정규화 뒤 pathname이 //outside.example로 바뀌어도 첫 URL 객체의 origin은 https://app.local로 유지됐다. 반환값을 다음 단계에서 다시 상대 URL로 해석하면 outside.example가 호스트가 됐다. %2e%2e로 인코딩한 상위 경로도 같은 우회를 만들었다.

수정 전 코드

safeRelativeReturnPath

export function safeRelativeReturnPath(value: unknown) {
  const candidate = typeof value === "string" ? value : "/";
  if (!candidate.startsWith("/") || candidate.startsWith("//")) return "/";
  try {
    const url = new URL(candidate, "https://app.local");
    if (url.origin !== "https://app.local" || url.pathname.startsWith("/api/auth/")) {
      return "/";
    }
    return `${url.pathname}${url.search}${url.hash}`;
  } catch {
    return "/";
  }
}

수정 후 코드

safeRelativeReturnPath

URL 객체를 만든 뒤 반환할 pathname의 // 시작 여부를 추가로 검사하고 해당 경로를 /로 돌린다. 내부 경로에는 정규화된 pathname과 쿼리·해시를 함께 반환한다.

export function safeRelativeReturnPath(value: unknown) {
  const candidate = typeof value === "string" ? value : "/";
  if (!candidate.startsWith("/") || candidate.startsWith("//")) return "/";
  try {
    const url = new URL(candidate, "https://app.local");
    if (url.origin !== "https://app.local" || url.pathname.startsWith("//") || url.pathname.startsWith("/api/auth/")) {
      return "/";
    }
    return `${url.pathname}${url.search}${url.hash}`;
  } catch {
    return "/";
  }
}

검증 결과

Wrangler의 로컬 Worker에서 수정 전후 원본 safeRelativeReturnPath에 같은 입력 7개를 넣었다. 반환값을 https://app.local 기준 URL로 다시 해석해 최종 origin을 비교했으며, 외부 출처 반환은 2/7개에서 0/7개로 줄었다.

수정 전: 두 입력의 sameOrigin false원본 보기 (새 창)

수정 전: 두 입력의 sameOrigin false

100%원본 보기 (새 창)

확대한 이미지는 상하좌우로 이동할 수 있습니다. 키보드는 이미지 영역에 초점을 두고 방향키를 사용하세요. Esc로 닫습니다.

입력수정 전 반환값수정 후 반환값
/safe/..//outside.example//outside.example, 외부 출처/, 동일 출처
/safe/%2e%2e//outside.example//outside.example, 외부 출처/, 동일 출처
/\outside.example//
//outside.example//
/api/auth/google/start//
/learn/sql/sqld?view=theory#unit입력 경로 유지입력 경로 유지
/admin/../guides/guides/guides
수정 후: 입력 7개의 sameOrigin true원본 보기 (새 창)

수정 후: 입력 7개의 sameOrigin true

100%원본 보기 (새 창)

확대한 이미지는 상하좌우로 이동할 수 있습니다. 키보드는 이미지 영역에 초점을 두고 방향키를 사용하세요. Esc로 닫습니다.