CVE-2025-11837 (QSA-25-47) 分析与复现
本文最后更新于 天前,文中部分描述可能已经过时。
在前不久的 PWN2OWN 2025 大赛上,QNAP Malware Remover 被曝出存在一个严重的 PreAuth RCE 漏洞,编号 CVE-2025-11837 (QSA-25-47)。作为 QNAP NAS 系统(QTS、QuTS hero、QuTScloud)的预置软件,这个漏洞的影响范围可以说是非常广泛,应该有不少人已经做了相关研究,不过网上似乎目前还没有关于漏洞细节的分析。正好前段时间 QWB 的一道 RW 题就是考察的这个漏洞,为了解题做了一些分析和研究,就顺便水一篇文章吧。
顺带一提,QWB 那道题目用的 QuTScloud 系统版本有点低,似乎可以用之前的一个其他组件的漏洞去打,不知道有多少队伍是非预期解。估计是因为 QuTScloud 的授权比较难搞,网上能查到的授权方法只适用于低版本的 QuTScloud。不过我后面又做了一些研究,已经搞定了最新版 QuTScloud 5.2.0 的授权,后续大概或许有空会再写一篇文章分享出来。
漏洞分析
对于这种已经有补丁的漏洞,对比补丁前后的代码无疑是最好的思路。旧版本解包后的内容可以在 QWB 给的题目环境的 /share/CACHEDEV1_DATA/.qpkg/MalwareRemover 目录下找到。

至于最新版其实不必联网安装后再提取(联网还可能导致授权失效),可以直接从 QNAP 官方网站下载最新的 QPKG 格式的安装包,然后使用 binwalk -e 进行解包,根据 binwalk 报告的文件类型再对 binwalk 的提取结果使用 TAR / GZIP 进一步解包。
解包后可以看到,Malware Remover 主要的代码集中在 modules 目录,全是编译好的 pyc 文件,此外还有 MalwareRemover_exec、MalwareRemover_scan、www/malware_remover.cgi 等二进制程序。

作为 Web 手自然不是很想去看二进制,于是决定先看看那堆 pyc 文件。用 uncompyle6 反编译后,对比补丁前后的 Python 代码,结果发现除了编译日期不同外,没有任何区别。
看来还是得看看二进制程序了。由于漏洞描述中提到攻击者可以远程利用,那看来应该位于 CGI 程序 www/malware_remover.cgi 中。通过 IDA Pro 结合 BinDiff 对比分析补丁前后的 malware_remover.cgi,发现相似度几乎为 100%,只有 main 函数的相似度为 97%。

分析范围缩小至 main 函数后,很快就发现了区别。补丁后的代码中多了一段检查,要求某个字符串必须是纯英文字母或数字,否则直接返回 401 未授权错误。

进一步跟踪发现,这个字符串实则取自用户请求的 Cookie 头中,这表明这个字符串应该是和认证相关的。


从反编译的结果来看,这个字符串对应的 Cookie 名称存储在 off_219660 数组中,然而跟过去一看傻眼了,这都是些啥???





这 Cookie 名称都不是 ASCII 字符啊???怎么可能?莫慌,八成是加密了,看一眼交叉引用。


果然,在 main 函数的开头还有这么长一串东西差点被我忽略了,盲猜这个 a6cC35277ad1a4g 应该就是密钥,sub_B420 是解密函数,而 sub_B520 则是对密钥做了初始化。于是把 sub_B420、sub_B520 的反编译结果丢给 Gemini 3 Pro,它很快就给我写出了 Python 版本的解密代码:
raw_key_str = "6c=c3527=7ad1a4gdg4=4g00g6d`a1<`"
xor_val = 243 ^ 246
real_key = ""
for c in raw_key_str:
real_key += chr(ord(c) ^ xor_val)
key_bytes = [ord(c) for c in real_key]
key_len = len(key_bytes)
def decrypt(cipher):
res = ""
for i, b in enumerate(cipher):
k = key_bytes[i % key_len]
val = (b - k) & 0xFF
res += chr(val)
return res有了这个脚本后,不仅仅是 Cookie 名称,可以把 main 函数开头那一大堆加密的内容全解开。解开一看,原来四个 Cookie 名称分别是 nas_sid、NAS_SID、QTS_SSID、QTS_SSL_SSID,看来漏洞应该就是在这四个 Cookie 中的任何一个,传入一些字母数字之外的特殊字符,最终造成 RCE。
于是继续往下跟踪,看看到底哪里用到了这些 SID,很快就又注意到了问题。

