구독해서 새 게시물에 대한 알림을 받으세요.

Turnstile을 Cloudflare WAF와 통합하여 가져오기 요청에 인증 질문하기

2023-12-18

4분 읽기
이 게시물은 English, 繁體中文, Français, Deutsch, 日本語, Português, Español, Polski简体中文로도 이용할 수 있습니다.

두 달 전, 우리는 Cloudflare Turnstile을 일반에 공개하여 모든 웹 사이트 소유자가 캡차를 발행하지 않고도 봇을 쉽게 차단할 수 있는 방법을 제공했습니다. Turnstile을 사용하면 모든 웹 사이트 소유자가 간단한 코드 스니펫을 사용하여 웹 사이트에 Cloudflare 인증 질문을 삽입할 수 있으므로 인간의 트래픽만 통과할 수 있도록 쉽게 지원할 수 있습니다. Turnstile은 웹 사이트의 프런트엔드를 보호하는 것 외에도 웹 관리자가 내부에서 실행되는 브라우저 시작(AJAX) API 호출을 강화할 수 있도록 지원합니다. 이러한 API는 일반적으로 React, Angular, Vue.js로 만든 앱과 같은 동적 단일 페이지 웹 앱에서 사용됩니다.

Integrating Turnstile with the Cloudflare WAF to challenge fetch requests

오늘, Turnstile을 Cloudflare 웹 앱 방화벽(WAF)과 통합했다는 소식을 알려드리게 되어 기쁩니다. 이는 웹 관리자가 웹 사이트에 Turnstile 코드 스니펫을 추가한 다음 이러한 요청을 관리하도록 Cloudflare WAF를 구성할 수 있다는 것을 의미합니다. 이는 WAF 규칙을 사용하여 완전히 사용자 정의할 수 있습니다. 예를 들어, Turnstile로 인증된 사용자가 추가 인증 질문 없이 앱의 모든 API 엔드포인트와 상호 작용하도록 허용하거나 로그인과 같은 민감한 특정 엔드포인트가 항상 인증 질문을 발행하도록 구성할 수 있습니다.

Cloudflare WAF에서 가져오기 요청 인증 질문하기

Cloudflare의 WAF로 보호되는 수백만 개의 웹 사이트에서는 봇은 차단하는 반면 인간은 통과할 수 있도록 Cloudflare의 JS 인증 질문, 관리형 인증 질문, 대화형 인증 질문을 활용하고 있습니다. 이러한 각 인증 질문에 대해 Cloudflare는 일치하는 요청을 가로채고 브라우저에서 렌더링된 HTML 페이지로 응답하며, 이 경우 사용자는 기본 작업을 완료하여 자신이 인간임을 증명합니다. 사용자가 인증 질문을 성공적으로 완료하면, 사용자는 cf_clearance 쿠키를 받게 되는데, 이는 Cloudflare에 사용자가 인증 질문을 성공적으로 통과했음을 알려주고, 인증 질문 유형, 완료 시점을 알려줍니다. 허가 쿠키는 사용자 간에 공유할 수 없으며, Cloudflare 고객이 보안 설정 대시보드에서 설정한 시간 동안만 유효합니다.

이 프로세스는 브라우저가 가져오기 요청에 대한 인증 질문을 받고 이전에 인증 질문을 통과한 적이 없는 경우를 제외하고는 정상적으로 작동합니다. 가져오기 요청 또는 XML HTTP 요청(XHR)에서 브라우저에서는 단순한 텍스트(JSON 또는 XML 형식)가 반환될 것으로 예상하며 인증 질문을 실행하는 데 필요한 HTML을 렌더링할 수 없습니다.

예를 들어, 결제를 처리하는 API 엔드포인트에 데이터를 제출하는 결제 페이지가 있는 React로 온라인 주문 양식을 구축한 피자 가게 주인이 있다고 가정해 보겠습니다. 사용자가 신용 카드 세부 정보를 추가하기 위해 웹 양식을 볼 때 관리형 인증 질문을 통과할 수 있지만, 사용자가 가져오기 요청을 통해 신용 카드 세부 정보를 제출하면 브라우저에서는 인증 질문이 실행되는 데 필요한 코드가 실행되지 않습니다. 피자 가게 주인이 의심스러운(그러나 잠재적으로 합법적인) 요청을 처리할 수 있는 유일한 옵션은 이를 차단하는 것인데, 이는 긍정 오류의 위험이 있으므로 이 가게의 매출 손실이 초래될 수 있습니다.

