Referrer-Policy의 이해

sejin kim·2022년 12월 10일
10
post-thumbnail

'레퍼러가 없어졌어요!'

사용자들이 서비스를 어떻게/잘 사용하고 있는지를 파악하고 분석하는 일은 중요합니다. 여타 다른 서비스들처럼, 필자가 개발하는 서비스 역시 성과 측정이나 사용자 행동 패턴 분석 등의 목적으로 Google Analytics와 같은 외부 솔루션과 인하우스 솔루션을 복합적으로 활용하여 데이터를 수집하고 있었습니다.

그런데 어느 날 '특정 기간 이후부터 로그에서 레퍼러가 확인되지 않는다'는 연락이 왔습니다. 사용자가 무슨 행동을 했는지(event/action)는 알 수 있지만, 어떤 페이지에서 이동해 왔는지(referer)를 알 수 없게 되어 유입/유출 분석에 문제가 생긴 것입니다.

다행히 원인은 금방 짚어낼 수 있었습니다. 특정 시점부터 갑자기 유실된 것이었기 때문에, 해당 시점을 전후로 발생했던 어떠한 변경을 추적해 나가면 되었기 때문입니다.






동일 출처 정책

웹 사이트 보안의 기본적인 대전제는 동일 출처Same-Origin 입니다. 이것을 Same-Origin Policy(SOP) 라고 합니다.

가능한 공격 벡터를 줄이기 위해, 어떤 출처에서 로드된 문서나 스크립트와 같은 리소스가 다른 출처의 리소스와 상호작용할 수 있는 방법을 제한하는 보안 정책의 핵심입니다.

어떤 요청이 동일한 출처에서 발생하지 않은 경우에는 Cross-Site 또는 Cross-Origin 이라고 하며, 개인 정보 보호 및 웹 공격 방어 차원에서 특정한 기능이나 정보가 제한됩니다.

대개 프론트엔드에서는 CORS와 같이, 백엔드 서버와 통신한다던가 하는 통상적인 개발 과정에서 이러한 이슈와 흔히 맞닥뜨리기 마련인데, 이 중에서도 요청이 발생한 출처의 URL 정보를 의미하는 Referer 헤더가 일부 또는 전체 생략되는 현상이 익숙할 수 있습니다.


HTTP 레퍼러를 다루는 글에서 아주 흔하게 언급되는 내용이기는 하지만, 스펙 정의 단계에서 'referrer'를 'referer'로 오타를 낸 것에서 기인해 'HTTP referer'가 되었다는 배경이 있습니다. RFC 1945 - section 10.13
다만 Referrer-Policy 헤더에서는 'referrer'로 철자가 정확하게 되어 있는 등 일관적이진 않습니다.


어떤 상황에서 이러한 동작이 발생하는지 알아보기에 앞서, originsite의 차이가 무엇인지 각 정의를 먼저 살펴보겠습니다.






same-site, same-origin


origin(출처)이란 scheme + hostname + port 의 조합입니다. 예를 들어, URL이 https://www.example.com:443/search?query=frontend인 경우, origin은 https://www.example.com:443이 됩니다.



출처 A출처 Bsame-origin 여부
https://www.example.com:443https://www.naver.com:443cross-origin : 호스트가 다름
https://example.com:443cross-origin : 호스트가 다름
https://search.example.com:443cross-origin : 호스트가 다름
http://www.example.com:443cross-origin : 프로토콜이 다름
https://www.example.com:80cross-origin : 포트가 다름
https://www.example.com:443same-origin : 완전 일치
https://www.example.comsame-origin : 포트가 암시적으로 일치


site(사이트).com, .org 와 같이 IANA에 등록된 TLD(Top-Level Domain, 최상위 도메인)와 바로 그 앞의 도메인의 조합을 말합니다. 가령 https://www.example.com:443/search인 경우라면, site는 example.com 이 됩니다.

다만 이것만으로는 식별이 불가한 경우가 있을 수 있어서, .co.kr 이나 .github.io 같이 eTLD (effective Top-Level Domain, 유효 최상위 도메인) 이라는 개념을 추가로 정의하여, eTLD + 1 으로 site를 정의하기도 합니다.




출처 A출처 Bsame-site 여부
https://www.example.com:443https://www.practice.com:443cross-site : 도메인이 다름
https://search.example.com:443(schemeful) same-site : 서브 도메인은 무관함
http://www.example.com:443same-site : 스킴은 무관함
또는
cross-site : schemeful same-site 인 경우 cross-site로 간주
https://www.example.com:80(schemeful) same-site : 포트는 무관함
https://www.example.com:443same-site : 완전 일치
https://www.example.com(schemeful) same-site : 포트는 무관함


HTTP 프로토콜의 취약점을 방어하기 위해, 스킴에 한해서는 더 엄격하게 정의되는 경우가 존재할 수 있는데, 이를 Schemeful Same-Site 라고 정의합니다.

