메타데이터 가져오기로 웹 공격으로부터 리소스 보호

CSRF, XSSI, 교차 출처 정보 유출을 방지합니다.

Lukas Weichselbaum
Lukas Weichselbaum

웹 리소스 격리에 신경 써야 하는 이유는 무엇인가요?

많은 웹 애플리케이션은 크로스 사이트 요청 위조 (CSRF), 교차 사이트 스크립트 포함 (XSSI), 타이밍 공격, 교차 출처 정보 유출 또는 추측 실행 측 채널 (스펙터) 공격과 같은 교차 출처 공격에 취약합니다.

메타데이터 가져오기 요청 헤더를 사용하면 강력한 심층 방어 메커니즘(리소스 격리 정책)을 배포하여 일반적인 교차 출처 공격으로부터 애플리케이션을 보호할 수 있습니다.

특정 웹 애플리케이션에 의해 노출된 리소스는 다른 웹사이트가 아닌 애플리케이션 자체에서만 로드되는 것이 일반적입니다. 이 경우 메타데이터 가져오기 요청 헤더를 기반으로 리소스 격리 정책을 배포하는 작업은 별다른 노력이 필요하지 않으며 동시에 크로스 사이트 공격으로부터 애플리케이션을 보호할 수 있습니다.

브라우저 호환성

메타데이터 가져오기 요청 헤더는 모든 최신 브라우저 엔진에서 지원됩니다.

브라우저 지원

  • 76
  • 79
  • 90
  • 16.4

소스

배경

웹이 기본적으로 개방되어 있고 애플리케이션 서버가 외부 애플리케이션에서 발생하는 통신으로부터 스스로를 쉽게 보호할 수 없기 때문에 많은 교차 사이트 공격이 가능합니다. 일반적인 교차 출처 공격은 공격자가 자신이 제어하는 사이트로 사용자를 유인한 다음 사용자가 로그인한 서버로 양식을 제출하는 크로스 사이트 요청 위조 (CSRF)입니다. 요청이 다른 도메인 (크로스 사이트)에서 시작되었는지 서버에서 알 수 없고 브라우저가 크로스 사이트 요청에 쿠키를 자동으로 첨부하므로, 서버는 사용자를 대신하여 공격자가 요청한 작업을 실행합니다.

교차 사이트 스크립트 포함 (XSSI) 또는 교차 출처 정보 유출과 같은 다른 교차 사이트 공격은 본질적으로 CSRF와 유사하며, 피해자 애플리케이션으로부터 공격자가 제어하는 문서로 리소스를 로드하고 피해자 애플리케이션에 대한 정보를 유출하는 것에 의존합니다. 애플리케이션은 신뢰할 수 있는 요청과 신뢰할 수 없는 요청을 쉽게 구분할 수 없으므로 악성 크로스 사이트 트래픽을 삭제할 수 없습니다.

메타데이터 가져오기 소개

메타데이터 가져오기 요청 헤더는 서버가 교차 출처 공격으로부터 스스로를 보호할 수 있도록 설계된 새로운 웹 플랫폼 보안 기능입니다. Sec-Fetch-* 헤더 집합에 HTTP 요청의 컨텍스트에 관한 정보를 제공하면 응답 서버가 요청을 처리하기 전에 보안 정책을 적용할 수 있습니다. 이를 통해 개발자는 요청이 이루어진 방식과 사용될 맥락에 따라 요청을 수락할지 또는 거부할지 결정할 수 있으므로, 자신의 애플리케이션이 제기한 합법적인 요청에만 응답할 수 있습니다.

동일 출처
자체 서버 (동일 출처)에서 제공하는 사이트에서 시작된 요청은 계속 작동합니다. 자바스크립트로 https://site.example/foo.json 리소스에 대한 https://site.example의 가져오기 요청을 실행하면 브라우저에서 HTTP 요청 헤더 'Sec Fetch-Site: same-origin'을 전송합니다.
크로스 사이트
Sec-Fetch-* 헤더에서 제공하는 HTTP 요청에 추가 컨텍스트가 있기 때문에 서버에서 악의적인 크로스 사이트 요청을 거부할 수 있습니다. img 요소의 src 속성을 'https://site.example/foo.json'으로 설정한 https://evil.example의 이미지는 브라우저에서 HTTP 요청 헤더 'Sec-Fetch-Site: cross-site'를 전송합니다.

