群晖 C3RB3R 勒索事件:漏洞技术分析
整理时间:2026-11-10
资料来源:Innora Security Research(CVE-2026-13639 反汇编分析)、Rapid7 DEF CON 33 议题、Synology-SA-26:13/26:14 官方公告、CosmicBytez Labs、360 反勒索团队归因
分析对象:DS420plus,DSM 90080
一、先把因果理清
这轮事件的链条不是一条,而是三条并行:
链一(主入口) CVE-2026-13639 会话密钥全零 → 伪造 JWT → 免密登录
链二(同批) CVE-2026-13684 SCGI 输出编码 → 任意文件读写
链三(老洞) CVE-2024-10441 两年前修的洞,没升级的机器还在中
↓
拿到 admin / root
↓
植入 /etc/cron.d/synot* 定时任务
↓
下载 C3RB3R 加密程序
↓
文件改名 .L0CK3D + read-me3.txt
三条链里,链一最致命,因为它绕过了你认为"最有用"的所有防护。
二、CVE-2026-13639:会话密钥变成 32 个零
2.1 一句话
DSM 生成会话 JWT 签名密钥时,把刚清零的栈缓冲区写进了文件,而不是随机数生成器的输出。结果密钥是 32 个 0x00。
2.2 反汇编证据
libsyno-pam-login.so.7.0 里的 synojwt::GetOrCreateSessionHashKey():
movaps [rsp], xmm0 ; 栈缓冲清零(32 字节 = 0)
movaps [rsp+0x10], xmm0
...
lea rdi, [0xfbf00] ; randombytes_buf(全局缓存, 32) ← 随机数写进全局缓冲
call randombytes_buf
mov rdi, rsp ; fwrite(栈缓冲, 1, 32, f) ← 却从栈读!
call fwrite ; → hash.key 写入 32 个零字节
...
lea rdi, [0xfbf00] ; fread(全局缓存, 1, 32, f) ← 再读回来
call fread ; → 内存中的密钥 = 32 × 0x00
这里有两层错:
- 随机数生成器调用正常,输出落在全局缓冲
0xfbf00 - 但
fwrite用的是rsp(栈),那个缓冲区刚被movaps清零
于是磁盘上的密钥文件是 32 个零字节,内存里读回来的也是 32 个零字节。
这不是"熵不足",是熵为零——密钥是个常量。
2.3 修复对比
| 项目 | 漏洞版(72806-8 及以前) | 修复版(72806-9) |
|---|---|---|
| 写入源 | rsp(清零的栈) |
r12(随机数缓冲) |
| 打开方式 | fopen("w") |
open64(O_WRONLY|O_CREAT|O_TRUNC|O_EXCL|O_NOFOLLOW, 0600) |
| 权限 | 随 umask | fchmod(0400) + fchown |
| 目录 | /usr/syno/etc/private/session/ 权限 1777 |
收紧 |
注意那个 1777 的目录权限——它意味着任何本地用户都能在这个目录里预置文件,配合 fopen("w") 不检查符号链接,还有一个本地提权面。
2.4 利用链:为什么密码和 2FA 都失效
① 伪造 JWT
header {"alg":"HS256","typ":"JWT"}
payload {"sub":"admin","aud":"2FA","iat":<now-60>,"exp":<now+3600>}
signature = HMAC-SHA256(signing_input, 32×0x00) ← 密钥已知
② POST /webapi/entry.cgi api=SYNO.API.Auth method=login
account=admin passwd=<伪造的 JWT> ← 密码框填 JWT
③ DSM 的 PAM 栈 /etc/pam.d/webui 执行顺序:
auth [success=3 ...] pam_syno_1fa_tmptok.so ← 第一步
SynoPAMVerifyTmpToken(user, "2FA", token)
· 检查 sub == 用户名 ✓
· 检查 aud == "2FA" ✓
· 检查 iat ≤ now ≤ exp ✓
· 用 hash.key 验 HS256 签名 ✓(密钥全零,攻击者也有)
→ 验证通过,success=3 直接跳过后面 3 个模块
(被跳过的)
auth ... pam_unix.so ← 密码校验,跳过
auth ... pam_syno_winbind.so ← 域认证,跳过
auth ... pam_syno_ldap.so ← LDAP,跳过
④ 继续走 pam_syno_otp.so
· 对"未启用 2FA 的账户"直接通过
· 对启用了 2FA 的账户——但前面已经 success=3 跳过了
⑤ 结果:无密码、无 2FA,认证为该用户(可以是 admin)
关键点:pam_syno_1fa_tmptok.so 在密码模块之前。这是设计如此——"一次性临时令牌"本来就应该跳过密码。问题在于验证这个令牌的密钥是可预测的。
2.5 这解释了三件"反常"
| 你以为的保护 | 为什么没用 |
|---|---|
| 16 位复杂密码 | 密码模块根本没被调用 |
| 手机令牌 2FA | 1FA 临时令牌被当作"已通过 2FA",且 success=3 跳过 |
| 错 5 次封 IP | 攻击者一次就成功,没有"错误"可计 |
| 改默认端口 | 只改了路径,不改变漏洞 |
你所有的防护都建立在"攻击者需要猜密码"这个假设上。而这个漏洞让攻击者根本不需要碰密码环节。
三、CVE-2026-13684:SCGI 输出编码
比链一简单,但同样是 9.8。
SCGI 的定位:DSM 前端 Web 服务器 ⇄ 后端服务进程之间的协议
(作用类似 FastCGI)
缺陷:输出越过这个接口时,没有正确编码/转义
→ 攻击者可以构造畸形 SCGI 请求
→ 读写预期范围之外的文件
→ 或者让后端服务崩溃(DoS)
没有公开的反汇编级细节,Synology 只说"improper encoding or escaping of output"(CWE-838)。
危害同链一:未认证的任意文件读写。拿到这个,攻击者可以直接写 /etc/cron.d/ 或者 SSH 公钥。
四、CVE-2024-10441:两年前的老洞
这是最值得说的一条。
CVE-2024-10441 2024 年 12 月就修复了
2026 年 10 月还在被大规模利用
为什么:
- 群晖的更新是分阶段推送——你的控制面板可能显示"已是最新",但实际版本号没到
- 很多用户早年更新踩过坑(升级后套件不兼容),于是关掉了自动更新
- NAS 是"装完就忘"的设备,放在角落几年不动
结果就是:一个已经修复两年的漏洞,仍然有大量设备在被打。
这轮的受害者里,有一部分就停在 DSM 7.2.1 Update 5 这类老版本上,连 2024 年那个洞都没修。
五、C3RB3R 勒索样本做了什么
5.1 五个动作
① 查杀安全进程 遍历并 kill 常见的杀毒/监控进程
② 识别 CPU 架构 自动判断 x86_64 还是 ARM → 拉对应编译版
③ 建防重标记 /bin/l0ck.enc
④ 拉双备份主程序 /bin/qnetd(主)+ /bin/libgld(备)
⑤ 自删痕迹 加密完删掉 /bin/qnetd、/etc/cron.d/*、/bin/l0ck.enc
5.2 为什么它"不搞死"系统
它硬编码跳过了 27 个群晖核心系统目录:
/boot /etc /usr/syno /var/packages ...
目的不是仁慈,是商业——系统必须能开机,受害者才能看到 read-me3.txt 并去付钱。
5.3 分级加密策略
> 5MB 的普通文件 每隔 5MB 只加密头部 1MB
→ 几秒钟废掉一部几十 GB 的蓝光原盘
187 个高价值扩展名 全文件加密
(数据库、财务账套、虚拟机镜像……)
这是针对 NAS 场景优化的——NAS 里动辄几万部电影、几十 TB 数据,全量加密跑不完,用户会中途拔电源。
5.4 加密算法
每个文件:ChaCha8 独立 256 位密钥 + 64 位 nonce
再用内置 RSA-2048 公钥加密文件密钥
没有私钥,数学上无法暴力破解。 这就是为什么"别付赎金"之外,唯一的出路是备份。
六、对你的机器:一个必须立刻检查的点
你升级到了 DSM 90080,超过了修复基线 7.4-90075。
但是——Innora 的分析里有这么一句:
升级后若密钥全零:删除
/usr/syno/etc/private/session/hash.key(修复版代码会在下次登录正确重建)——零密钥签名的会话应视为已泄露。
为什么"升级了还可能有问题":
修复版代码的逻辑是 GetOrCreateSessionHashKey —— 名字里的 "OrCreate"
意思是:文件存在就直接用,不存在才创建
你是从旧版本升级上来的
→ hash.key 在升级前就已经存在了(内容是 32 个零)
→ 升级后,代码看到文件存在,直接读它
→ 密钥仍然是 32 个零
所以升级不一定解决了这个问题。 必须实际看一眼。
检查命令(需要 SSH)
xxd /usr/syno/etc/private/session/hash.key
判读:
00000000: 0000 0000 0000 0000 0000 0000 0000 0000 ← 全零 = 仍有问题
00000000: 0000 0000 0000 0000 0000 0000 0000 0000
→ 必须删除这个文件
00000000: a3f2 91c8 4e7b ... ← 随机字节 = 正常
→ 无需处理
如果确认是全零
# 1. 删除(修复版会在下次登录时用正确逻辑重建)
rm -f /usr/syno/etc/private/session/hash.key
# 2. 强制所有会话失效(重启相关服务)
synosystemctl restart nginx 2>/dev/null || /usr/syno/bin/synosystemctl restart nginx
# 3. 重新登录 DSM(这时会生成新密钥)
副作用:所有已登录的会话会失效,需要重新登录。这是好事——那些会话可能已经被伪造过。
顺便查入侵痕迹
# 恶意文件
ls -la /bin/qnetd /bin/libgld /bin/l0ck.enc 2>/dev/null
# 恶意定时任务
ls -la /etc/cron.d/ | grep -i synot
# 被加密的文件
find /volume1 -maxdepth 3 -name "*.L0CK3D" -o -name "README*.txt" 2>/dev/null | head
七、为什么这次事件值得记住
7.1 安全假设被打破了
普通人装 NAS 的安全思路是:
改端口 + 强密码 + 2FA + 关闭 admin 账号 = 安全
这四件事全部建立在"认证环节是可信的"这个前提上。
CVE-2026-13639 直接把认证环节本身变成了一条绕过通道——它不是"猜密码猜得更快",是"根本不去猜"。
推论:任何"仅靠认证强度"的防护,在认证逻辑本身有缺陷时全部归零。
7.2 "最新版"不等于"已修复"
三个层次:
① 控制面板说"已是最新",但分阶段推送没到你 → 版本号要自己看
② 你打了 DSM 补丁,但套件(PHP/Web Station)没打 → PHP2Shell 还在
③ 你升级了,但升级前的坏状态被继承(hash.key 全零)→ 补丁不生效
7.3 备份的形态决定了能不能救命
| 备份方式 | 勒索时 |
|---|---|
| RAID | 无效,还把乱码同步到每块盘 |
| 实时双向同步(Cloud Sync) | 有害,把加密文件推上云覆盖好的 |
| 常年插着的 USB 硬盘 | 无效,root 权限一锅端 |
| Btrfs 不可变快照 | 有效,C3RB3R 没清快照 |
| 离线拔掉的硬盘 / 只读异地 | 有效 |
文章里那句话是对的:
你的"实时备份"在替病毒打工。
八、给你的行动清单
✅ 已完成
DSM 升级到 90080(超过基線 7.4-90075)
全部共享文件夹启用不可变快照
公网零暴露(22 端口全关,域名零命中)
SSH 关闭
⚠️ 待确认(SSH 关闭,需要你开一下)
① xxd /usr/syno/etc/private/session/hash.key
全零 → 删除,重新登录
② 查 /bin/qnetd、/etc/cron.d/synot*、*.L0CK3D
📋 建议补
③ Hyper Backup 重装(或接受只有快照)
④ NFS 收紧授权范围(现在是 192.168.31.0/24 + no_root_squash)
⑤ PHP 老版本停用(7.3/7.4/8.0/8.1 全异常,留 8.2 即可)
最优先的是 ①。这是唯一一个"升级了但可能还没修好"的点。
附:时间线
2024-12 CVE-2024-10441 修复(后续仍被大量利用)
2025-08 Rapid7 在 DEF CON 33 讲 DisguiseDelimit(分隔符注入)
2026-09-18 Synology-SA-26:13 发布,修复 8 个 DSM 漏洞
CVE-2026-13639 / 13684 / 13635
2026-10-21 PHP 官方紧急修复 PHP2Shell(CVE-2026-109618/109619)
2026-10-22 Synology-SA-26:14,PHP 套件安全更新
2026-10-28 凌晨,全球范围 C3RB3R 集中爆发
2026-10-29 Synology 官方声明
2026-11-02 国内高校发布排查通知
2026-11-10 本文整理