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

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

Lukas Weichselbaum
Lukas Weichselbaum

웹 리소스를 격리하는 것이 중요한 이유는 무엇인가요?

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

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

일반적으로 특정 웹 애플리케이션에서 노출되는 리소스는 다른 웹사이트가 아닌 애플리케이션 자체에서만 로드됩니다. 이러한 경우 메타데이터 가져오기 요청 헤더를 기반으로 리소스 격리 정책을 배포하면 별도의 작업 없이 교차 사이트 공격으로부터 애플리케이션을 보호할 수 있습니다.

브라우저 호환성

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

브라우저 지원

  • Chrome: 76
  • Edge: 79
  • Firefox: 90.
  • Safari: 16.4.

소스

배경

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

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

메타데이터 가져오기 소개

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

Same-Origin
자체 서버에서 제공하는 사이트 (동일 출처)에서 발생하는 요청은 계속 작동합니다. JavaScript에서 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

브라우저 지원

  • Chrome: 76
  • Edge: 79
  • Firefox: 90.
  • Safari: 16.4.

소스

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

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

Sec-Fetch-Mode

브라우저 지원

  • Chrome: 76
  • Edge: 79
  • Firefox: 90.
  • Safari: 16.4.

소스

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

Sec-Fetch-Dest

브라우저 지원

  • Chrome: 80.
  • Edge: 80.
  • Firefox: 90.
  • Safari: 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.examplesite.example/foo.json를 요청하는 동일 출처 요청은 항상 허용됨).
  • 하위 도메인에서 발생합니다.
  • 사용자가 사용자 에이전트와 상호작용하여 명시적으로 발생합니다 (예: 직접 탐색 또는 북마크 클릭 등).
if req['sec-fetch-site'] in ('same-origin', 'same-site', 'none'):
  return True  # Allow this request

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

다른 사이트에서 사이트를 계속 연결할 수 있도록 하려면 간단한 (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에서 메타데이터 가져오기 리소스 격리 정책을 출시한 경험에 비추어 볼 때 대부분의 애플리케이션은 기본적으로 이러한 정책과 호환되며 교차 사이트 트래픽을 허용하기 위해 엔드포인트를 제외할 필요가 거의 없습니다.

리소스 격리 정책 적용

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

추가 자료