黑客把入侵外包给 AI 代理:NorForge 从裸奔在公网的控制台里看到了全过程
10 月 19 日,挪威安全公司 NorForge 发了一篇 16 分钟读完的调查报告《Inside Cairn》,同一天路透社拿到了独家。故事的主线是:一个攻击者把入侵外包给了 AI 代理——对 1200 多个站点发起尝试,100 多家被确认攻破,成功个案平均耗时不到 23 小时,整个活动的 token 花费估算约 5000 美元。
但真正让这份报告值得读的,不是「AI 能打站」这个结论——那件事已经被说过很多遍了。是取证入口:研究者并不是靠流量分析或蜜罐抓到这条线的,而是发现这名攻击者把自己的 AI 代理控制台裸奔在公网 8000 端口上,面板连鉴权都没有,里面装着他所有客户被探测出来的凭据(官方定义合规的公司/客户一般叫目标)。安全公司连进去看到的东西,比攻击者自己想看到的还多。
先把数字账摆清楚
先把可核的与不可核的分开。
NorForge 报告里明确写下的(一手口径):
- 三个活动集群,合计 25 个暴露在公网的控制面板(AIA0001 十四个、AIA0002 十个、AIA0003 一个),端口统一是 8000。
- 主集群 AIA0001 关联超过 1,100 个攻击项目,语言为中文,判定置信度「高」,被攻击站点集中在美国、加拿大、日本、澳大利亚四国的真实电商站(流量 20 万–150 万)。
- 整体成功率约 10%;到達目标(成功个案)平均耗时略低于 23 小时;成功的项目平均产出约 140 条 fact。
- 用 Trufflehog 扫描面板数据,产出「数以百计」的各类凭据,其中11% 被验证仍然有效。
- 平均单个任务成本估算约 5 美元(含失败与提前终止的项目);有项目超过 1000 条 intent/fact,这类项目成本可能超过 100 美元。报告自己写明「不确定性很大,误差可能明显」。
路透社与后续报道补充的(第三方口径,未经官方确认):
- 尝试站点数「超过 1,200」这个数字出现于后续报道;被攻破的公司被点名到 John Deere、SharkNinja、Cummins 等——这三家出现在第三方报道的标题里,NorForge 报告本身对受害者是脱敏的。
- NorForge 的 Snorre Fagerland 对路透社给出的估算是总计约 5,000 美元 token、平均每次成功入侵约 50 美元。
- 所有任务跑在一个名为
claudecode_deepseek-v4.1-flash的 worker 下,NorForge 据此推测是 Claude Code 这类 harness 挂上了 DeepSeek 的低价模型。这条细节 Anthropic 与 DeepSeek 都没确认过,写进文章时要按「研究者推测」处理。 - 发现时间线:Fagerland 大约在 10 月 2 日开始发现暴露的 Cairn 面板,到那时已认出约二十来个被用于恶意目的实例。
一条有意思的旁证:「10% 成功率」听起来很低,但把它和成本放在一起看就变味了。10% 乘上「每次尝试几美元」,等于单次成功入侵的边际成本压到了几十美元量级——这个数字足以解释为什么这类活动会变多:它不再需要人的时间,只需要电费和 token。
Cairn 是什么:把入侵拆成状态机
Cairn 是一个开源的问题求解 harness(报告成文时它的代码在 GitHub 上公开可查)。它的设计不是「让模型自由发挥」,而是状态式的问题求解引擎:
- 每个攻击任务是一个 project,带
origin(从哪里开始)和goal(什么算完成)。 - 所有中间结果放在一块黑板(blackboard)上,分两类:fact(已确认的事实,比如「这个域名是 Magento 2.4.9」)和 intent(打算做的事)。上面那个「平均 140 条 fact / 项目」就是这个黑板的规模指标。
- 多个 agent 分工协作(bootstrap / reason / explore 这类角色),由 dispatcher 按优先级和并发上限派活。
- hints 用来注入人工提示——这也是攻击者唯一需要出手的地方。
这带来一个很关键的副作用,NorForge 点出来了:黑板上的东西会被原样存在面板数据库里。也就是说,只要面板暴露,攻击者积累的所有情报——包括他在别处收割的、用于这批目标的凭据——就一起暴露了。对合法的红队来说这是噩梦(拿客户凭据测试,结果把凭据发到了公网上),对攻击者来说这是把柄。
任务长什么样
NorForge 公开了一份「相当典型」的任务原文(中文原样保留,报告给了英译)。摘其要点:
这是四国(US/CA/JP/AU)已确认的真实购物电商站(流量 200k–1.5M)。 目标:找未认证 RCE(命令执行/代码注入/反序列化/文件上传 webshell/SQLi→RCE/SSTI/SSRF→RCE)。 优先:登录 SQLi、未认证端点、文件上传、反序列化、认证绕过、账户接管。 落地用 uploader shell(HTML 上传表单 +
move_uploaded_file保存),绝不落地命令执行型 webshell。 Goal met whenwhoami/id输出确认命令执行。
这份文本里有三个值得单独拿出来讲的细节,它们说明这套东西已经工程化到什么程度:
一是目标经过预筛选。 上游喂料管线用 Playwright 自动验证过「这个站能不能下单」,再按流量排序投喂给 Cairn。攻击者省掉的第一步侦察,是用真浏览器跑出来的。
二是「绝不落地命令执行型 webshell」是一条刻意的策略。 命令执行型 webshell 的特征太明显(system/exec/eval),上传器型只有上传表单 + 重命名落盘,杀软与完整性检查都更难抓。攻击者的目标不是「马上拿 shell」,而是先拿到长期访问权——AIA0001 集群的行为特征被描述为「专门为了拿到访问权、以后再回来做更多事」。
三是喂料管线里有个低级 bug,反而暴露了关联性。 多个面板上的目标域名都被吃掉了一个开头的字母 w:Wholesale 变成 holesale、winter 变成 inter。原因是有一个共用的 domain 规范化脚本在去 www 时多删了一个字母。NorForge 说这反而让关联面板变得非常好认。这是本次事件里最适合拿来当反面教材的一条:你自动化管线里的每个 bug,都是别人归因你的线索。
取证窗口:他们看到了攻击者的工具箱
有一次,其中一个 AIA0001 主机为了做带外(out-of-band)测试,在高位端口上开了一个 Web 服务器。就在这个短暂的窗口里,AI worker 的工作目录被看到了——于是有了这份工具清单:
| 类别 | 看到的工具 |
|---|---|
| 浏览器自动化 | Playwright + Chromium(目录里是浏览器状态) |
| PHP 载荷 | PHPGGC(PHP Generic Gadget Chains,生成反序列化 gadget chain) |
| 特定漏洞研究 | StyleSmuggler HTTP PoC,以及一个 Docker 化、按 revision 钉住的 Magento Open Source 2.4.9 验证实验室(对应 CVE-2026-75650) |
| 侦察 | OWASP Amass、dnsrecon、ffuf、Nmap、rockyou 字典 |
| 运行环境 | Docker、PHP/Composer、Python 3、Node.js、Xvfb、tmux、OpenSSL |
| 其他 | Vosk(离线语音识别)、Odoo 框架相关产物 |
两点值得注意:Magento 漏洞有专门的 Docker 实验室,说明攻击链是先在本地复现、再打真站的;以及工具清单里有识别语音的工具——安全圈对此的反应普遍是「这套工具箱不像只用来打电商站」。
还有一段 agent 任务更新被原文引用了:
本地环境是一个 Kali 容器,属于 tsec CTF/智能渗透测试竞赛框架……
NorForge 的判断是:这套 agent 环境是从一个更早的 CTF 竞赛配置里改出来的,「几乎可以确定是犯罪分子复用了既有工具,没有任何理由认为相关厂商参与其中」。这句话写得很克制——把一条容易被误读成「某厂商资助黑客」的证据,明确按「工具链复用」处理了。
防御侧今天就能做的四件事
以下是基于这份报告的可核事实,能立刻落到自己环境里的动作。代码都是标准库,可以直接跑。
一、先查自己的「AI 工具面板」有没有裸奔
这份报告最扎眼的一点是 25 个面板全在 8000 端口,而且没有鉴权。8000、8080、3000、8888 是各种 agent/编排框架(LangServe、FastAPI、Jupyter、Gradio、Open WebUI 一类)的默认端口。先扫自己的网段:
# scan_exposed_panels.py —— 只扫你自己拥有的主机,别扫别人的
import socket, ipaddress, concurrent.futures as cf
PORTS = [8000, 8080, 8888, 3000, 5000] # 常见 Agent/编排框架默认端口
BANNERS = [(b"uvicorn", "FastAPI/uvicorn"), (b"gradio", "Gradio"),
(b"jupyter", "Jupyter"), (b"Cairn", "Cairn 面板")]
def probe(ip, port, timeout=1.5):
try:
with socket.create_connection((ip, port), timeout=timeout) as s:
s.sendall(b"GET / HTTP/1.0\r\nHost: %b\r\n\r\n" % ip.encode())
data = s.recv(2048)
except OSError:
return None
hit = [name for sig, name in BANNERS if sig.lower() in data.lower()]
return {"target": f"{ip}:{port}", "server": data.decode("latin-1", "ignore").splitlines()[0] if data else "",
"matches": hit, "looks_like_panel": bool(hit)}
def scan(cidr):
ips = [str(i) for i in ipaddress.ip_network(cidr).hosts()]
out = []
with cf.ThreadPoolExecutor(max_workers=64) as ex:
futs = [ex.submit(probe, ip, p) for ip in ips for p in PORTS]
for f in cf.as_completed(futs):
r = f.result()
if r and r["looks_like_panel"]:
out.append(r)
return out
if __name__ == "__main__":
import json
for row in scan("10.0.0.0/24"): # 换成你自己的网段
print(json.dumps(row, ensure_ascii=False))
拿到清单后的动作比扫描更重要:默认绑 127.0.0.1 而不是 0.0.0.0,对外只开在有鉴权的反代后面。这份报告里所有攻击者的便宜,都来自「默认监听所有网卡 + 没有鉴权」这一个组合。
二、把「上传器型 webshell」变成可检测对象
报告里那份任务原文明确写了落地形态:HTML 上传表单 + move_uploaded_file,不带命令执行函数。针对这一类,签名检测是无效的——它本来就没有特征函数。有效的是完整性基线 + 上传目录纪律:
# upload_watch.py —— 上传目录的基线比对 + 上传器形态启发式
import hashlib, pathlib, json, re, sys
SUSPECT = re.compile(r"(move_uploaded_file|\$_(?:FILES|POST)\s*\[|<input[^>]+type=[\"']file[\"'])", re.I)
WEB_EXT = {".php", ".php3", ".php5", ".phtml", ".jsp", ".jspx", ".asp", ".aspx", ".war", ".cgi", ".pl"}
UPLOAD_DIR = pathlib.Path(sys.argv[1] if len(sys.argv) > 1 else "uploads")
BASE = pathlib.Path("upload_baseline.json")
def snapshot(root):
out = {}
for p in root.rglob("*"):
if p.is_file():
out[str(p.relative_to(root))] = hashlib.sha256(p.read_bytes()).hexdigest()
return out
now, base = snapshot(UPLOAD_DIR), (json.loads(BASE.read_text()) if BASE.exists() else {})
for rel, digest in now.items():
new = base.get(rel) != digest
if not new:
continue
f = UPLOAD_DIR / rel
flags = []
if f.suffix.lower() in WEB_EXT:
flags.append("可执行后缀")
try:
body = f.read_text("utf-8", "ignore")[:8192]
if SUSPECT.search(body):
flags.append("上传器形态代码")
except OSError:
pass
print(f"[新增/变更] {rel} {'| ' + ' '.join(flags) if flags else ''}")
BASE.write_text(json.dumps(now, indent=1))
两个工程要点:上传目录必须禁止脚本执行(Nginx 里对上传路径直接 location ~* /uploads/.*\.(php|phtml)$ { deny all; },或让上传落盘到另一个域名的裸对象存储),以及别把上传目录放在 webroot 下——报告里 AIA0002 集群的任务原文就写着「藏到主目录之外的深层目录里,名字起得像正常文件,比如 confirm.php」,说明攻击者是按这个假设找位置的。
三、从访问日志里抓「代理脚印」
AI 代理和爬虫不同:它的请求密度、UA、以及它验证目标的方式都很特别。报告给了一条现成的特征——目标是用 Playwright 真浏览器跑过的。
# hunt_agent_traffic.py —— 从 nginx access log 里找 Playwright/无头浏览器与扫描行为
import re, sys, collections, datetime
LINE = re.compile(r'(?P<ip>\S+) .*?"(?P<req>[^"]*)" (?P<st>\d{3}) .*?"(?P<ua>[^"]*)"')
HEADLESS = re.compile(r"(HeadlessChrome|Playwright|puppeteer|Xvfb|Chrome/\d+.*Headless)", re.I)
SCANNER = re.compile(r"(ffuf|nmap|nikto|sqlmap|amass|masscan)", re.I)
hits = collections.Counter()
for line in open(sys.argv[1], errors="ignore"):
m = LINE.search(line)
if not m:
continue
ua = m.group("ua") or ""
if HEADLESS.search(ua) or SCANNER.search(ua):
# 按「IP + 小时」聚合:短时间内密集探测才是信号,偶发一次不算
hits[(m.group("ip"), 1)] += 1
for (ip, _), n in hits.most_common(50):
if n >= 30:
print(f"{ip}\t{n} 次无头/扫描器请求 —— 建议核对是否为自动化验收,否则封禁并查来源")
这份代码不抓攻击,它抓的是**「值得看一眼的异常」**。把它接进日志管道,比事后翻日志有效得多——报告里 AIA0001 到被发现时已经跑了 1,100 多个项目。
四、别忘了最便宜的那件事:凭据卫生
报告里 Trufflehog 扫出来「数以百计」的凭据、11% 验证有效。这个比例之所以高,是因为面板里存的是攻击者为了打这批目标而专门准备的凭据——它们本来就是活的。自己这边要做的是反过来:确认没有任何一份凭据躺在没有鉴权的服务里。最容易被忽略的三处是:面板/编排工具的数据库、CI 的构建日志与环境变量转储、以及测试环境里从生产复制过来的配置。
# 用官方镜像扫一遍你自己的仓库与部署目录(不联网、只读)
docker run --rm -v "$PWD:/p" trufflesecurity/trufflehog:latest filesystem /p --only-verified
--only-verified 是关键:它会真的去尝试用这个凭据认证,只报验证有效的,正好对上报告里那个 11% 的口径。
别把这篇读成机器人大末日
有三件事必须说清楚,否则这类新闻很容易被读成「AI 已经能自主攻破一切」。
第一,成功率是约 10%。 也就是说绝大多数项目并没有达成目标。报告自己的措辞是「很多剩余任务达成了部分目标,或者收集到了可用于后续尝试的信息」——这更像是「把人工侦察与试探的成本压到接近零」,而不是「自动化的完胜」。
第二,攻击者的 opsec 差得离谱。 面板无鉴权、SSH 登录凭据写在任务里、域名规范化脚本有 bug、工具目录在带外测试时被开在公网端口上。报告结论段写得很直白:「在追求快速结果的过程中,很多人忘了基本的安全预防措施,不但把自己的行动安全搞砸了,还让攻击面比原本的意图大得多。」
第三,模型与 harness 都在快速变化。 报告里那句「这只是当下这个时间点的快照」值得抄一遍:这套数字的保质期可能只有几个月。
真正需要记住的推论是成本结构变了:一次入侵尝试从「人的几小时」变成了「几美元」。这不会让所有系统立刻被打穿,但它会改变谁的资产值得被试探——报告里 AIA0001 的挑选标准写得很清楚:四国、真实电商站、流量 20 万以上。它会先来试探那些「看起来像有钱」的站。
总结
这份报告里最有用的一句话,是它对攻击者自己的评价:「不论这些操作者掌握多少攻击技术,保护自己的基础设施显然不在他们的强项里。」
对我们这边,可执行的顺序是:
- 收口:所有 agent/编排/面板类服务默认绑本机,对外必须有鉴权——报告里 25 个面板、统一 8000 端口、零鉴权,这一条就能挡住同类事故的大头。
- 上传纪律:上传目录移出 webroot、禁止脚本执行、做完整性基线;对「上传器型 webshell」不要指望签名,指望基线。
- 日志里带上代理视角:无头浏览器身份与高频探测是最早的信号,比事后做取证便宜。
- 凭据卫生:用
--only-verified扫,把「躺着的活凭据」清零;攻击者是因为把凭据存在了裸奔面板上才被抓到的,同样的错误在防守方这边一样致命。
最后提醒一句事实边界:本文引用的 claudecode_deepseek-v4.1-flash 这个 worker 名、以及「约 50 美元一次成功入侵」「1,200 多个站点」这些数字,分别来自研究者的推测与第三方报道口径,厂商没有确认,NorForge 也明确标注了不确定性。可以据此调风险策略,但别把这些数字当成可以引用的定论。
参考与致谢
- NorForge《Inside Cairn》(2026-10-19,一手报告):https://norforge.com/blog/inside_cairn
- Reuters(2026-10-19,独家报道):https://www.reuters.com/legal/government/how-one-hacker-used-ai-agents-few-thousand-dollars-break-into-scores-sites-2026-10-19/
- The Next Web(第三方汇总,含 5,000 美元 token 估算):https://thenextweb.com/news/ai-agents-hacker-cairn-100-sites-deere-norforge
- Unite.AI(对工具清单的整理):https://unite.ai/norforge-details-20-plus-exposed-cairn-panels-used-in-ai-driven-intrusions
- Cairn 开源仓库(配置样例里可见
ANTHROPIC_BASE_URL指向第三方 Anthropic 兼容端点):https://github.com/oritera/Cairn - TruffleHog(凭据扫描,支持
--only-verified):https://github.com/trufflesecurity/trufflehog - CVE-2026-75650(报告提到的 Magento 漏洞,可自行到 NVD 核对受影响版本区间):https://nvd.nist.gov/vuln/detail/CVE-2026-75650
黑客把入侵外包给 AI 代理:NorForge 从裸奔在公网的控制台里看到了全过程
- 作者
- 摆渡
- 发布于
- 许可协议
- CC BY-NC-SA 4.0
除另有声明外,本文采用上述许可协议。转载或改编请注明作者与原文地址,并遵守许可条款。