这里调用了 snprintf 函数,格式化字符串模板是 byte_21A000,两个参数是 v77[0] 和 v8,其中 v8 就是提取出用户输入的 Cookie 值。那么自然就会好奇这个模板长什么样,跟过去结果发现又是加密的。。。(这么喜欢加密,一定有什么不可告人的秘密藏着掖着吧 hhh)

继续用上面的解密脚本解开,然后。。。瞳孔地震

还记得第二个参数吗?在补丁之前的版本中,那可是用户完全可控的 Cookie 值啊!就这么水灵灵地拼接进 Python 代码里了???
至此真相大白,malware_remover.cgi 在处理用户请求时,会从 Cookie 头中依次提取出 nas_sid、NAS_SID、QTS_SSID、QTS_SSL_SSID,拼接进这段 Python 代码里执行,调用 qnap_helper 模块的 check_sid 函数进行检查,如果这个函数返回 True,则认为用户已认证通过,如果四个 Cookie 值都没有通过检查则返回 401 未授权错误。补丁前的版本没有对用户传入的 Cookie 值做任何检查,导致攻击者可以注入任意 Python 代码,从而实现远程代码执行。补丁后的版本则增加了对 Cookie 值的检查,要求其必须是纯字母数字,虽然这个不优雅的代码拼接依然存在,但也只能留给攻击者无尽遐想了。。。
漏洞利用
EXP:
都读到这了,如果你真的认真读了,写出 EXP 应该是轻而易举的,所以我就不写了,留给大家自己动手实践吧~(注意遵守相关法律法规,千万不要拿别人的 NAS 下手,不许干坏事哦!)总结
原以为 PWN2OWN 上曝出的价值 $20000 的漏洞应该会相当复杂,没想到竟是如此简单的代码注入漏洞,整个分析过程稍微有点难度的也就是解密那堆字符串,如今交给大模型也已经能够轻松搞定了。然而就是这么简单的一个漏洞,却能够造成巨大的危害,真真切切地影响成千上万 QNAP NAS 用户的数据安全。从这个漏洞中可以看出,QNAP 在代码质量方面确实存在不小的问题,尤其是这种直接把用户输入拼接进代码执行、为了不被发现还搞了一堆加密的做法,简直就是草台班子。对于真正想搞事情的攻击者来说,这些加密根本起不到任何作用。
事实上,在后续研究了 QNAP 其他组件的代码后,短时间内我是不会考虑购买 QNAP 的 NAS 设备了。整个系统简直是个用胶水勉强粘起来的缝合怪——各种模块之间全靠五花八门的胶水代码硬凑在一起,语言和框架混用得让人眼花缭乱:C/C++、Python (Flask、Django)、PHP (Laravel、LMVC)、Bash,你敢想象网络与虚拟交换机这种核心功能居然是用 Django 写的。光是用户认证就见识了不下三种实现方式:有的调用动态链接库中的 auth_get_session 函数,有的通过 HTTP 请求调用 authLogin.cgi 接口,还有的执行 /sbin/user_cmd 命令。即使是 Python 调用 authLogin.cgi 接口,又能搞出三种花样:有拼接 curl 命令的,有用 requests 库的,还有直接上 urllib 的。在这个漏洞中,malware_remover.cgi 明明可以调用 auth_get_session 函数进行认证,结果偏偏就要自己拼接 Python 代码调用 qnap_helper.check_sid,搞笑的是 qnap_helper.check_sid 又是去调用 curl 命令请求 authLogin.cgi 接口,真是相当令人无语。


值得肯定的是,QNAP 对于安全漏洞的态度是相当积极的,不仅推出了漏洞赏金计划,积极参与 PWN2OWN 这类国际级安全赛事,补丁发布也较为及时。希望 QNAP 未来能够更加注重提升代码质量,少一些胶水代码,多一些严谨设计,毕竟用户的数据安全才是最重要的。

评论