Thu 10 October 2024
Introduction
CVE-2024-47374
is a high-severity vulnerability affecting the LiteSpeed Cache
plugin for WordPress, an all-in-one site acceleration plugin that helps improve the performance of a website by storing frequently accessed data in a cache. This stored cross-site scripting (XSS
) vulnerability impacts all versions of the plugin up to and including 6.5.0.2
. The flaw, if exploited, could allow malicious actors to execute arbitrary JavaScript code.
Technical Details
The vulnerability stems from the way the plugin handles the X-LSCACHE-VARY-VALUE
HTTP header. This header value is parsed without adequate sanitization and output escaping, allowing for the injection of javascript code. The flaw is specifically related to the plugin's CSS optimization features.
Vulnerable Components
The CCSS and UCSS generation functions, _ccss()
and _load()
, accept the necessary parameters and HTTP headers to generate and store the data. The queue is created using the following lines of code:
_ccss()
inlitespeed-cache/src/css.cls.php
private function _ccss()
{
global $wp;
$request_url = home_url($wp->request);
$filepath_prefix = $this->_build_filepath_prefix('ccss');
$url_tag = $this->_gen_ccss_file_tag($request_url);
$vary = $this->cls('Vary')->finalize_full_varies();
=============== Skip ================
// Store it to prepare for cron
Core::comment('QUIC.cloud CCSS in queue');
$this->_queue = $this->load_queue('ccss');
if (count($this->_queue) > 500) {
self::debug('CCSS Queue is full - 500');
return null;
}
$queue_k = (strlen($vary) > 32 ? md5($vary) : $vary) . ' ' . $url_tag;
$this->_queue[$queue_k] = array(
'url' => apply_filters('litespeed_ccss_url', $request_url),
'user_agent' => substr($ua, 0, 200),
'is_mobile' => $this->_separate_mobile(),
'is_webp' => $this->cls('Media')->webp_support() ? 1 : 0,
'uid' => $uid,
'vary' => $vary,
'url_tag' => $url_tag,
); // Current UA will be used to request
$this->save_queue('ccss', $this->_queue);
load()
inlitespeed-cache/src/ucss.cls.php
public function load($request_url, $dry_run = false)
{
=============== Skip ================
$vary = $this->cls('Vary')->finalize_full_varies();
=============== Skip ================
// Store it for cron
$this->_queue = $this->load_queue('ucss');
if (count($this->_queue) > 500) {
self::debug('UCSS Queue is full - 500');
return false;
}
$queue_k = (strlen($vary) > 32 ? md5($vary) : $vary) . ' ' . $url_tag;
$this->_queue[$queue_k] = array(
'url' => apply_filters('litespeed_ucss_url', $request_url),
'user_agent' => substr($ua, 0, 200),
'is_mobile' => $this->_separate_mobile(),
'is_webp' => $this->cls('Media')->webp_support() ? 1 : 0,
'uid' => $uid,
'vary' => $vary,
'url_tag' => $url_tag,
); // Current UA will be used to request
$this->save_queue('ucss', $this->_queue);
Note that the $vary
variable is stored using the $this->save_queue
function. The $vary
variable is constructed by the finalize_full_varies
function which includes data from the X-LSCACHE-VARY-VALUE
HTTP header.
public function finalize_full_varies()
{
$vary = $this->_finalize_curr_vary_cookies(true);
$vary .= $this->finalize_default_vary(get_current_user_id());
$vary .= $this->get_env_vary();
return $vary;
}
/**
* Get request environment Vary
*
* @since 4.0
*/
public function get_env_vary()
{
$env_vary = isset($_SERVER['LSCACHE_VARY_VALUE']) ? $_SERVER['LSCACHE_VARY_VALUE'] : false;
if (!$env_vary) {
$env_vary = isset($_SERVER['HTTP_X_LSCACHE_VARY_VALUE']) ? $_SERVER['HTTP_X_LSCACHE_VARY_VALUE'] : false;
}
return $env_vary;
}
When displaying the queue in the admin panel, the Vary Group
information (derived from $vary
) is printed directly without any sanitization or escaping on the Admin page.
According to the plugin vendor's website, the Vary Group
functionality merges the ideas of cache varies
and user roles
. The vulnerability arises because users can provide the Vary Group
through an HTTP header, which is then displayed on the admin page without proper sanitization.
Here's the relevant code snippet from litespeed-cache/tpl/page_optm/settings_css.tpl.php
:
<?php if (!empty($ucss_queue)) : ?>
<div class="litespeed-callout notice notice-warning inline">
<h4>
<?php echo sprintf(__('URL list in %s queue waiting for cron', 'litespeed-cache'), 'UCSS'); ?> ( <?php echo count($ucss_queue); ?> )
<a href="<?php echo Utility::build_url(Router::ACTION_UCSS, UCSS::TYPE_CLEAR_Q); ?>" class="button litespeed-btn-warning litespeed-right">Clear</a>
</h4>
<p>
<?php $i = 0;
foreach ($ucss_queue as $k => $v) : ?>
<?php if ($i++ > 20) : ?>
<?php echo '...'; ?>
<?php break; ?>
<?php endif; ?>
<?php if (!is_array($v)) continue; ?>
<?php if (!empty($v['_status'])) : ?><span class="litespeed-success"><?php endif; ?>
<?php echo esc_html($v['url']); ?>
<?php if (!empty($v['_status'])) : ?></span><?php endif; ?>
<?php if ($pos = strpos($k, ' ')) echo ' (' . __('Vary Group', 'litespeed-cache') . ':' . substr($k, 0, $pos) . ')'; ?>
<?php if ($v['is_mobile']) echo ' <span data-balloon-pos="up" aria-label="mobile">📱</span>'; ?>
<?php if (!empty($v['is_webp'])) echo ' WebP'; ?>
<br />
<?php endforeach; ?>
</p>
</div>
This lack of sanitization allows an attacker to inject malicious scripts into the admin panel.
Exploitation Requirements
For the exploit to be successful, two specific Page Optimization settings must be enabled:
- CSS Combine: ON
- Generate UCSS: ON
Impact of CVE-2024-47374
The vulnerability's impact is significant due to several factors:
High Severity: With a CVSS score of 7.2, this vulnerability is classified as high-severity.
Wide Attack Surface: LiteSpeed Cache
is a popular WordPress plugin installed on over 6 million WordPress websites.
Admin Account Takeover: If an administrator's session is hijacked, the attacker could gain complete control over the WordPress site. This is the most severe scenario, as it allows for:
- Installing malicious plugins or themes
- Modifying site content
- Accessing and exfiltrating sensitive data
- Using the compromised site as a pivot for further attacks
Session Hijacking: The vulnerability could be used to hijack authenticated user sessions, allowing attackers to perform actions on behalf of the victim.
The stored XSS vulnerability in LiteSpeed Cache
allows attackers to inject and execute arbitrary JavaScript code. Our detection script focuses on identifying vulnerable instances by checking the plugin version. Let's take a look at it:
VULNERABLE_VERSIONS = [
r"(?P<major_0_to_5>[0-5])\.(?P<minor>[0-9]+)(\.(?P<patch>[0-9]+))?",
r"(?P<major_6_0_to_4>6)\.(?P<minor_0_to_4>[0-4])(\.(?P<patch>[0-9]+))?",
r"(?P<major_6_5>6)\.5\.0(\.(?P<patch_0_to_2>[0-2]))$",
]
class CVE202447374Exploit(webexploit.WebExploit):
accept_request = definitions.Request(
method="GET", path="/wp-content/plugins/litespeed-cache/readme.txt"
)
check_request = definitions.Request(
method="GET", path="/wp-content/plugins/litespeed-cache/readme.txt"
)
accept_pattern = [re.compile("LiteSpeed Cache")]
match_pattern = [
re.compile(f"Stable tag: {version}") for version in VULNERABLE_VERSIONS
]
Version Identification Analysis
The VULNERABLE_VERSIONS
list contains three regular expressions, each designed to match specific ranges of vulnerable versions:
r"(?P<major_0_to_5>[0-5])\.(?P<minor>[0-9]+)(\.(?P<patch>[0-9]+))?"
:
- This regex matches all versions from
0.0.0
to5.x.x
. (?P<major_0_to_5>[0-5])
captures the major version number from 0 to 5.\.(?P<minor>[0-9]+)
matches the minor version number.(\.(?P<patch>[0-9]+))?
optionally matches a patch version, as some versions might not have it.
r"(?P<major_6_0_to_4>6)\.(?P<minor_0_to_4>[0-4])(\.(?P<patch>[0-9]+))?"
:
- This regex covers versions
6.0.x
to6.4.x
. (?P<major_6_0_to_4>6)
specifically matches major version 6.\.(?P<minor_0_to_4>[0-4])
matches minor versions 0 to 4.- The patch version is again optional.
r"(?P<major_6_5>6)\.5\.0(\.(?P<patch_0_to_2>[0-2]))$"
:
- This regex is highly specific, targeting only versions
6.5.0
,6.5.0.1
, and6.5.0.2
. (?P<major_6_5>6)\.5\.0
matches exactly version6.5.0
.(\.(?P<patch_0_to_2>[0-2]))$
ensures that only patch versions 0, 1, or 2 are matched, and nothing else.
This precise version matching is crucial because the vulnerability affects all versions up to and including 6.5.0.2
. By using these carefully crafted regular expressions, the script can accurately identify vulnerable versions while excluding the patched version 6.5.1
and any future releases.
Target Identification and Vulnerability Check
The script uses two key components for identifying the target and checking for vulnerability:
-
Target Identification:
python accept_pattern = [re.compile("LiteSpeed Cache")]
This pattern checks for the presence ofLiteSpeed Cache
in thereadme.txt
file. It's a simple yet effective way to confirm that theLiteSpeed Cache
plugin is installed on the target WordPress site. -
Vulnerability Check:
python match_pattern = [ re.compile(f"Stable tag: {version}") for version in VULNERABLE_VERSIONS ]
This approach creates a list of regex patterns, each looking for theStable tag:
line in thereadme.txt
file with a version number matching one of the vulnerable versions. By iterating through these patterns, the script can quickly determine if the installed version is vulnerable.
File Request Strategy
The script targets the readme.txt
file located at /wp-content/plugins/litespeed-cache/readme.txt
. This file is chosen because:
- It's typically publicly accessible in WordPress installations.
- It contains the version information in a standardized format (
Stable tag: X.X.X
). - Accessing this file is non-intrusive and doesn't risk modifying or damaging the target system.
By combining these elements, the script provides a robust, accurate, and safe method for detecting instances of LiteSpeed Cache
vulnerable to CVE-2024-47374
.
Mitigation
To mitigate this vulnerability, website administrators should update the LiteSpeed Cache
plugin to version 6.5.1
or later. This version was released on September 25, 2024, and addresses the CVE-2024-47374
vulnerability.
Testing for the Vulnerability Using OXO
If you're concerned your instance might be vulnerable, follow these steps to run a test using the OXO tool:
Install OXO via pip:
pip install -U ostorlab
Install the asteroid agent from the OXO agent store:
oxo agent install agent/ostorlab/asteroid
Run the scan using the asteroid agent with the following command:
oxo scan run --agent agent/ostorlab/asteroid link --url <target-URL> --method GET
We do newsletters, too
Get the latest news, updates, and product innovations from Ostorlab right in your inbox.