웹 개발자를 위한 사이트 격리

데스크톱의 Chrome 67에는 사이트 격리가 기본적으로 사용 설정되는 새로운 기능이 있습니다. 이 도움말에서는 사이트 격리의 정의, 사이트 격리가 필요한 이유, 웹 개발자가 사이트 격리를 알아야 하는 이유를 설명합니다.

사이트 격리란 무엇인가요?

인터넷은 고양이 동영상을 보고 암호화폐 지갑을 관리하는 용도이지만, fluffycats.example에서도 내가 소중한 암호화폐를 사용할 수 있게 되는 것은 바람직하지 않습니다. 다행히 동일 출처 정책으로 인해 웹사이트는 일반적으로 브라우저 내에서 서로의 데이터에 액세스할 수 없습니다. 하지만 악성 웹사이트가 이 정책을 우회하여 다른 웹사이트를 공격하려고 시도할 수 있으며, 동일 출처 정책을 시행하는 브라우저 코드에서 보안 버그가 발견되는 경우도 있습니다. Chrome팀은 이러한 버그를 최대한 빨리 수정하기 위해 노력하고 있습니다.

사이트 격리는 이러한 공격의 성공 가능성을 낮추기 위한 추가 방어선을 제공하는 Chrome의 보안 기능입니다. 서로 다른 웹사이트의 페이지가 항상 서로 다른 프로세스에 배치되며, 각 프로세스는 프로세스가 실행할 수 있는 작업을 제한하는 샌드박스에서 실행됩니다. 또한 프로세스가 다른 사이트로부터 특정 유형의 민감한 정보를 수신하지 못하도록 차단합니다. 따라서 사이트 격리를 사용하면 악성 웹사이트가 스펙터와 같은 추측성 부채널 공격을 사용하여 다른 사이트에서 데이터를 도용하기가 훨씬 더 어려워집니다. Chrome팀에서 추가 시정 조치를 완료하면 사이트 격리는 공격자의 페이지가 자체 프로세스에서 일부 규칙을 위반할 수 있는 경우에도 도움이 됩니다.

사이트 격리는 실질적으로 신뢰할 수 없는 웹사이트가 다른 웹사이트의 사용자 계정 정보를 액세스하거나 도용하기 어렵게 합니다. 또한 최근의 멜트다운 및 스펙터 부채널 공격과 같은 다양한 유형의 보안 버그로부터 추가 보호를 제공합니다.

사이트 격리에 대한 자세한 내용은 Google 보안 블로그의 도움말을 참고하세요.

교차 출처 읽기 차단

모든 크로스 사이트 페이지가 별도의 프로세스에 배치되더라도 페이지에서 이미지 및 자바스크립트와 같은 일부 크로스 사이트 하위 리소스를 합법적으로 요청할 수 있습니다. 악성 웹페이지는 <img> 요소를 사용하여 은행 잔액과 같은 민감한 정보가 포함된 JSON 파일을 로드할 수 있습니다.

<img src="https://your-bank.example/balance.json" />
<!-- Note: the attacker refused to add an `alt` attribute, for extra evil points. -->

사이트 격리가 없으면 JSON 파일의 콘텐츠가 렌더기 프로세스의 메모리에 전달되며, 이때 렌더기는 유효한 이미지 형식이 아니며 이미지를 렌더링하지 않음을 알게 됩니다. 하지만 공격자는 스펙터와 같은 취약점을 악용하여 메모리 청크를 잠재적으로 읽을 수 있습니다.

공격자는 <img>를 사용하는 대신 <script>를 사용하여 민감한 정보를 메모리에 커밋할 수도 있습니다.

<script src="https://your-bank.example/balance.json"></script>

교차 출처 읽기 차단(CORB)은 MIME 유형에 따라 balance.json의 콘텐츠가 렌더러 프로세스 메모리의 메모리에 들어가지 않도록 방지하는 새로운 보안 기능입니다.

CORB의 작동 방식을 자세히 살펴보겠습니다. 웹사이트는 서버에 두 가지 유형의 리소스를 요청할 수 있습니다.

  1. 데이터 리소스(예: HTML, XML, JSON 문서)
  2. 미디어 리소스(예: 이미지, JavaScript, CSS, 글꼴)

웹사이트는 자체 출처 또는 Access-Control-Allow-Origin: *와 같은 허용 CORS 헤더가 있는 다른 출처에서 데이터 리소스를 수신할 수 있습니다. 반면에 미디어 리소스는 허용 CORS 헤더가 없어도 모든 출처에서 포함될 수 있습니다.

