NodeBB账户接管漏洞分析(CVE-2022-46164) | STAR Labs
作者:Ngo Wei Lin (@Creastery) & River Koh (@oceankex)
日期:2023年9月29日
阅读时间:14分钟
目录
关于CVE-2022-46164
CVE-2022-46164影响NodeBB,一个基于Node.js并搭配Redis、MongoDB或PostgreSQL数据库的开源社区论坛平台。该平台利用Socket.IO实现即时交互和实时通知功能。
NodeBB官方公告的相关细节如下:
- 标题:通过原型漏洞实现账户接管
- 受影响版本:< 2.6.1
- 已修补版本:2.6.1
- CVSS评分:严重 - 9.4(CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L)
- 影响:由于在socket.io消息处理中使用了一个带有原型的普通对象,特制负载可用于伪装其他用户并接管账户。
- 变通方案:站点维护者可以将提交48d1439精选到代码库中以修补漏洞。
进一步调查发现:
- 漏洞允许在继承默认Object.prototype的对象上任意调用方法。
- 通过调用Object类型的内置方法,攻击者可覆盖Socket.IO连接的属性。
- 除了用户伪装和账户接管,还可利用此漏洞使服务器崩溃。
- CVSSv3.1评分应为9.8而非9.4,向量字符串为CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H。
- v2.6.1版本中的补丁不足以缓解漏洞。
- 漏洞影响至NodeBB v2.8.0(即NodeBB < v2.8.1)。
漏洞详情
注:本节漏洞分析使用易受CVE-2022-46164攻击的NodeBB v2.6.0。
NodeBB虽提供REST API促进客户端与服务器通信,但某些功能通过Socket.IO执行以实现更低延迟的交互。NodeBB平台使用Socket.IO库建立WebSocket连接,并回退到HTTP长轮询。Socket.IO库支持除默认open、close、error和message事件外的自定义事件发射。标准Socket.IO消息可使用socket连接上的.emit()方法发射:
|
|
NodeBB利用此功能模拟传统HTTP请求。在src/socket.io/index.js中,Socket.IO事件名称用于指定“API路由”,消息数据用于传递请求参数。注意,发送的参数可以是除函数外的任何类型。
|
|
API路径由斜杠分隔,而eventName由点分隔。例如,以下代码从uid为46的用户移除管理员角色:
|
|
为实现此功能,NodeBB导出一组方法并将其存储在requireModules()的Namespaces对象中:
|
|
用户要执行操作,需使用eventName指定要调用的方法,并提供参数作为消息体。以下代码片段演示了如何使用eventName调用指定方法。为确保指定函数有效,NodeBB在onMessage()中递归查找eventName,每次迭代检查对象的指定属性是否有效:
|
|
每次迭代执行检查prev !== null && prev[cur]。此处存在漏洞。根据MDN Web文档:
JavaScript中几乎所有对象都是Object的实例;典型对象从Object.prototype继承属性(包括方法)。
只要方法是Namespaces内的嵌套属性,即可执行。但由于Namespaces未使用Object.create(null)初始化,它包含指向Object.prototype的私有属性__proto__:
|
|
由此,Object.prototype的constructor属性将返回包含多个可调用内置方法的Object的引用:
|
|
通过检查方法调用,可观察此漏洞:
|
|
预期功能是允许用户调用Namespaces对象内的函数。但可调用内置方法如Object.assign()和Object.defineProperties()。这两种方法特别有用,因为它们允许覆盖socket对象的属性。
实现管理员伪装
与无状态HTTP请求不同,Socket.IO连接是有状态的。存储在socket对象中的此“状态”允许服务器识别客户端及其相关权限。NodeBB通常在模块的before()函数中执行此授权检查,该函数在调用methodToCall()之前执行:
|
|
SocketAdmin.before()的实现如下:
|
|
观察到socket对象的uid属性是用于确定用户相应访问权限的唯一标识符。因此,将uid覆盖为管理员用户的uid将赋予我们管理员权限。假设默认管理员账户uid:1正在使用中,以下负载允许使用同一Socket.IO连接的后续事件以管理员权限执行:
|
|
由于user.isAdministrator(1) === true,使用同一Socket.IO连接的后续事件可以管理员权限执行。一旦攻击者实现权限提升,即可注册管理员账户。以管理员用户登录后,攻击者可通过API更改其他账户的密码和邮箱来危害其他账户。
实现拒绝服务
此漏洞还可用于使服务器崩溃。除了uid,socket对象的另一个属性是adapter。Socket.IO具有Adapter,这是一个服务器端组件,负责向所有或部分客户端广播事件。当socket连接关闭时,Socket.IO内部调用socket.adapter.delAll()函数将其从其他房间移除。因此,将adapter属性覆盖为{}意味着typeof socket.adapter.delAll === object(即非函数),导致无效函数调用,进而使进程崩溃:
|
|
进程终止时,NodeBB尝试通过启动另一个进程来重启。但如果快速连续触发多次重启(更具体地说,10秒内3次重启),NodeBB将完全停止:
|
|
利用条件
要实现用户伪装或账户接管,数据库中必须至少有一个用户。实现持久拒绝服务未识别其他约束。未经验证的攻击者预计能够可靠地利用此漏洞。
概念验证
创建了一个概念验证利用脚本来自动化以下操作:
- 以管理员身份执行任何方法
- 注册管理员账户
- 接管任何现有账户
利用脚本
|
|
使用示例
安装以下依赖:
|
|
以管理员身份执行任何方法:
|
|
注册管理员账户:
|
|
接管任何现有账户:
|
|
触发持久拒绝服务:
|
|
补丁分析
v2.6.1
根据此漏洞的官方公告,NodeBB v2.6.1中的提交48d1439包含一行修复,应精选到现有代码库中以修补漏洞:
|
|