당초 same-site를 판단할 때 스킴을 고려하지 않았던 것은, HTTP에서 HTTPS로 전환하는 사이트를 지원해야 했었기 때문입니다. 그래서 동일한 호스트에서 보안(HTTPS) 환경일 때와 비보안(HTTP)환경일 때를 모두 same-site로 간주했던 것인데, 이는 공격자에게 있어 보호 정책을 우회할 수 있는 포인트가 되므로 이를 보완하고자 Schemeful Same-Site가 제안된 것입니다.

Sec-Fetch-Site 라는, Fetch metadata request header에서 제공되는 추가 헤더를 참고하면 아래와 같이 리소스 출처의 관계를 직관적으로 파악할 수 있지만, 브라우저에 따라 지원되지 않을 수 있어 경우에 따라서는 적절히 활용하지 못할 수 있습니다.


Sec-Fetch-Site: cross-site
Sec-Fetch-Site: same-origin
Sec-Fetch-Site: same-site
Sec-Fetch-Site: none






Referer

Referer의 정확한 정의는, '현재 요청을 보낸 페이지의 절대 혹은 부분 주소' 입니다. 아래와 같은 경우에 존재할 수 있는데,


  • 사용자의 링크 클릭
  • 이미지, 스크립트, iframe, 기타 리소스 등 브라우저의 하위 리소스(subresource) 요청

이때 '#section' 과 같은 Anchor(Hash, Fragment)는 포함되지 않으며, 요청에 대한 Referrer-Policy에 따라 오직 아래와 같이 origin, path, querystring으로 구성되거나, 아예 비어 있는 경우만 존재할 수 있습니다.


Referer: https://www.example.com/ko-KR/docs (Full URL : origin + path)
Referer: https://www.example.com/search?query=frontend (Full URL : origin + path + querystring)
Referer: https://www.example.com/ (Origin only)


이렇게 정보를 제한하는 이유는, 추적이나 도용에 악용되거나 사용자의 개인 정보가 노출될 여지가 있기 때문입니다.



극단적인 예시일 수 있지만, 위에서처럼 사이트에 방문한 사용자가 어디에서 왔고 누구인지를 식별할 수 있게 될 수 있습니다. 요컨대 잠재적인 취약점이 될 수 있는 것입니다.

비단 보안 문제가 아니더라도, 사용자가 굳이 공개하고 싶지 않았을 정보를 필요 이상으로 과도하게 드러내게 될 수 있다는 개인 정보 문제도 존재합니다.

그래서 브라우저에서는 가급적 레퍼러를 제한하려고 하는데, 특히 HTTP인 경우에는 보안적으로 전혀 신뢰할 수 없기 때문에 레퍼러를 아예 제거해 버리기도 합니다.






Referrer-Policy

  • no-referrer: Referer 헤더 생략
  • no-referrer-when-downgrade: 프로토콜 보안 수준이 동일하거나 더 높을 때(HTTP->HTTP, HTTP->HTTPS, HTTPS->HTTPS)는 Full URL을, 더 낮을 때(HTTPS->HTTP, HTTPS->File)는 No data
  • origin: Origin만 전송
  • origin-when-cross-origin: 동일한 프로토콜 보안 수준에서 same-origin 요청일 때는 Full URL을, cross-origin 요청 및 보안 수준이 낮을 때는 Origin만 전송
  • same-origin: same-origin일 때는 Full URL을, cross-origin일 때는 생략
  • strict-origin: 프로토콜 보안 수준이 동일할 때(HTTP->HTTP, HTTPS->HTTPS)에 한해 Origin만 전송
  • strict-origin-when-cross-origin: same-origin일 때는 Full URL, cross-origin일 때는 프로토콜 보안 수준이 유지될 때에 한해 Origin만 전송, 보안 수준이 낮아진다면 No data
  • unsafe-url: 무조건 Full URL (권장되지 않음)

  • 프로토콜 보안 수준(HTTP/HTTPS)를 고려하는 정책에서는 보안성이 떨어지는 HTTP -> HTTP 출처 간의 요청이라고 해도 HTTPS -> HTTPS일 때와 동일하게 처리합니다. 이는 보안 수준의 다운그레이드 여부, 즉 요청으로 인해 암호화된 출처의 데이터가 암호화되지 않은 출처에 노출될 수 있는지 여부가 중요한 것이기 때문입니다.
  • same-origin이라면, 프로토콜 보안 수준도 같을 것이므로 다운그레이드도 발생하지 않은 것입니다.

Referer는 위에서 언급했던 것처럼 레퍼러 정책, Referrer-Policy가 어떻게 설정되느냐에 따라 결정됩니다. 만약 직접 명시하지 않으면 브라우저의 기본 정책을 따르게 되는데, 가급적이면 직접 명시하는 편이 바람직합니다. 그 이유는 아래와 같습니다.


  • 브라우저에 따라 Referrer-Policy의 기본값이 다르거나, 구체적인 구현 내용이 다를 수도 있습니다. 또한 같은 브라우저에서도, 시크릿 모드(Incognito mode)와 같이 브라우징 모드에 따라서도 다르게 적용될 수 있는 등 일관적이지 않습니다.

  • Referer trimming과 같이, 브라우저는 cross-origin 요청일 때 최대한 레퍼러를 엄격하게 제한하려고 하는 것이 기본적인 기조입니다. 때문에 브라우저에 의해 강제로 제어되기보다는, 개발자가 직접 예측 가능한 범위에서 의도적으로 제어하는 편이 테스트 등 통제의 측면에서 적절할 수 있습니다.


