訂閱以接收新文章的通知:

將 Turnstile 與 Cloudflare WAF 整合以驗證 fetch 請求

2023-12-18

閱讀時間:4 分鐘
本貼文還提供以下語言版本:EnglishFrançaisDeutsch日本語한국어PortuguêsEspañolPolski简体中文

兩個月前,我們全面推出了 Cloudflare Turnstile,為世界各地的網站擁有者提供了一種簡單的方法來抵禦傀儡程式,而無需發佈 CAPTCHA。Turnstile 允許任何網站擁有者透過簡單的程式碼片段在其網站上嵌入無障礙的 Cloudflare 驗證,從而輕鬆幫助確保只有人類流量才能通過。除了保護網站的前端之外,Turnstile 還使網路系統管理員能夠強化後台執行的瀏覽器啟動 (AJAX) API 呼叫。這些 API 通常由動態單頁 Web 應用程式使用,例如使用 React、Angular、Vue.js 建立的應用程式。

Integrating Turnstile with the Cloudflare WAF to challenge fetch requests

今天,我們很高興地宣佈,我們已將 Turnstile 與 Cloudflare Web 應用程式防火牆 (WAF) 整合。這意味著 Web 管理員可以將 Turnstile 程式碼片段新增到其網站,然後設定 Cloudflare WAF 來管理這些請求。這可以使用 WAF 規則完全自訂;例如,您可以允許經過 Turnstile 驗證的使用者與應用程式的所有 API 端點進行互動,而無需面臨更多驗證,或者您可以設定某些敏感端點(例如登入)以始終發出驗證。

挑戰 Cloudflare WAF 中的 fetch 請求

受 Cloudflare 的 WAF 保護的數百萬個網站利用我們的 JS 驗證、受管驗證和互動式驗證來阻止傀儡程式,同時允許人類通過。對於每一個驗證,Cloudflare 都會攔截匹配的請求並使用瀏覽器呈現的 HTML 頁面進行回應,使用者在其中完成基本任務以證明他們是人類。當使用者成功完成驗證時,他們會收到 cf_clearance cookie,該 cookie 告訴 Cloudflare 使用者已成功通過驗證、驗證類型以及完成時間。clearance cookie 不能在使用者之間共用,並且僅在 Cloudflare 客戶在其安全設定儀表板中設定的時間內有效。

此過程運作良好,除非瀏覽器收到 fetch 請求的驗證並且瀏覽器之前未通過驗證。在 fetch 請求或 XML HTTP 請求 (XHR) 中,瀏覽器期望返回簡單文字(JSON 或 XML 格式),並且無法呈現執行驗證所需的 HTML。

舉個例子,我們假設一家披薩店老闆在 React 中構建了一個線上訂購表單,其中包含一個支付頁面,該頁面將資料提交到處理支付的 API 端點。當使用者檢視 Web 表單以新增其信用卡詳細資訊時,他們可以通過受管驗證,但當使用者透過發出 fetch 請求提交其信用卡詳細資訊時,瀏覽器將不會執行運行驗證所需的程式碼。披薩店老闆處理可疑(但可能合法)請求的唯一選擇是封鎖這些請求,這存在誤判的風險,可能導致餐廳失去銷售。

這就是 Turnstile 可以提供幫助的地方。Turnstile 允許網際網路上的任何人在其網站上的任何位置嵌入 Cloudflare 驗證。在今天之前,Turnstile 的輸出只是一次性使用的權杖。為了使客戶能夠對這些 fetch 請求發出驗證,Turnstile 現在可以為其嵌入的網域發出一個 clearance cookie。客戶可以在 fetch 請求之前在 HTML 頁面中發出驗證,_預先允許_訪客與支付 API 進行互動。

Turnstile Pre-Clearance 模式

