本篇博文是《从0到1学习安全测试》中漏洞复现系列的第四篇博文,主要内容是通过代码审计来分析 NodeBB 存在拒绝服务攻击的原因,并对此进行复现,往期系列文章请访问博主的 安全测试 专栏;
严正声明:本博文所讨论的技术仅用于研究学习,旨在增强读者的信息安全意识,提高信息安全防护技能,严禁用于非法活动。任何个人、团体、组织不得用于非法目的,违法犯罪必将受到法律的严厉制裁。
漏洞的 CVE 编号为 CVE-2023-30591,适用于 NodeBB 版本小于 2.8.10;
NodeBB 是一个基于 Node.js 构建的开源社区论坛平台,该平台的特点之一是利用 Socket.IO
进行即时交互和实时通知。 以下是 NodeBB 的一些优势:
由于对 Socket.IO 消息的解析和处理不当,未经身份验证的攻击者能够发送恶意 Socket.IO 消息,导致 NodeBB 工作实例崩溃。尽管 NodeBB 的集群管理器尝试生成新的替代工作器,但在短时间内多次使 NodeBB 工作器崩溃后,可能会导致 NodeBB 集群管理器终止。
利用该漏洞,可以通过使用数组作为 Socket.IO 事件名称,在调用 eventName.startsWith()
时触发崩溃,或者使用对象作为 Socket.IO 事件名称,并设置属性toString
,在调用 eventName.toString()
时触发崩溃。
主要代码源自 /src/socket.io/index.js
:
jsasync function onMessage(socket, payload) {
...
const eventName = payload.data[0];
...
const parts = eventName.toString().split('.'); // [1]
const namespace = parts[0];
const methodToCall = parts.reduce((prev, cur) => { // [2]
if (prev !== null && prev[cur] && (!prev.hasOwnProperty || prev.hasOwnProperty(cur))) {
return prev[cur];
}
return null;
}, Namespaces);
if (!methodToCall || typeof methodToCall !== 'function') { // [3]
...
return callback({ message: `[[error:invalid-event, ${escapedName}]]` });
}
...
if (!eventName.startsWith('admin.') && ratelimit.isFlooding(socket)) { // [4]
winston.warn(`[socket.io] Too many emits! Disconnecting uid : ${socket.uid}. Events : ${socket.previousEvents}`);
return socket.disconnect();
}
...
}
根据上述源码,只需要绕开 3 在 4 处抛出异常或者直接在 1 处抛出异常,都将导致 NodeBB 拒绝服务,因为在 /loader.js 中,集群管理器尝试重新启动异常退出的工作进程,如果太多工作线程在硬编码的 10 秒阈值内异常退出,集群管理器就会得出结论,发生了启动错误,并将自行终止,从而杀死所有 NodeBB 工作线程:
由于攻击者可以随意导致 NodeBB 工作线程突然退出,这使得攻击者能够完全终止 NodeBB,从而导致持续的拒绝服务。
只需要直接在 1 处抛出异常或者绕开 3 在 4 处抛出异常,都将导致 NodeBB 拒绝服务。
源码没有对 eventName
执行类型验证或强制转换,并且假定 String
是类型。
结合 1 处将 eventName
转换成 String 的处理方式,因此可以直接构造 eventName
为 {"toString": 1};
,运行结果:
结合 1 处将 eventName
转换成 String 后进行分割提取事件名,可以构造如下 eventName
:
jsconst eventName = ["topics.loadMoreTags"];
eventName
为 topics.loadMoreTags
是因为在 /src/socket.io/index.js 源码中,modules
数组中的其中一个元素就是 topics
,而 loadMoreTags
是它的一个方法,如下所示:
jsfunction requireModules() {
const modules = [
'admin', 'categories', 'groups', 'meta', 'modules',
'notifications', 'plugins', 'posts', 'topics', 'user',
'blacklist', 'uploads',
];
modules.forEach((module) => {
Namespaces[module] = require(`./${module}`);
});
}
这样子 methodToCall
就会获取到相应的值,使得 !methodToCall || typeof methodToCall !== 'function'
值为 false,从而进行绕过。
举个例子,下面将用 url.parse
来代替 topics.loadMoreTags
:
Namespaces[module]
赋值:
methodToCall
赋值:
!methodToCall || typeof methodToCall !== 'function'
的值:
然后在 eventName.startsWith('admin.')
处抛出异常:
本文复现了旧版 NodeBB 存在的拒绝服务攻击漏洞,通过本案例提醒各位读者,赶紧升级 NodeBB 的版本,同时提高自身的安全意识,在自己编写代码时,一定要对变量进行校验以及强制类型转换,以防被绕过造成危害!
以上就是博文 NodeBB 被爆未授权拒绝服务攻击 的所有内容了,希望对大家有所帮助!
严正声明:本博文所讨论的技术仅用于研究学习,旨在增强读者的信息安全意识,提高信息安全防护技能,严禁用于非法活动。任何个人、团体、组织不得用于非法目的,违法犯罪必将受到法律的严厉制裁。
📝 上篇精讲:(三)建议升级!旧版 Cecil 存在路径遍历漏洞!
💖 我是 𝓼𝓲𝓭𝓲𝓸𝓽,期待你的关注,创作不易,请多多支持;
👍 公众号:sidiot的技术驿站;
🔥 系列专栏:安全测试工具和技术:从漏洞扫描到渗透测试
我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。