1. 소개
흥미로운 웹 애플리케이션은 일반적으로 웹에 노출되는 엔드포인트가 많으며, 그 엔드포인트는 사용자의 민감한 데이터를 노출하거나 사용자를 대신해 동작할 수도 있습니다. 사용자의 브라우저는 이러한 엔드포인트로 요청을 보내도록 쉽게 유도될 수 있으며, 해당 요청에는 사용자의 상황별 자격 증명(쿠키, 인트라넷 내 특권 위치 등)이 포함될 수 있습니다. 따라서, 애플리케이션은 이러한 엔드포인트가 오용되지 않도록 동작 방식을 매우 신중하게 설계해야 합니다.
일부 경우에는 주의하는 것이 어렵고("간단한" CSRF), 다른 경우(사이트 간 검색, 타이밍 공격 등)에는 사실상 불가능할 수 있습니다. 후자의 범주에는 특정 응답을 생성하기 위한 서버 측 처리에 기반한 타이밍 공격 및 길이 측정(웹 기반 타이밍 공격과 수동 네트워크 공격자 모두 포함)이 포함됩니다.
서버가 이러한 후자 범주를 완화하기 위해 요청이 어떻게 만들어졌는지에 따라 개별 요청에 응답할지 더 지능적으로 판단할 수 있다면 도움이 될 것입니다. 예를 들어, 은행의 "내 돈 전부 이체"
엔드포인트가
img
태그에서 참조될 것으로 예상하지는 않을 것이고,
evil.com에서 합법적인 요청이 발생할 일도 없을 것입니다. 이상적으로는, 서버가 이러한 요청을 애플리케이션 백엔드로 전달하기 전에 a
priori로 거부할 수 있어야 합니다.
여기서는 사용자 에이전트가 추가적인 컨텍스트를 아웃바운드 요청에 첨부함으로써 이러한 의사결정이 가능해지는 메커니즘을 설명합니다. Fetch 메타데이터 헤더 집합에 메타데이터를 서버로 전달함으로써, 애플리케이션이 일부 사전 조건 확인만으로 빠르게 요청을 거부할 수 있게 해줍니다. 이는 필요하다면 애플리케이션 레이어를 넘어(리버스 프록시, CDN 등) 상위 계층에서 처리하는 것도 가능합니다.
1.1. 예시
picture
요소에 의해 생성된 요청에는 다음과 같은 HTTP 요청 헤더가 포함됩니다:
Sec-Fetch-Dest: image Sec-Fetch-Mode: no-cors Sec-Fetch-Site: cross-site
https://example.com에서 https://example.com/으로의 상위 탐색이 사용자의 인페이지 링크 클릭에 의해 발생할 경우,
다음과 같은 HTTP 요청 헤더가 포함됩니다:
Sec-Fetch-Dest: document Sec-Fetch-Mode: navigate Sec-Fetch-Site: same-origin Sec-Fetch-User: ?1
2. Fetch 메타데이터 헤더
다음 섹션에서는 여러 Fetch 메타데이터 헤더를 정의합니다. 각각은 서버에 흥미로운 요청 속성을 노출합니다.
2.1. Sec-Fetch-Dest HTTP 요청 헤더
Sec-Fetch-Dest HTTP 요청 헤더는 요청의 대상을 서버에 노출합니다. 이 헤더는 값이 반드시 토큰이어야 하는 구조화 필드(Structured Field)입니다. [RFC9651]
ABNF는 다음과 같습니다:
Sec-Fetch-Dest = sf-token
유효한 Sec-Fetch-Dest 값은 요청의 대상으로 [Fetch]에서 정의하는 값의 집합을 포함합니다.
아직 알려지지 않은 요청 타입과의 호환성을 위해, 이 헤더에 유효하지 않은 값이 포함될 경우 서버는 무시해야 합니다.
//fetch()의 destination은 빈 문자열입니다: Sec-Fetch-Dest: empty //<img>의 destination은 "image"입니다 Sec-Fetch-Dest: image //new Worker()의 destination은 "worker"입니다 Sec-Fetch-Dest: worker // 상위 탐색의 destination은 "document"입니다 Sec-Fetch-Dest: document //<iframe>탐색의 destination은 "iframe"입니다 Sec-Fetch-Dest: iframe
Sec-Fetch-Dest 헤더 설정을 위해, 요청 r에
대해:
-
단언: r의 url은 신뢰할 수 있는 URL임을 보장해야 합니다.
-
만약 r의 destination이 빈 문자열이라면, header의 값을 "
empty"로 설정합니다. 그렇지 않으면, header의 값을 r의 destination으로 설정합니다.참고: Fetch의 빈 문자열 destination은 처리 단순화를 위해 "
empty" 토큰으로 명시적으로 매핑합니다. -
구조화 필드 값 설정 `
Sec-Fetch-Dest`/header 를 r의 헤더 목록에 설정합니다.
2.2. Sec-Fetch-Mode HTTP 요청 헤더
Sec-Fetch-Mode HTTP 요청 헤더는 요청의 mode
를 서버에 노출합니다. 값이 반드시 토큰이어야 하는 구조화
필드입니다. [RFC9651]
ABNF는 다음과 같습니다:
Sec-Fetch-Mode = sf-token
유효한 Sec-Fetch-Mode 값에는 "cors", "navigate",
"no-cors", "same-origin",
"websocket"이 포함됩니다. 아직 알려지지 않은 요청 타입과의 호환성을 위해, 이 헤더에 유효하지 않은 값이 포함되는 경우 서버는 해당 헤더를 무시해야 합니다.
Sec-Fetch-Mode 헤더 설정을 위해, 요청 r에
대해:
-
단언: r의 url은 신뢰할 수 있는 URL이어야 합니다.
-
header의 값을 r의 mode로 설정합니다.
-
구조화 필드 값 설정 `
Sec-Fetch-Mode`/header 를 r의 헤더 목록에 설정합니다.
2.3. Sec-Fetch-Site HTTP 요청 헤더
Sec-Fetch-Site HTTP 요청 헤더는
요청 발신자의 오리진과 타겟 오리진 간의 관계를 노출합니다. 이는 값이 토큰인 구조화 필드입니다. [RFC9651]
ABNF는 다음과 같습니다:
Sec-Fetch-Site = sf-token
유효한 Sec-Fetch-Site 값에는 "cross-site", "same-origin",
"same-site", "none"이 포함됩니다.
아직 알려지지 않은 요청 타입과의 호환성을 위해, 서버는 이 헤더에 유효하지 않은 값이 포함된 경우 무시해야 합니다.
Sec-Fetch-Site 헤더 설정을 위해, 요청 r에
대해:
-
단언: r의 url은 신뢰할 수 있는 URL이어야 합니다.
-
header의 값을
same-origin으로 설정합니다. -
만약 r이 사용자의 명시적 인터랙션(주소 직접 입력, 북마크 클릭 등)에 의해 발생한 네비게이션 요청라면, header의 값을
none으로 설정합니다.참고: 이 다소 모호하게 정의된 단계에 대한 자세한 내용은 § 4.3 직접 사용자에 의해 시작된 요청을 참조하세요.
-
만약 header의 값이
none이 아니라면, r의 url 목록의 각 url에 대해: -
구조화 필드 값 설정 `
Sec-Fetch-Site`/header 를 r의 헤더 목록에 설정합니다.
2.4. Sec-Fetch-User HTTP 요청 헤더
Sec-Fetch-User HTTP 요청 헤더는 네비게이션 요청이
사용자 활성화에 의해 트리거되었는지 여부를 노출합니다. 이는 값이 불리언인 구조화 필드입니다. [RFC9651] ABNF는 다음과 같습니다:
Sec-Fetch-User = sf-boolean
참고: 이 헤더는 오직 네비게이션 요청에
한해 전달되며, 값이 true일 때만 포함됩니다.
앞으로 이 정보를 노출해 개선될 만한 사용 사례가 명확해지고, 모든 서브리소스 요청 타입에 대해 해당 상태 정의가 합의된다면 범위 확대도 가능하나, 현재로서는 네비게이션 요청이 명확하게 유용하며
상호운용적으로 정의하기 쉽습니다.
Sec-Fetch-User 헤더 설정을 위해, 요청 r에
대해:
-
단언: r의 url은 신뢰할 수 있는 URL이어야 합니다.
-
만약 r이 네비게이션 요청이 아니거나, r의 user-activation이
false라면, 반환합니다. -
header의 값을
true로 설정합니다. -
구조화 필드 값 설정 `
Sec-Fetch-User`/header 를 r의 헤더 목록에 설정합니다.
3. Fetch 및 HTML과의 통합
Sec-Fetch-User를 지원하기 위해, request는 user-activation을 가지며, 이는 HTML의 create navigation params by fetching 알고리즘에 의해 별도로
지정되지 않는 한 false입니다.
Fetch 메타데이터 헤더는 Fetch의 "HTTP-network-or-cache" 알고리즘 내에서 다음 단계들을 사용해 아웃바운드 요청에 추가됩니다. 통합에 대한 자세한 내용은 해당 명세를 참조하세요. [FETCH]
-
만약 r의 url이 신뢰할 수 있는 URL이 아니라면, 반환합니다.
-
Sec-Fetch-Dest헤더 설정을 r에 수행합니다. -
Sec-Fetch-Mode헤더 설정을 r에 수행합니다. -
Sec-Fetch-Site헤더 설정을 r에 수행합니다. -
Sec-Fetch-User헤더 설정을 r에 수행합니다.
4. 보안 및 프라이버시 고려사항
4.1. 리디렉션
사용자 에이전트는 리디렉션 체인의 각 요청에 Sec-Fetch-Site 헤더를 함께 보냅니다. cross-origin이나
cross-site 리디렉션이 있을 경우 혼란을 방지하기 위해 헤더의 값이 변경됩니다.
Sec-Fetch-Site 헤더 설정 알고리즘은 request의 전체 url 목록을 순회하며, 목록에 있는 어떤 URL이라도 요청의 current url과 cross-site일 경우 cross-site를, 모든
URL이 same-site일 경우 same-site를, 모든 URL이 same-origin일 경우 same-origin을 전송합니다.
예를 들어, https://example.com/이 https://example.com/redirect를 요청하면, 최초 요청의
Sec-Fetch-Site 값은 same-origin입니다. 그 응답이
https://subdomain.example.com/redirect로 리디렉션된다면 해당 요청의 Sec-Fetch-Site 값은
same-site(https://subdomain.example.com/과 https://example.com/은 등록
가능한 도메인이 동일하기 때문)입니다. 이어서 https://example.net/redirect로 리디렉션된다면 Sec-Fetch-Site 값은
cross-site입니다(https://example.net/은 https://example.com/ 및
https://subdomain.example.com/과 same-site가 아님). 이후 다시 https://example.com/로
리디렉션되어도, 리디렉션 체인에 https://example.net/이 있으므로 최종 요청의 Sec-Fetch-Site 값은 여전히
cross-site입니다.
참고: Sec-Fetch-Site: None의 특수한 경우, 주소창에 단축
링크를 복사/붙여넣기 하는 일반적인 상황을 지원하기 위해 리디렉션 도중에도 해당 값을 유지하는 것이 합리적입니다. 즉, 사용자 에이전트가
https://sho.rt/link로의 주소창 내비게이션을 Sec-Fetch-Site: none으로 처리하면, 이후
https://target.com/long/path/goes/here로의 리디렉션이 발생해도 역시 Sec-Fetch-Site: none을 유지해야
합니다.
4.2. Sec- 접두사
이 문서에서 정의한 모든 헤더는 Sec-로 시작하며, 따라서 모두 금지된 응답 헤더 이름이 되어 JavaScript에서 수정할 수 없습니다. 이는 악의적인 웹사이트가
요청에 위조된 메타데이터를 사용자 에이전트로 하여금 전송하게 만드는 것을 방지하며, 사이트가 광고된 정보에 더 신뢰를 가지고 적절히 대응할 수 있도록 합니다.
4.3. 직접 사용자에 의해 시작된 요청
Sec-Fetch-Site 헤더를 설정할 때, 사용자 에이전트는 "사용자의 명시적
상호작용에 의해 발생한 내비게이션 요청"을 구분해야 합니다. 이 표현은 다소 모호하며, HTML에서 차용된 것으로 설명에
따르면 "사용자 에이전트는 이 명세에 정의된 것 이외에도 사용자가 명시적으로 브라우징 컨텍스트를 내비게이션하도록 다양한 방법을 제공할 수 있다"고 합니다.
목표는 주어진(잠재적으로 악의적일 수 있는) 웹사이트가 제어하는 "웹" 내비게이션(예: 링크, window.location setter, 폼 제출 등)과, 웹사이트가 제어하지 않는 내비게이션(예:
주소창, 북마크 등 직접 입력) 간을 구분하는 것입니다. 전자는 상황에 따라 Sec-Fetch-Site 헤더의 값이 same-origin,
same-site 또는 cross-site가 되며, 후자는 값이 none이 됩니다. 실제 특정 사이트의 책임이 아닌 요청임을
명확히 하여, 서버에서 이를 신뢰받은 사용자 의도로 처리할 수 있게 합니다.
각 사용자 에이전트는 상황에 따라 이들 가운데 어느 쪽에 해당하는지 자체적으로 판단할 가능성이 높으며, 이런 경우를 위한 자동화된 테스트 스위트를 공유하기는 어려우나, 흔히 발생하는 동작에 대해서는 일관된 행동이 바람직합니다. 몇 가지 예시는 다음과 같습니다:
-
주소창 내비게이션: 일반적으로 이런 내비게이션은 직접 사용자에 의해 트리거된 것으로 간주해야 하며,
Sec-Fetch-Site: none을 포함해야 합니다. 사용자 에이전트가 붙여넣기 동작(특히 복사 동작의 출처가 특정 오리진임을 추적할 수 있을 경우)과 직접 키보드 입력을 구분하여 처리하는 것도 합리적일 수 있습니다. -
사용자 에이전트 UI(북마크, 새탭 등)에서 발생하는 내비게이션: 사용자가 에이전트 UI 내의 링크와 상호작용하는 경우도 주소창 입력과 유사하게 취급되어
Sec-Fetch-Site: none을 포함해야 합니다. -
링크 컨텍스트 메뉴(예: "새 창에서 열기")를 통한 내비게이션: 링크의 대상이 페이지에서 제어되므로, 사용자 에이전트는 해당 내비게이션을 사이트 제어로 처리하고 링크를 제공한 사이트와 열리는 사이트 간의 관계에 맞게
Sec-Fetch-Site헤더를 설정해야 합니다. -
Ctrl-클릭(새 탭에서 열기): 위와 같은 논의가 동일하게 적용됩니다.
-
히스토리 내비게이션(뒤로가기 등):
-
드래그 앤 드롭: 여기서는 드래그한 콘텐츠의 출처에 따라 동작을 구분하는 것이 합리적입니다. 탭에서 드래그한 콘텐츠라면 오리진을 식별할 수 있으므로
Sec-Fetch-Site를 적절히 설정하고, 그렇지 않고 (예: 북마크바, 다른 앱 등)에서 끌었을 경우Sec-Fetch-Site: none이 적합할 수 있습니다.
4.4. 확장 프로그램에 의해 시작된 요청
일부 사용자 에이전트는 사용자의 웹 환경 제어를 강화하기 위해 요청을 생성할 수 있는 확장 프로그램을 지원합니다. 이들은 웹 플랫폼의 엄밀한 범위 밖이지만, 사용자 에이전트는 이러한 요청을 서버에 어떻게 표현할지 신중하게 고민해야 합니다. 일반적으로 사용자 에이전트는 두 가지 목표를 달성해야 합니다:
-
특정 사이트에 대한 특수 권한이 없는 확장 프로그램은 해당 사이트의 서버 측 Fetch 메타데이터 로직을 우회하는 요청을 유발할 수 없어야 합니다.
-
개발자가 확장 프로그램에서 시작된 요청임을 인지하여, 서버 Fetch 메타데이터 로직에서 예외 처리를 선택적으로 할 수 있게 해야 합니다. 이는 개발자가 합법적인 사용자 이익을 침해하지 않으면서도 Fetch 메타데이터 보호를 자신 있게 배포할 수 있도록 합니다.
이러한 목표를 위해, 사용자 에이전트는 다음과 같은 동작을 구현하는 것이 권장됩니다:
-
확장 프로그램이 특정 URL 접근 권한이 없다면, 해당 URL로의 요청은 일반 웹 요청과 마찬가지로
Sec-Fetch-Site값이cross-site인 헤더를 포함하게 할 수 있습니다. 권한이 있다면Sec-Fetch-Site의 값은same-origin이 될 수 있습니다. -
확장 프로그램 컨텍스트에서 나가는 요청에는, 구현 정의 값의
Origin헤더를 추가하여 서버가 확장 프로그램 시작 요청과 일반 웹 요청을 구분 가능하게 할 수 있습니다.
5. 배포 관련 고려사항
5.1. Vary
특정 엔드포인트의 응답이 클라이언트가 전달한 Fetch 메타데이터 헤더 값에 따라 달라진다면, 캐시가 요청을 적절히 처리하도록 Vary
헤더를 반드시 포함해야 합니다 [RFC9110]. 예:
Vary: Accept-Encoding, Sec-Fetch-Site.
5.2. 헤더 비대
이전 버전의 본 문서에서는 딕셔너리 형태의 단일 Sec-Metadata 헤더를 정의했으나, 이후 논의(및 Mark Nottingham의 훌륭한 [mnot-designing-headers])를 통해, 하나의 딕셔너리 대신 각각 하나의
토큰만을 포함하는 단순한 헤더 여러 개로 설계가 이동했습니다. 이 방식은 HTTP의 HPACK 압축 알고리즘 하에서 훨씬 더 높은 성능을 보장합니다.
관련 논의는 w3ctag/design-reviews#280의 리뷰 스레드에서 확인할 수 있습니다.
6. IANA 관련 사항
다음 Fetch 메타데이터 헤더들을 위해 메시지 헤더 필드 레지스트리를 다음과 같이 업데이트해야 합니다: [RFC3864]
6.1. Sec-Fetch-Dest 등록
- 헤더 필드 이름
-
Sec-Fetch-Dest
- 적용 프로토콜
-
http
- 상태
-
standard
- 작성자/변경 컨트롤러
-
Me
- 명세 문서
-
이 명세서 (자세한 내용은 § 2.1 Sec-Fetch-Dest HTTP 요청 헤더 참조)
6.2. Sec-Fetch-Mode 등록
- 헤더 필드 이름
-
Sec-Fetch-Mode
- 적용 프로토콜
-
http
- 상태
-
standard
- 작성자/변경 컨트롤러
-
Me
- 명세 문서
-
이 명세서 (자세한 내용은 § 2.2 Sec-Fetch-Mode HTTP 요청 헤더 참조)
6.3. Sec-Fetch-Site 등록
- 헤더 필드 이름
-
Sec-Fetch-Site
- 적용 프로토콜
-
http
- 상태
-
standard
- 작성자/변경 컨트롤러
-
Me
- 명세 문서
-
이 명세서 (자세한 내용은 § 2.3 Sec-Fetch-Site HTTP 요청 헤더 참조)
6.4. Sec-Fetch-User 등록
- 헤더 필드 이름
-
Sec-Fetch-User
- 적용 프로토콜
-
http
- 상태
-
standard
- 작성자/변경 컨트롤러
-
Me
- 명세 문서
-
이 명세서 (자세한 내용은 § 2.4 Sec-Fetch-User HTTP 요청 헤더 참조)
7. 감사의 글
Anne van Kesteren, Artur Janc, Dan Veditz, Łukasz Anforowicz, Mark Nottingham, Roberto Clapis께 이 메커니즘 설계에 많은 도움을 주신 데 감사드립니다.