CORB는 다음과 같은 경우 렌더러 프로세스가 교차 출처 데이터 리소스 (예: HTML, XML, JSON)를 수신하지 못하도록 합니다.

  • 리소스에 X-Content-Type-Options: nosniff 헤더가 있음
  • CORS가 리소스에 대한 액세스를 명시적으로 허용하지 않음

교차 출처 데이터 리소스에 X-Content-Type-Options: nosniff 헤더가 설정되지 않은 경우 CORB는 응답 본문을 스니핑하여 HTML, XML 또는 JSON인지 확인합니다. 이는 예를 들어 일부 웹 서버가 잘못 구성되어 이미지를 text/html로 제공하기 때문에 필요합니다.

CORB 정책에 의해 차단된 데이터 리소스는 프로세스에 비어 있는 것으로 표시되지만 요청은 계속 백그라운드에서 발생합니다. 그 결과, 악성 웹페이지는 크로스 사이트 데이터를 절도 프로세스에 끌어내기가 어렵습니다.

최적의 보안을 유지하고 CORB의 이점을 얻으려면 다음을 수행하는 것이 좋습니다.

  • 응답을 올바른 Content-Type 헤더로 표시하세요. 예를 들어 HTML 리소스는 text/html, JSON 리소스는 JSON MIME 유형, XML 리소스의 XML MIME 유형으로 제공해야 합니다.
  • X-Content-Type-Options: nosniff 헤더를 사용하여 스니핑을 선택 해제합니다. 이 헤더가 없으면 Chrome은 빠른 콘텐츠 분석을 실행하여 유형이 올바른지 확인하지만, 자바스크립트 파일과 같은 항목을 차단하지 않기 위해 응답을 허용하는 측의 실수이므로 직접 올바른 작업을 하는 것이 좋습니다.

자세한 내용은 웹 개발자를 위한 CORB 문서 또는 심층 CORB 설명을 참조하세요.

웹 개발자가 사이트 격리에 관심을 가져야 하는 이유는 무엇인가요?

대부분의 경우 사이트 격리는 웹 개발자에게 직접 노출되지 않는 보이지 않는 브라우저 기능입니다. 예를 들어 새로 학습해야 할 웹에 노출된 API는 없습니다. 일반적으로 웹페이지는 사이트 격리 사용 여부와 관계없이 실행할 때 차이를 구별할 수 없어야 합니다.

그러나 이 규칙에는 몇 가지 예외가 있습니다. 사이트 격리를 사용 설정하면 웹사이트에 영향을 미칠 수 있는 몇 가지 미묘한 결과가 수반됩니다. Google에서는 알려진 사이트 격리 문제 목록을 유지관리하고 있으며 가장 중요한 문제는 아래에 자세히 설명합니다.

전체 페이지 레이아웃이 더 이상 동기식이 아님

사이트 격리를 사용하면 페이지의 프레임이 여러 프로세스에 분산될 수 있으므로 전체 페이지 레이아웃이 더 이상 동기식이 아닐 수도 있습니다. 레이아웃 변경이 페이지의 모든 프레임에 즉시 전파된다고 가정하면 페이지에 영향을 미칠 수 있습니다.

예를 들어 social-widget.example에서 호스팅되는 소셜 위젯과 통신하는 fluffykittens.example라는 웹사이트를 가정해 보겠습니다.

<!-- https://fluffykittens.example/ -->
<iframe src="https://social-widget.example/" width="123"></iframe>
<script>
  const iframe = document.querySelector('iframe');
  iframe.width = 456;
  iframe.contentWindow.postMessage(
    // The message to send:
    'Meow!',
    // The target origin:
    'https://social-widget.example'
  );
</script>

우선 소셜 위젯의 <iframe> 너비는 123픽셀입니다. 하지만 그런 다음 FluffyKittens 페이지는 너비를 456 픽셀 (트리거 레이아웃)로 변경하고 소셜 위젯에 메시지를 전송합니다. 이 위젯에는 다음과 같은 코드가 포함됩니다.

<!-- https://social-widget.example/ -->
<script>
  self.onmessage = () => {
    console.log(document.documentElement.clientWidth);
  };
</script>

소셜 위젯이 postMessage API를 통해 메시지를 수신할 때마다 루트 <html> 요소의 너비를 로깅합니다.

어떤 너비 값이 기록되나요? Chrome에서 사이트 격리를 사용 설정하기 전에는 정답은 456였습니다. document.documentElement.clientWidth에 액세스하면 Chrome에서 사이트 격리를 사용 설정하기 전에는 동기식이던 레이아웃이 강제 적용됩니다. 하지만 사이트 격리를 사용 설정하면 이제 교차 출처 소셜 위젯 재레이아웃이 별도의 프로세스에서 비동기식으로 발생합니다. 따라서 답은 이제 123(즉, 이전 width 값)도 될 수 있습니다.