Sec-Fetch-Site

브라우저 지원

  • 76
  • 79
  • 90
  • 16.4

소스

Sec-Fetch-Site는 요청을 보낸 사이트를 서버에 알려줍니다. 브라우저는 이 값을 다음 중 하나로 설정합니다.

  • same-origin: 자체 애플리케이션에서 요청한 경우 (예: site.example)
  • same-site: 사이트의 하위 도메인 (예: bar.site.example)에서 요청한 경우
  • none: 사용자와 사용자 에이전트의 상호작용 (예: 북마크 클릭)으로 인해 명시적으로 요청이 발생한 경우
  • cross-site: 요청을 다른 웹사이트에서 보낸 경우 (예: evil.example)

Sec-Fetch-Mode

브라우저 지원

  • 76
  • 79
  • 90
  • 16.4

소스

Sec-Fetch-Mode는 요청의 모드를 나타냅니다. 이는 대략적으로 요청 유형과 일치하며 이를 통해 리소스 로드와 탐색 요청을 구분할 수 있습니다. 예를 들어 navigate의 대상은 최상위 탐색 요청을 나타내고 no-cors는 이미지 로드와 같은 리소스 요청을 나타냅니다.

Sec-Fetch-Dest

브라우저 지원

  • 80
  • 80
  • 90
  • 16.4

소스

Sec-Fetch-Dest는 요청의 대상을 노출합니다 (예: script 또는 img 태그로 인해 브라우저에서 리소스를 요청한 경우).

메타데이터 가져오기를 사용하여 교차 출처 공격으로부터 보호하는 방법

이러한 요청 헤더가 제공하는 추가 정보는 매우 간단하지만, 추가적인 컨텍스트를 사용하면 단 몇 줄의 코드만으로 서버 측에서 강력한 보안 로직(리소스 격리 정책이라고도 함)을 빌드할 수 있습니다.

리소스 격리 정책 구현

리소스 격리 정책을 사용하면 외부 웹사이트에서 리소스를 요청할 수 없습니다. 이러한 트래픽을 차단하면 CSRF, XSSI, 타이밍 공격, 교차 출처 정보 유출과 같은 일반적인 교차 사이트 웹 취약점이 완화됩니다. 이 정책은 애플리케이션의 모든 엔드포인트에 사용 설정할 수 있으며 자체 애플리케이션에서 발생하는 모든 리소스 요청은 물론 HTTP GET 요청을 통한 직접 탐색도 허용합니다. 크로스 사이트 컨텍스트에 로드되어야 하는 엔드포인트 (예: CORS를 사용하여 로드된 엔드포인트)는 이 로직에서 선택 해제할 수 있습니다.

1단계: 메타데이터 가져오기를 전송하지 않는 브라우저의 요청 허용

모든 브라우저가 메타데이터 가져오기를 지원하는 것은 아니므로 sec-fetch-site이 있는지 확인하여 Sec-Fetch-* 헤더를 설정하지 않는 요청을 허용해야 합니다.

if not req['sec-fetch-site']:
  return True  # Allow this request

2단계: 동일한 사이트 및 브라우저에서 시작된 요청 허용하기

교차 출처 컨텍스트에서 발생하지 않은 요청 (예: evil.example)은 모두 허용됩니다. 특히 다음과 같은 요청이 여기에 해당합니다.

  • 자체 애플리케이션에서 발생합니다 (예: site.example 요청 site.example/foo.json가 항상 허용되는 동일 출처 요청).
  • 하위 도메인에서 발생합니다.
  • 사용자와 사용자 에이전트의 상호작용 (예: 직접 탐색 또는 북마크 클릭 등)으로 인해 명시적으로 발생합니다.
if req['sec-fetch-site'] in ('same-origin', 'same-site', 'none'):
  return True  # Allow this request

3단계: 간단한 최상위 탐색 및 iframe 허용

내 사이트가 다른 사이트에서 계속 연결될 수 있도록 하려면 간단한 (HTTP GET) 최상위 탐색을 허용해야 합니다.

