订阅以接收新文章的通知:

将 Turnstile 与 Cloudflare WAF 集成以质询 fetch 请求

2023-12-18

4 分钟阅读时间
这篇博文也有 EnglishFrançaisDeutsch日本語한국어PortuguêsEspañolPolski繁體中文版本。

两个月前,我们全面推出了 Cloudflare Turnstile,为世界各地的网站所有者提供了一种简单的方法来抵御机器人,而无需发布验证码。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

每个拥有 Free 或以上计划的 Cloudflare 用户都可以在托管模式下免费使用 Turnstile,请求数量不限。如果您是 Cloudflare 用户,希望提高关键 API 端点的安全性和用户体验,请立即前往我们的仪表板并创建具有 Pre-Clearance 功能的 Turnstile 小部件

我们保护整个企业网络,帮助客户高效构建互联网规模的应用程序,加速任何网站或互联网应用程序抵御 DDoS 攻击,防止黑客入侵,并能协助您实现 Zero Trust 的过程

从任何设备访问 1.1.1.1,以开始使用我们的免费应用程序,帮助您更快、更安全地访问互联网。要进一步了解我们帮助构建更美好互联网的使命,请从这里开始。如果您正在寻找新的职业方向,请查看我们的空缺职位
安全性CAPTCHABotsTurnstile产品新闻开发人员Micro-frontends

在 X 上关注

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

相关帖子

2024年10月24日 13: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....

2024年10月09日 13:00

Improving platform resilience at Cloudflare through automation

We realized that we need a way to automatically heal our platform from an operations perspective, and designed and built a workflow orchestration platform to provide these self-healing capabilities across our global network. We explore how this has helped us to reduce the impact on our customers due to operational issues, and the rich variety of similar problems it has empowered us to solve....