Analyse approfondie : vulnérabilité XSS stockée dans le plugin LiteSpeed Cache pour WordPress (CVE-2024-47374)
Un examen approfondi de la vulnérabilité CVE-2024-47374 qui affecte le plugin LiteSpeed Cache pour WordPress, de son impact, et une analyse technique de notre méthode de détection.
Introduction
CVE-2024-47374 est une vulnérabilité de sévérité élevée qui affecte le plugin LiteSpeed Cache pour WordPress, un plugin tout-en-un d'accélération de site qui aide à améliorer les performances d'un site web en stockant dans un cache les données fréquemment consultées. Cette vulnérabilité de cross-site scripting (XSS) stocké touche toutes les versions du plugin jusqu'à 6.5.0.2 incluse. Si cette faille est exploitée, des acteurs malveillants peuvent exécuter du code JavaScript arbitraire.
Détails techniques
La vulnérabilité provient de la façon dont le plugin traite l'en-tête HTTP X-LSCACHE-VARY-VALUE. La valeur de cet en-tête est analysée sans assainissement ni échappement adéquats en sortie, ce qui permet d'injecter du code JavaScript. La faille est spécifiquement liée aux fonctionnalités d'optimisation CSS du plugin.
Composants vulnérables
Les fonctions de génération de CCSS et d'UCSS, _ccss() et _load(), acceptent les paramètres et les en-têtes HTTP nécessaires pour générer et stocker les données. La file d'attente est créée à l'aide des lignes de code suivantes :
_ccss()danslitespeed-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()danslitespeed-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);
Notez que la variable $vary est stockée à l'aide de la fonction $this->save_queue. La variable $vary est construite par la fonction finalize_full_varies, qui inclut des données issues de l'en-tête HTTP X-LSCACHE-VARY-VALUE.
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;
}
Lors de l'affichage de la file d'attente dans le panneau d'administration, l'information Vary Group (dérivée de $vary) est affichée directement, sans aucun assainissement ni échappement, sur la page d'administration.
D'après le site de l'éditeur du plugin, la fonctionnalité Vary Group fusionne les notions de cache varies et de user roles. La vulnérabilité apparaît parce que les utilisateurs peuvent fournir le Vary Group par un en-tête HTTP, qui est ensuite affiché sur la page d'administration sans assainissement approprié.
Voici l'extrait de code concerné, dans 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>
Cette absence d'assainissement permet à un attaquant d'injecter des scripts malveillants dans le panneau d'administration.
Conditions d'exploitation
Pour que l'exploit réussisse, deux paramètres spécifiques de Page Optimization doivent être activés :
- CSS Combine : ON
- Generate UCSS : ON
Impact de la CVE-2024-47374
L'impact de la vulnérabilité est important, pour plusieurs raisons :
Sévérité élevée : avec un score CVSS de 7,2, cette vulnérabilité est classée en sévérité élevée.
Large surface d'attaque : LiteSpeed Cache est un plugin WordPress populaire, installé sur plus de 6 millions de sites WordPress.
Prise de contrôle du compte administrateur : si la session d'un administrateur est détournée, l'attaquant peut prendre le contrôle complet du site WordPress. C'est le scénario le plus grave, car il permet de :
- Installer des plugins ou des thèmes malveillants
- Modifier le contenu du site
- Accéder à des données sensibles et les exfiltrer
- Utiliser le site compromis comme point de pivot pour d'autres attaques
Détournement de session : la vulnérabilité peut servir à détourner des sessions d'utilisateurs authentifiés, ce qui permet aux attaquants d'effectuer des actions au nom de la victime.
La vulnérabilité XSS stockée de LiteSpeed Cache permet à des attaquants d'injecter et d'exécuter du code JavaScript arbitraire. Notre script de détection vise à identifier les instances vulnérables en vérifiant la version du plugin. Examinons-le :
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
]
Analyse de l'identification des versions
La liste VULNERABLE_VERSIONS contient trois expressions régulières, chacune conçue pour correspondre à des plages précises de versions vulnérables :
r"(?P<major_0_to_5>[0-5])\.(?P<minor>[0-9]+)(\.(?P<patch>[0-9]+))?" :
- Cette regex correspond à toutes les versions de
0.0.0à5.x.x. (?P<major_0_to_5>[0-5])capture le numéro de version majeure de 0 à 5.\.(?P<minor>[0-9]+)correspond au numéro de version mineure.(\.(?P<patch>[0-9]+))?correspond facultativement à un numéro de correctif, car certaines versions n'en ont pas.
r"(?P<major_6_0_to_4>6)\.(?P<minor_0_to_4>[0-4])(\.(?P<patch>[0-9]+))?" :
- Cette regex couvre les versions
6.0.xà6.4.x. (?P<major_6_0_to_4>6)correspond spécifiquement à la version majeure 6.\.(?P<minor_0_to_4>[0-4])correspond aux versions mineures de 0 à 4.- Le numéro de correctif est de nouveau facultatif.
r"(?P<major_6_5>6)\.5\.0(\.(?P<patch_0_to_2>[0-2]))$" :
- Cette regex est très précise : elle cible uniquement les versions
6.5.0,6.5.0.1et6.5.0.2. (?P<major_6_5>6)\.5\.0correspond exactement à la version6.5.0.(\.(?P<patch_0_to_2>[0-2]))$garantit que seuls les numéros de correctif 0, 1 ou 2 sont retenus, et rien d'autre.
Cette correspondance précise des versions est essentielle, car la vulnérabilité affecte toutes les versions jusqu'à 6.5.0.2 incluse. Grâce à ces expressions régulières soigneusement conçues, le script identifie avec exactitude les versions vulnérables tout en excluant la version corrigée 6.5.1 et toutes les versions ultérieures.
Identification de la cible et vérification de la vulnérabilité
Le script utilise deux éléments clés pour identifier la cible et vérifier la vulnérabilité :
-
Identification de la cible :
python accept_pattern = [re.compile("LiteSpeed Cache")]Ce motif vérifie la présence deLiteSpeed Cachedans le fichierreadme.txt. C'est un moyen simple mais efficace de confirmer que le pluginLiteSpeed Cacheest installé sur le site WordPress cible. -
Vérification de la vulnérabilité :
python match_pattern = [ re.compile(f"Stable tag: {version}") for version in VULNERABLE_VERSIONS ]Cette approche crée une liste de motifs regex, chacun recherchant dans le fichierreadme.txtla ligneStable tag:avec un numéro de version correspondant à l'une des versions vulnérables. En parcourant ces motifs, le script détermine rapidement si la version installée est vulnérable.
Stratégie de requête de fichier
Le script cible le fichier readme.txt situé à l'adresse /wp-content/plugins/litespeed-cache/readme.txt. Ce fichier est choisi pour les raisons suivantes :
- Il est généralement accessible publiquement dans les installations WordPress.
- Il contient l'information de version dans un format standardisé (
Stable tag: X.X.X). - L'accès à ce fichier est non intrusif et ne risque ni de modifier ni d'endommager le système cible.
En combinant ces éléments, le script offre une méthode robuste, précise et sûre pour détecter les instances de LiteSpeed Cache vulnérables à la CVE-2024-47374.
Atténuation
Pour atténuer cette vulnérabilité, les administrateurs de sites web doivent mettre à jour le plugin LiteSpeed Cache vers la version 6.5.1 ou une version ultérieure. Cette version a été publiée le 25 septembre 2024 et corrige la vulnérabilité CVE-2024-47374.
Tester la vulnérabilité avec OXO
Si vous craignez que votre instance soit vulnérable, suivez ces étapes pour exécuter un test avec l'outil OXO :
Installez OXO via pip :
pip install -U ostorlab
Installez l'agent asteroid depuis l'agent store d'OXO :
oxo agent install agent/ostorlab/asteroid
Lancez le scan avec l'agent asteroid à l'aide de la commande suivante :
oxo scan run --agent agent/ostorlab/asteroid link --url <target-URL> --method GET