LiteSpeed Cache for WordPress漏洞CVE-2024-50550
LiteSpeed Cache for WordPress(LSCWP)是一款多功能网站加速插件,具有独一无二的服务器级缓存以及优化功能,安装量超六百万。
LiteSpeed Cache是一款非常流行的WordPress网站加速插件,顾名思义,它具有高级缓存功能和优化功能,安装在超过六百万个站点上。
一、基本情况
LSCWP是一款开源插件,这款插件支持WordPress Multisite,并且可兼容大多数流行插件,包括WooCommerce、bbPress和Yoast SEO。
栋科技漏洞库关注到 LiteSpeed Cache for WordPress 近期披露一个高危安全漏洞,该漏洞被追踪为 CVE-2024-50550,CVSS评分为8.1。
二、漏洞分析
CVE-2024-50550漏洞是一个未经身份验证的权限升级问题,该漏洞可能允许未经身份验证的攻击者获得管理员访问权限并执行恶意操作。
CVE-2024-50550漏洞源于使用弱安全哈希检查,可能会被不良行为者暴力破解,从而允许爬虫功能被滥用来模拟登录用户,包括管理员。
Patchstack研究人员分析,该漏洞源于一个名为 is_role_simulation 的函数,与2024年08月公开记录的早期的漏洞CVE-2024-28000类似。
该漏洞依赖于具有已知值的弱安全哈希检查且只能通过额外罕见配置才能重现,这些配置必须由插件爬虫功能中的管理员角色的用户执行。
具体的插件配置如下:
· Crawler -> General Settings -> Crawler: ON
· Crawler -> General Settings -> Run Duration: 2500 – 4000
· Crawler -> General Settings -> Interval Between Runs: 2500 – 4000
·Crawler -> General Settings -> Server Load Limit: 0
·Crawler -> Simulation Settings -> Role Simulation: 1 (ID of user with administrator role)
·Crawler -> Summary -> Activate: Turn every row to OFF except Administrator
PHP中的 rand() 和 mt_rand() 函数返回的值对于许多用例而言可能是“足够随机”的,但它们的不可预测性本身就不足以用于安全相关功能。
特别是在 mt_srand 的使用可能性有限的情况下不可预测性增加,且需要明确的是,LiteSpeed Cache 插件的爬虫功能使用角色模拟功能。
LiteSpeed Cache for WordPress插件“role simulation”功能哈希检查较弱,该功能旨在帮助网站爬虫模拟不同的用户级别以优化内容交付。
函数is_role_simulation()依赖litespeed_hash和litespeed_flash_hash,存储在cookie中两个哈希值,但哈希值在随机因素不足情况下生成。
正是这种哈希值在随机因素不足的情况下生成的情况存在,这使得哈希值变成了可预测的情况,因此容易遭到不怀好意的黑客暴力破解。
1、LSCWP is_role_simulation 函数:
src/router.cls.php, function is_role_simulation()
public function is_role_simulation()
{
if (is_admin()) {
return;
}
if (empty($_COOKIE['litespeed_hash']) && empty($_COOKIE['litespeed_flash_hash'])) {
return;
}
self::debug('starting role validation');
// Check if is from crawler
// if ( empty( $_SERVER[ 'HTTP_USER_AGENT' ] ) || strpos( $_SERVER[ 'HTTP_USER_AGENT' ], Crawler::FAST_USER_AGENT ) !== 0 ) {
// Debug2::debug( '[Router] user agent not match' );
// return;
// }
// Flash hash validation
if (!empty($_COOKIE['litespeed_flash_hash'])) {
$hash_data = self::get_option(self::ITEM_FLASH_HASH, array());
if ($hash_data && is_array($hash_data) && !empty($hash_data['hash']) && !empty($hash_data['ts']) && !empty($hash_data['uid'])) {
if (time() - $hash_data['ts'] < 120 && $_COOKIE['litespeed_flash_hash'] == $hash_data['hash']) {
self::debug('role simulate uid ' . $hash_data['uid']);
self::delete_option(self::ITEM_FLASH_HASH);
wp_set_current_user($hash_data['uid']);
return;
}
}
}
// Hash validation
if (!empty($_COOKIE['litespeed_hash'])) {
$hash_data = self::get_option(self::ITEM_HASH, array());
if ($hash_data && is_array($hash_data) && !empty($hash_data['hash']) && !empty($hash_data['ts']) && !empty($hash_data['uid'])) {
if (time() - $hash_data['ts'] < $this->conf(Base::O_CRAWLER_RUN_DURATION) && $_COOKIE['litespeed_hash'] == $hash_data['hash']) {
if (empty($hash_data['ip'])) {
$hash_data['ip'] = self::get_ip();
self::update_option(self::ITEM_HASH, $hash_data);
} else {
$server_ips = apply_filters('litespeed_server_ips', array($hash_data['ip']));
if (!self::ip_access($server_ips)) {
self::debug('WARNING: role simulator ip check failed [db ip] ' . $hash_data['ip'], $server_ips);
return;
}
}
wp_set_current_user($hash_data['uid']);
return;
}
}
}
self::debug('WARNING: role simulator hash not match');
}
插件第一个检查是 Flash Hash 检查,只有当哈希时间在120秒内生成时才接受,原则上说此检查应足以防止对大量哈希值进行暴力攻击。
第二次检查$_COOKIE['litespeed_hash'],根据$this->conf(Base::O_CRAWLER_RUN_DURATION)检查哈希TTL,该设置默认值400秒。
这个设置值可以在爬虫设置中找到,管理员可以将该值设置为较高但现实的值,例如 2500 到 4000 秒,如此操作就可以使上述漏洞重现。
现在使用的哈希值确实将有32个字符的长度,但rrand()函数在mt_rand()调用之前仍然使用如下函数:
mt_srand((int)((float)microtime()*1000000))
因此尽管它有32个随机字符,但生成的哈希值被限制在100万种可能性。
此漏洞还需要$This->conf(Base::O_CRAWLER_LOAD_LIMIT)的值为0,这是因为如果该值设置为0时,爬虫程序在启动时立即停止。
2、再来看看下面的两个函数,分别是_engine_start和_adjust_current_threads:
src/crawler.cls.php, function _engine_start()
private function _engine_start()
{
// check if is running
// if ($this->_summary['is_running'] && time() - $this->_summary['is_running'] < $this->_crawler_conf['run_duration']) {
// $this->_end_reason = 'stopped';
// self::debug('The crawler is running.');
// return;
// }
// check current load
$this->_adjust_current_threads();
if ($this->_cur_threads == 0) {
$this->_end_reason = 'stopped_highload';
self::debug('Stopped due to heavy load.');
return;
}
------------ CUT HERE ------------
src/crawler.cls.php, function _adjust_current_threads()
private function _adjust_current_threads()
{
$curload = $this->get_server_load();
if ($curload == -1) {
self::debug('set threads=0 due to func sys_getloadavg not exist!');
$this->_cur_threads = 0;
return;
}
$curload /= $this->_ncpu;
// $curload = 1;
if ($this->_cur_threads == -1) {
// init
if ($curload > $this->_crawler_conf['load_limit']) {
$curthreads = 0;
} elseif ($curload >= $this->_crawler_conf['load_limit'] - 1) {
$curthreads = 1;
} else {
$curthreads = intval($this->_crawler_conf['load_limit'] - $curload);
if ($curthreads > $this->conf(Base::O_CRAWLER_THREADS)) {
$curthreads = $this->conf(Base::O_CRAWLER_THREADS);
}
}
} else {
// adjust
$curthreads = $this->_cur_threads;
if ($curload >= $this->_crawler_conf['load_limit'] + 1) {
sleep(5); // sleep 5 secs
if ($curthreads >= 1) {
$curthreads--;
}
} elseif ($curload >= $this->_crawler_conf['load_limit']) {
// if ( $curthreads > 1 ) {// if already 1, keep
$curthreads--;
// }
} elseif ($curload + 1 < $this->_crawler_conf['load_limit']) {
if ($curthreads < $this->conf(Base::O_CRAWLER_THREADS)) {
$curthreads++;
}
}
}
// $log = 'set current threads = ' . $curthreads . ' previous=' . $this->_cur_threads
// . ' max_allowed=' . $this->conf( Base::O_CRAWLER_THREADS ) . ' load_limit=' . $this->_crawler_conf[ 'load_limit' ] . ' current_load=' . $curload;
$this->_cur_threads = $curthreads;
$this->_cur_thread_time = time();
}
因此,爬虫程序在执行时将调用_engine_start()函数,然后该函数将调用_adjust_current_threads()函数。
$this->_cur_threads本身的初始值是-1,因此当进程在_adjust_current_threads()函数上启动
并且我们将$this->_crawler_conf['load_limit']值设置为0时,它将把$curthreads设置为0值,并最终将其分配给$this->.cur_thread变量。
回到_engine_start()函数,如果$this->_cur_threads值设置为0,则初始爬网过程将停止。
回到is_role_simulation()函数,由于初始爬网过程中角色模拟尚未命中,$hash_data值不包含爬虫IP。
因此当攻击者试图对哈希执行暴力并成功执行时,它将绕过爬虫IP的检查(!self::IP_access($server_ips))。
综合来说该漏洞利用难度相对较大,安全研究人员表示:该漏洞凸显了确保用作安全哈希或随机数的值的强度和不可预测性的至关重要性。
3、该漏洞具体时间线如下:
2024年09月23日,中国台湾研究人员发现缺陷并向 Patchstack 报告,但由于PoC不完整和使用不切实际场景导致报告被拒。
2024年9月24日,PatchStack 关注到该缺陷后,向受影响插件背后的公司 LiteSpeed Technologies 发出预警,并建议进行安全增强。
2024年10月5日至10日,LiteSpeed Technologies 开发人员创建概念验证(PoC)漏洞并与 LiteSpeed 共享。
2024年10月17日, LiteSpeed Technologies 发布受影响插件的修补版本 LiteSpeed Cache 6.5.2,用于修补报告的问题。
2024年10月29日,将漏洞添加到 Patchstack 漏洞数据库中,安全咨询文章公开发布。
LiteSpeed 在发布的修复补丁中删除了角色模拟过程,并且使用随机值生成器更新了哈希生成步骤,以避免将哈希限制为 100 万种可能性。
尽管该漏洞已被成功修补,但安全人员提示,该漏洞严重性不应被低估,因为它允许威胁行为者通过利用易受攻击的哈希值来冒充管理员。
获得管理员权限后,攻击者可能会部署恶意插件、更改网站内容、访问后端数据库或者是部署后门程序来以实现持久性访问,严重性倍增。
三、漏洞影响
LiteSpeed Cache for WordPress < 6.5.2
四、修复建议
LiteSpeed Cache for WordPress >= 6.5.2
五、参考链接
https://patchstack.com/articles/rare-case-of-privilege-escalation-patched-in-litespeed-cache-plugin/