페이지가 교차 출처 <iframe>의 크기를 변경한 후 이 페이지로 postMessage를 전송하는 경우, 사이트 격리를 사용하면 수신 프레임이 메시지를 수신할 때 아직 새 크기를 알지 못할 수 있습니다. 일반적으로 레이아웃 변경사항이 페이지의 모든 프레임에 즉시 전파된다고 가정하면 페이지가 손상될 수 있습니다.

이 특정 예에서 더 강력한 솔루션은 상위 프레임에 width를 설정하고 resize 이벤트를 수신 대기하여 <iframe>의 변경사항을 감지합니다.

로드 취소 핸들러 시간이 초과될 수 있음

프레임이 이동하거나 닫히면 기존 문서와 그 안에 삽입된 모든 서브프레임 문서는 모두 unload 핸들러를 실행합니다. 새 탐색이 동일한 렌더기 프로세스에서 발생하는 경우 (예: 동일 출처 탐색의 경우) 기존 문서와 하위 프레임의 unload 핸들러는 새 탐색이 커밋될 수 있도록 허용하기 전에 임의의 긴 시간 동안 실행될 수 있습니다.

addEventListener('unload', () => {
  doSomethingThatMightTakeALongTime();
});

이 상황에서 모든 프레임의 unload 핸들러는 매우 안정적입니다.

하지만 사이트 격리가 없어도 일부 기본 프레임 탐색은 크로스 프로세스이므로 언로드 핸들러 동작에 영향을 미칩니다. 예를 들어 주소 표시줄에 URL을 입력하여 old.example에서 new.example로 이동하는 경우 new.example 탐색이 새 프로세스에서 발생합니다. old.example 및 하위 프레임의 로드 취소 핸들러는 new.example 페이지가 표시된 후 백그라운드의 old.example 프로세스에서 실행되며 특정 제한 시간 내에 완료되지 않으면 기존 로드 취소 핸들러가 종료됩니다. 로드 취소 핸들러가 제한 시간 전에 완료되지 않을 수 있으므로 로드 취소 동작의 안정성이 떨어집니다.

사이트 격리를 사용하면 모든 크로스 사이트 탐색이 크로스 프로세스로 전환되므로 다른 사이트의 문서가 서로 프로세스를 공유하지 않습니다. 따라서 위의 상황이 더 많은 경우에 적용되며 <iframe>의 언로드 핸들러는 위에서 설명한 백그라운드 및 시간 제한 동작을 취하는 경우가 많습니다.

사이트 격리에서 비롯된 또 다른 차이점은 로드 취소 핸들러의 새로운 병렬 순서입니다. 사이트 격리가 없으면 로드 취소 핸들러가 프레임에서 엄격한 하향식 순서로 실행됩니다. 그러나 사이트 격리를 사용하면 언로드 핸들러가 여러 프로세스에서 동시에 실행됩니다.

이는 사이트 격리를 사용 설정할 때 나타나는 근본적인 결과입니다. Chrome팀은 가능한 경우 일반적인 사용 사례를 대상으로 언로드 핸들러의 안정성을 개선하기 위해 노력하고 있습니다. 또한 서브프레임 언로드 핸들러가 아직 특정 기능을 활용할 수 없고 이를 해결하기 위해 노력하는 버그를 인지하고 있습니다.

로드 취소 핸들러의 중요한 사례는 세션 종료 핑을 전송하는 것입니다. 이 작업은 일반적으로 다음과 같이 진행됩니다.

addEventListener('pagehide', () => {
  const image = new Image();
  img.src = '/end-of-session';
});

이 변경사항을 고려할 때 더 강력한 접근 방식은 대신 navigator.sendBeacon를 사용하는 것입니다.

addEventListener('pagehide', () => {
  navigator.sendBeacon('/end-of-session');
});

요청을 더 세부적으로 제어해야 하는 경우 Fetch API의 keepalive 옵션을 사용할 수 있습니다.

addEventListener('pagehide', () => {
  fetch('/end-of-session', {keepalive: true});
});

결론

사이트 격리를 사용하면 각 사이트를 자체 프로세스로 분리하여 신뢰할 수 없는 웹사이트가 다른 웹사이트의 사용자 계정 정보를 액세스하거나 도용하기가 더 어려워집니다. 그 일환으로 CORB는 민감한 정보 리소스를 렌더기 프로세스에서 제외하려고 합니다. 위의 권장사항을 따르면 새로운 보안 기능을 최대한 활용할 수 있습니다.

이 문서의 초안을 읽고 의견을 제공해 주신 Alex Moshchuk, Charlie Reis, Jason Miller, Nasko Oskov, Philip Walton, Shubhie Panicker, Thomas Steiner에게 감사드립니다.