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의 현재 URL은 잠재적으로 신뢰할 수 있는 URL입니다.
-
r의 대상이 빈 문자열이면, header의 값을 "
empty" 문자열로 설정합니다. 그렇지 않으면 header의 값을 r의 대상으로 설정합니다. -
r의 헤더 목록에서 `
Sec-Fetch-Dest`/header로 구조화된 필드 값을 설정합니다.
2.2. Sec-Fetch-Mode HTTP 요청 헤더
Sec-Fetch-Mode HTTP 요청 헤더는 요청의
모드를 서버에 노출합니다. 이는 값이 반드시
토큰이어야 하는 구조화
필드입니다. [RFC9651] 해당
ABNF는 다음과 같습니다:
Sec-Fetch-Mode = sf-token
유효한 Sec-Fetch-Mode 값에는 "cors", "navigate",
"no-cors", "same-origin",
그리고 "websocket"이 포함됩니다. 아직 알려지지 않은 요청 유형과의 향후 호환성을 지원하기 위해,
서버는 이 헤더에 유효하지 않은 값이 포함되어 있으면 이를 무시해야 합니다.
Sec-Fetch-Mode 헤더를 설정하려면:
-
단언: r의 현재 URL은 잠재적으로 신뢰할 수 있는 URL입니다.
-
header의 값을 r의 모드로 설정합니다.
-
r의 헤더 목록에서 `
Sec-Fetch-Mode`/header로 구조화된 필드 값을 설정합니다.
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의 현재 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의 현재 URL은 잠재적으로 신뢰할 수 있는 URL입니다.
-
header의 값을
true로 설정합니다. -
구조화된 필드 값을 설정합니다 `
Sec-Fetch-User`/header를 r의 헤더 목록에 설정합니다.
3. Fetch 및 HTML과의 통합
Sec-Fetch-User를 지원하기 위해, 요청에는 사용자 활성화가 있으며, 그 값은 false입니다.
단, HTML의 가져오기를 통해 탐색 매개변수 생성 알고리즘에 의해 달리 설정된 경우는 예외입니다.
Fetch 메타데이터 헤더는 다음 단계에 따라 Fetch의 "HTTP-network-or-cache" 알고리즘 내에서 나가는 요청에 추가됩니다. 통합 세부 사항은 해당 명세를 참조하십시오 [FETCH].
-
r의 현재 URL이 잠재적으로 신뢰할 수 있는 URL이 아니면 반환합니다.
-
r에 대해
Sec-Fetch-Dest헤더를 설정합니다. -
r에 대해
Sec-Fetch-Mode헤더를 설정합니다. -
r에 대해
Sec-Fetch-Site헤더를 설정합니다. -
r에 대해
Sec-Fetch-User헤더를 설정합니다.
4. 보안 및 프라이버시 고려사항
4.1. 리디렉션
사용자 에이전트는 리디렉션 체인의 각 요청과 함께 Sec-Fetch-Site 헤더를
전송합니다. 혼동을 완화하기 위해 교차 출처 또는 교차 사이트
리디렉션이 있으면 헤더의 값이 변경됩니다.
Sec-Fetch-Site 헤더를 설정하는 알고리즘은 요청의 전체
URL 목록을 순회하며, 목록에 있는 URL 중 하나라도
요청의 현재 URL과 교차 사이트이면 cross-site를 전송하고, 목록의 모든
URL이
요청의 현재 URL과 동일 사이트인 경우에만 same-site를 전송하며, 목록의 모든
URL이
요청의 현재 URL과 동일 출처인 경우에만 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/과 동일 사이트가 아니기 때문입니다). 해당
응답이
최종적으로 https://example.com/으로 다시 리디렉션되더라도, 마지막 요청의
Sec-Fetch-Site
값은 여전히 cross-site입니다(리디렉션 체인에
https://example.net/이 포함되어 있으며, 이 URL은
여전히 다른 URL들과 동일 사이트가 아니기 때문입니다.
참고: 특수한 경우인
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에서 가져온 것으로, HTML은
다음과 같이
제안합니다.
"사용자 에이전트는 이 명세에 정의된 방법 외에도 사용자가 탐색 컨텍스트를
명시적으로 탐색하도록 하는 다양한 방법을 제공할 수 있습니다."
목표는 특정 (잠재적으로 악의적인!) 웹사이트가 제어하는 "웹 기반" 탐색(예: 링크,
window.location 설정자, 양식 제출 등)과 그렇지 않은 탐색(예: 사용자 에이전트의
주소 표시줄, 북마크 등에 대한 사용자 상호작용)을 구별하는 것입니다. 전자의 경우
상황에 따라 값이 same-origin, same-site 또는
cross-site인 Sec-Fetch-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로 보내는 요청에는 일반적인 웹 요청과 마찬가지로 값이
cross-site인Sec-Fetch-Site헤더가 포함될 수 있습니다. 확장 기능이 특정 URL에 접근할 수 있는 경우,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께 이 메커니즘 설계에 많은 도움을 주신 데 감사드립니다.