回到我們的披薩店範例,使用 Pre-Clearance 將 Turnstile 與 Cloudflare WAF 整合有三大優勢:

  1. 改善使用者體驗:當訪客輸入付款詳細資訊時,Turnstile 的內嵌驗證可在背景中執行。

  2. 在邊緣封鎖更多請求:由於 Turnstile 現在為其嵌入的網域發出了一個 clearance cookie,因此披薩店老闆可以使用自訂規則為支付 API 的每個請求發出受管驗證。這可確保嘗試直接針對支付 API 的自動攻擊在到達 API 之前就被 Cloudflare 阻止。

  3. (選用)保護動作和使用者的安全:無需變更後端程式碼即可獲得 Pre-Clearance 的好處。然而,進一步的 Turnstile 整合將提高整合 API 的安全性。披薩店老闆可以調整其付款形式以驗證收到的 Turnstile 權杖,確保每次付款嘗試均由 Turnstile 單獨驗證,以保護其付款端點免受工作階段劫持。

啟用 Pre-Clearance 的 Turnstile 小工具仍會發出 Turnstile 權杖,這讓客戶可以根據端點的重要性,靈活地決定是需要對每個請求進行安全檢查,還是每個工作階段僅進行一次安全檢查。Turnstile 小工具發出的 clearance cookie 會自動套用至 Turnstile 小工具內嵌的 Cloudflare 區域,無需進行設定。權杖的有效許可時間仍受區域特定「驗證成功後有效期間」時間控制。

實作具 Pre-Clearance 功能的 Turnstile

讓我們透過一個基本的實作來具體說明這一點。在開始之前,我們設定了一個簡單的示範應用程式,在 /your-api 端點上模擬前端與後端通訊。

為此,我們編寫了以下程式碼:

我們建立了一個按鈕。點擊後,Cloudflare 會向 /your-api 端點發出 fetch() 請求,並在回應容器中顯示結果。

<!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>

現在,我們假設我們設定了一個 Cloudflare WAF 規則,透過受管驗證來保護 /your-api 端點。

由於這條規則,我們剛剛編寫的應用程式將因前面描述的原因而失敗(瀏覽器期望 JSON 回應,但收到 HTML 形式的驗證頁面)。

如果我們檢查「網路」索引標簽,我們可以看到對 /your-api 的請求已得到 403 回應。

經檢查,Cf-Mitiated 標頭顯示該回應受到 Cloudflare 防火牆的驗證,因為訪客之前尚未解決驗證。

為了在我們的應用程式中解決這個問題,我們在 Pre-Clearance 模式下為我們想要使用的 Turnstile 網站金鑰設定了一個 Turnstile 小工具。

在我們的應用程式中,一旦收到 Cf-Mitiated 回應,我們就會覆寫 fetch() 函數來叫用 Turnstile。

上面的程式碼片段中發生了很多事情:首先,我們建立了一個隱藏的覆蓋元素,並覆寫了瀏覽器的 fetch() 函數。對 fetch() 函數進行了修改,以反省「驗證」的 Cf-Mitigated 標頭。如果發出驗證,初始結果將是不成功的;取而代之的是,我們的 Web 應用程式中將出現 Turnstile 覆蓋層(已啟用 Pre-Clearance)。完成 Turnstile 驗證後,我們將在 Turnstile 獲得 cf_clearance cookie 以通過 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 客戶均可使用 Pre-Clearance

每個擁有免費或以上方案的 Cloudflare 使用者都可以在託管模式下免費使用 Turnstile,請求數量不限。如果您是 Cloudflare 使用者,希望提高關鍵 API 端點的安全性和使用者體驗,請立即前往我們的儀表板並建立具有 Pre-Clearance 功能的 Turnstile 小工具

我們保護整個企業網路,協助客戶有效地建置網際網路規模的應用程式,加速任何網站或網際網路應用程式抵禦 DDoS 攻擊,阻止駭客入侵,並且可以協助您實現 Zero Trust

從任何裝置造訪 1.1.1.1,即可開始使用我們的免費應用程式,讓您的網際網路更快速、更安全。

若要進一步瞭解我們協助打造更好的網際網路的使命,請從這裡開始。如果您正在尋找新的職業方向,請查看我們的職缺
安全性CAPTCHABotsTurnstile產品新聞DevelopersMicro-frontends

在 X 上進行關注

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

相關貼文

2024年10月24日 下午1:00

Durable Objects aren't just durable, they're fast: a 10x speedup for Cloudflare Queues

Learn how we built Cloudflare Queues using our own Developer Platform and how it evolved to a geographically-distributed, horizontally-scalable architecture built on Durable Objects. Our new architecture supports over 10x more throughput and over 3x lower latency compared to the previous version....