if req['sec-fetch-mode'] == 'navigate' and req.method == 'GET'
  # <object> and <embed> send navigation requests, which we disallow.
  and req['sec-fetch-dest'] not in ('object', 'embed'):
    return True  # Allow this request

4단계: 크로스 사이트 트래픽을 제공할 엔드포인트 선택 해제 (선택사항)

경우에 따라 애플리케이션이 크로스 사이트 로드 용도의 리소스를 제공할 수 있습니다. 이러한 리소스는 경로별 또는 엔드포인트별로 제외해야 합니다. 이러한 엔드포인트의 예는 다음과 같습니다.

  • 교차 출처로 액세스해야 하는 엔드포인트: 애플리케이션에서 CORS가 사용 설정된 엔드포인트를 제공하는 경우 이러한 엔드포인트에 대한 교차 사이트 요청이 계속 가능하도록 리소스 격리를 명시적으로 선택 해제해야 합니다.
  • 공개 리소스 (예: 이미지, 스타일 등): 다른 사이트에서 교차 출처에서 로드할 수 있어야 하는 공개 및 인증되지 않은 리소스도 제외할 수 있습니다.
if req.path in ('/my_CORS_endpoint', '/favicon.png'):
  return True

5단계: 탐색용이 아닌 크로스 사이트인 다른 모든 요청 거부

다른 모든 크로스 사이트 요청은 이 리소스 격리 정책에 의해 거부되므로 일반적인 교차 사이트 공격으로부터 애플리케이션을 보호합니다.

예: 다음 코드는 서버에서 또는 미들웨어로 강력한 리소스 격리 정책을 완전하게 구현하여 간단한 탐색 요청을 허용하면서 악의적일 수 있는 크로스 사이트 리소스 요청을 거부하는 방법을 보여줍니다.

# Reject cross-origin requests to protect from CSRF, XSSI, and other bugs
def allow_request(req):
  # Allow requests from browsers which don't send Fetch Metadata
  if not req['sec-fetch-site']:
    return True

  # Allow same-site and browser-initiated requests
  if req['sec-fetch-site'] in ('same-origin', 'same-site', 'none'):
    return True

  # Allow simple top-level navigations except <object> and <embed>
  if req['sec-fetch-mode'] == 'navigate' and req.method == 'GET'
    and req['sec-fetch-dest'] not in ('object', 'embed'):
      return True

  # [OPTIONAL] Exempt paths/endpoints meant to be served cross-origin.
  if req.path in ('/my_CORS_endpoint', '/favicon.png'):
    return True

  # Reject all other requests that are cross-site and not navigational
  return False

리소스 격리 정책 배포

  1. 위의 코드 스니펫과 같은 모듈을 설치하여 사이트 작동 방식을 로깅 및 모니터링하고 제한사항이 합법적인 트래픽에 영향을 미치지 않는지 확인합니다.
  2. 합법적인 교차 출처 엔드포인트를 제외하여 잠재적 위반을 해결합니다.
  3. 규정을 준수하지 않는 요청을 삭제하여 정책을 시행합니다.

정책 위반 확인 및 수정

먼저 서버 측 코드의 보고 모드에서 정책을 사용 설정하여 부작용이 없는 방식으로 정책을 테스트하는 것이 좋습니다. 또는 미들웨어 또는 프로덕션 트래픽에 적용되었을 때 정책으로 인해 발생할 수 있는 위반을 기록하는 리버스 프록시에서 이 로직을 구현할 수 있습니다.

Google에서 메타데이터 가져오기 리소스 격리 정책을 배포해 본 결과, 대부분의 애플리케이션은 기본적으로 이러한 정책과 호환되며 교차 사이트 트래픽을 허용하기 위해 엔드포인트를 제외할 필요가 거의 없습니다.

리소스 격리 정책 적용

정책이 합법적인 프로덕션 트래픽에 영향을 미치지 않음을 확인하고 나면 다른 사이트에서 리소스를 요청할 수 없도록 보장하고 크로스 사이트 공격으로부터 사용자를 보호할 수 있도록 제한을 적용할 수 있습니다.

추가 자료