이때 Turnstile이 도움이 될 수 있습니다. Turnstile을 사용하면 인터넷 사용자라면 누구나 자신의 웹 사이트 어디에서든 Cloudflare 인증 질문을 포함할 수 있습니다. 이전에는 Turnstile의 출력은 일회성 토큰에 불과했습니다. 이제 고객이 이러한 가져오기 요청에 대해 인증 질문을 발행할 수 있도록, Turnstile에서는 포함된 도메인에 대한 허가 쿠키를 발행할 수 있습니다. 고객은 가져오기 요청 전에 HTML 페이지 내에서 인증 질문을 발급하여 방문자가 결제 API와 상호 작용할 수 있도록 _사전 허가_할 수 있습니다.

Turnstile 사전 허가 모드

피자 가게의 예로 돌아가서, 사전 허가를 사용하여 Turnstile을 Cloudflare WAF와 통합하면 다음과 같은 세 가지 큰 이점이 있습니다.

  1. 사용자 경험 개선: Turnstile의 임베디드 인증 질문이 방문자가 결제 세부 정보를 입력하는 동안 백그라운드에서 실행될 수 있습니다.

  2. 에지에서 더 많은 요청을 차단: 이제 Turnstile이 포함된 도메인에 대해 허가 쿠키를 발급하므로 피자 가게 주인은 사용자 지정 규칙을 사용하여 결제 API에 대한 모든 요청에 대해 관리형 인증 질문을 발급할 수 있습니다. 이렇게 하면 결제 API를 직접 겨냥하려는 자동화된 공격이 API에 도달하기 전에 Cloudflare에서 이를 차단할 수 있습니다.

  3. (선택 사항) 작업 및 사용자 보안: 사전 허가 혜택을 받기 위해 백엔드 코드를 변경할 필요는 없습니다. 하지만 Turnstile 통합을 추가하면 통합 API의 보안이 강화됩니다. 피자 가게 주인은 결제 양식을 조정하여 수신된 Turnstile 토큰의 유효성을 검사하고, 모든 결제 시도가 Turnstile에서 개별적으로 유효성 검사를 거치도록 하여 세션 하이재킹으로부터 결제 엔드포인트를 보호할 수 있습니다.

사전 허가가 활성화된 Turnstile 위젯에서는 여전히 Turnstile 토큰이 발급되므로 고객은 엔드포인트가 요청할 때마다 보안 확인이 필요할 만큼 중요한지, 아니면 세션에 한 번만 보안 확인이 필요한지 유연하게 결정할 수 있습니다. Turnstile 위젯에서 발급된 허가 쿠키는 별도의 구성 없이 Turnstile 위젯이 임베드 된 Cloudflare 영역에 자동으로 적용됩니다. 토큰이 유효하도록 허가된 시간은 여전히 영역별 '인증 유효 기간'에 의해 제어됩니다.

사전 허가를 통해 Turnstile 구현하기

기본적인 구현을 통해 이를 구체적으로 살펴보겠습니다. 시작하기 전에 /your-api 엔드포인트에서 백엔드와 대화하는 프런트엔드를 에뮬레이션하는 간단한 데모 앱을 설정해 보겠습니다.

이를 위해 다음 코드를 사용합니다.

버튼을 하나 만듭니다. 클릭하면 Cloudflare에서 fetch() 요청을 /your-api 엔드포인트에 대하여 해서 응답 컨테이너에 결과를 표시합니다.

<!DOCTYPE html>
<html lang="en">
<head>
   <title>Turnstile Pre-Clearance Demo </title>
</head>
<body>
  <main class="pre-clearance-demo">
    <h2>Pre-clearance Demo</h2>
    <button id="fetchBtn">Fetch Data</button>
    <div id="response"></div>
</main>

<script>
  const button = document.getElementById('fetchBtn');
  const responseDiv = document.getElementById('response');
  button.addEventListener('click', async () => {
  try {
    let result = await fetch('/your-api');
    if (result.ok) {
      let data = await result.json();
      responseDiv.textContent = JSON.stringify(data);
    } else {
      responseDiv.textContent = 'Error fetching data';
    }
  } catch (error) {
    responseDiv.textContent = 'Network error';
  }
});
</script>

이제 관리형 인증 질문으로/your-api 엔드포인트를 보호하는 Cloudflare WAF 규칙이 설정되어 있다고 가정해 보겠습니다.

이 규칙으로 인해 방금 작성한 앱은 앞서 설명한 이유로 실패하게 됩니다(브라우저에서는 JSON 응답을 기대하지만, 대신 인증 질문 페이지를 HTML로 받음).

네트워크 탭을 살펴보면/your-api에 대한 요청에 대하여 403 응답을 받은 것을 확인할 수 있습니다.

검사 결과, 방문자가 이전에 인증 질문을 해결한 적이 없으므로 Cf-Mitigated 헤더는 Cloudflare의 방화벽에 의해 응답이 거부되었음을 보여줍니다.