브라우저 별 기본 레퍼러 정책은 아래와 같습니다. 현재 시점에서는 strict-origin-when-cross-origin 정책이 보편적으로 적용된다고 할 수 있는데, 이는 대부분의 일반적인 상황에서 이 정책이 가장 합리적이고, 보안적으로도 바람직하기 때문입니다.



BrowserReferrer-Policy
Chrome85 버전부터 strict-origin-when-cross-origin (이전 버전의 경우 no-referrer-when-downgrade)
Firefoxstrict-origin-when-cross-origin
Edgeno-referrer-when-downgrade → strict-origin-when-cross-origin
Safaristrict-origin-when-cross-origin 과 유사하게 동작 (자세한 내용은 webkit 블로그 참고)


일반적으로 레퍼러 정책은 아래와 같은 형태로 요청 헤더에 명시하지만, HTML이나 JavaScript로도 설정할 수 있습니다.


Referrer-Policy: no-referrer
Referrer-Policy: no-referrer-when-downgrade
Referrer-Policy: origin
Referrer-Policy: origin-when-cross-origin
Referrer-Policy: same-origin
Referrer-Policy: strict-origin
Referrer-Policy: strict-origin-when-cross-origin
Referrer-Policy: unsafe-url


HTML에서는 <meta> 요소에서 레퍼러 정책을 명시하거나, <a>, <img>, <iframe>, <area>, <script>, <link> 와 같은 요소에 referrerpolicy 속성을 추가하여, document 전체 또는 element 레벨로 조금 더 디테일하게 설정해 줄 수 있습니다. 다만 element 레벨에서 정책을 설정하는 건 브라우저 호환성 이슈가 있을 수 있으니 확인이 필요합니다. caniuse


<meta name="referrer" content="strict-origin-when-cross-origin" />
<img src="" referrerpolicy="no-referrer-when-downgrade" />
<a src="" href="" referrerpolicy="no-referrer-when-downgrade"></a>

JavaScript에서는 옵션으로 요청 헤더를 설정하는 식으로 제어할 수 있습니다. Fetch API를 예로 들면 아래와 같습니다.


fetch('https://example.com/page', {
    referrerPolicy: 'no-referrer-when-downgrade'
});

이때 설정 방법에 따라 정책의 우선순위가 다른데, browser -> page (HTTP header, meta) -> element 순으로 우선순위가 높아집니다. 즉, element 레벨의 정책이 최우선 적용됩니다.






문제의 원인과 해결

다시 서두에서 언급했던 이야기로 되돌아와 보자면, 결국 레퍼러가 유실되었던 문제는 페이지 간 스킴(HTTP/HTTPS)이 달랐기 때문이었습니다.

이런 부분에서 문제가 되었다는 점이 꽤나 민망할 수 있는 부분이지만, 당시 레퍼러 유실이 발생했던 페이지는 HTTPS로 마이그레이션이 되어 있지 않았던 상태였습니다.

이런 상황에서 페이지 간 사용자 행동 로그를 수집하는 경우가 있었던 것이고, 브라우저의 기본 정책인 strict-origin-when-cross-origin에 의해 레퍼러가 제거되었던 것입니다.

모든 페이지가 서로 동일한 보안 컨텍스트였다면 요청시 origin 정보를 확인할 수 있었겠지만, 일부 레거시 페이지가 잔존해 있었기 때문에 발생한 이슈였다고 할 수 있겠습니다.


사실, 레퍼러 같은 정보는 애초에 항상 존재하지 않을 수도 있다는 점을 전제하고 접근하는 편이 바람직합니다. 얼마든지 조작도 가능한 데다, 흔히 사용되는 광고 차단기(AdGuard 등)에 의해서도 제거될 수 있고, 심지어는 명시적으로 정책을 설정했더라도 그게 브라우저의 기본 정책보다 더 낮은 보안 수준이라면 브라우저가 이를 무시하고 기본 정책을 적용해버리기도 하기 때문입니다.

그래서 레퍼러에 의존하여 무언가를 하려는 것은 적절치 않을 수 있습니다. 만약 더 신뢰성 있거나 디테일한 정보가 필요하다면 다른 대안을 고려해야 합니다.

여하튼 이를 계기로 전반적으로 레퍼러 정책을 검토하여 필요한 경우 명시적으로 설정했고, 상황에 따라서는 레퍼러 대신 Origin 헤더를 확인한다거나 URL 쿼리 파라미터에 필요한 정보를 추가하는 식으로 대응하였습니다.






참고 문서

profile
퇴고를 좋아하는 주니어 웹 개발자입니다.

2개의 댓글

comment-user-thumbnail
2023년 9월 13일

좋은 글 감사합니다. 도움이 많이 됬습니다.

답글 달기
comment-user-thumbnail
2024년 2월 1일

안녕하세요! 웹과 관련해서 문의드리고 싶은데 혹시 대화가 가능하실까요?! 메일도 드렸습니다!

답글 달기