Cloudflare의 앱에서 이 문제를 해결하기 위해 사용하려는 Turnstile 사이트 키에 대해 사전 허가 모드에서 Turnstile 위젯을 설정했습니다.

Cloudflare의 앱에서는 fetch() 함수를 재정의하여 Cf-Mitigated 응답이 수신되면 Turnstile을 호출합니다.

위의 스니펫에는 많은 일이 벌어지고 있습니다. 먼저 숨겨진 오버레이 요소를 생성하고 브라우저의 fetch() 함수를 재정의합니다. fetch() 함수는 '인증 질문'에 대한 Cf-Mitigated 헤더를 자체적으로 살펴보도록 변경됩니다. 인증 질문이 발급되면 초기 결과는 실패로 표시되며, 대신 Turnstile 오버레이(사전 허가가 활성화된 상태)가 웹 앱에 표시됩니다.Turnstile 인증 질문이 완료되면 Turnstile이 cf_clearance 쿠키를 획득하여 Cloudflare WAF를 통과한 후 이전 요청을 다시 시도합니다.

<script>
turnstileLoad = function () {
  // Save a reference to the original fetch function
  const originalFetch = window.fetch;

  // A simple modal to contain Cloudflare Turnstile
  const overlay = document.createElement('div');
  overlay.style.position = 'fixed';
  overlay.style.top = '0';
  overlay.style.left = '0';
  overlay.style.right = '0';
  overlay.style.bottom = '0';
  overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
  overlay.style.border = '1px solid grey';
  overlay.style.zIndex = '10000';
  overlay.style.display = 'none';
  overlay.innerHTML =       '<p style="color: white; text-align: center; margin-top: 50vh;">One more step before you proceed...</p><div style=”display: flex; flex-wrap: nowrap; align-items: center; justify-content: center;” id="turnstile_widget"></div>';
  document.body.appendChild(overlay);

  // Override the native fetch function
  window.fetch = async function (...args) {
      let response = await originalFetch(...args);

      // If the original request was challenged...
      if (response.headers.has('cf-mitigated') && response.headers.get('cf-mitigated') === 'challenge') {
          // The request has been challenged...
          overlay.style.display = 'block';

          await new Promise((resolve, reject) => {
              turnstile.render('#turnstile_widget', {
                  'sitekey': ‘YOUR_TURNSTILE_SITEKEY',
                  'error-callback': function (e) {
                      overlay.style.display = 'none';
                      reject(e);
                  },
                  'callback': function (token, preClearanceObtained) {
                      if (preClearanceObtained) {
                          // The visitor successfully solved the challenge on the page. 
                          overlay.style.display = 'none';
                          resolve();
                      } else {
                          reject(new Error('Unable to obtain pre-clearance'));
                      }
                  },
              });
          });

          // Replay the original fetch request, this time it will have the cf_clearance Cookie
          response = await originalFetch(...args);
      }
      return response;
  };
};
</script>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?onload=turnstileLoad" async defer></script>

Turnstile 위젯을 해결하면 오버레이가 사라지고 요청된 API 결과가 성공적으로 표시됩니다.

사전 허가는 모든 Cloudflare 고객에게 제공됩니다

무료 요금제 이상을 사용하는 모든 Cloudflare 사용자는 관리형 모드에서 요청 횟수 제한 없이 무료로 Turnstile을 사용할 수 있습니다. 중요한 API 엔드포인트의 보안과 사용자 경험을 개선하려는 Cloudflare 사용자라면, 지금 바로 대시보드로 이동하여 사전 허가를 받아 Turnstile 위젯을 생성하세요.

Cloudflare에서는 전체 기업 네트워크를 보호하고, 고객이 인터넷 규모의 애플리케이션을 효과적으로 구축하도록 지원하며, 웹 사이트와 인터넷 애플리케이션을 가속화하고, DDoS 공격을 막으며, 해커를 막고, Zero Trust로 향하는 고객의 여정을 지원합니다.

어떤 장치로든 1.1.1.1에 방문해 인터넷을 더 빠르고 안전하게 만들어 주는 Cloudflare의 무료 앱을 사용해 보세요.

더 나은 인터넷을 만들기 위한 Cloudflare의 사명을 자세히 알아보려면 여기에서 시작하세요. 새로운 커리어 경로를 찾고 있다면 채용 공고를 확인해 보세요.
보안CAPTCHABots (KO)Turnstile제품 뉴스개발자Micro-frontends (KO)

X에서 팔로우하기

Adam Martinetti|@adamemcf
Benedikt Wolters|@worengawins
Miguel de Moura|@miguel_demoura
Cloudflare|@cloudflare

관련 게시물