徹底解説:WordPress向けLiteSpeed Cacheプラグインにおける格納型XSSの脆弱性(CVE-2024-47374)
WordPress向けLiteSpeed Cacheプラグインに影響するCVE-2024-47374の脆弱性について、その影響と、当社の検出手法の技術的な解説を交えて詳しく見ていきます。
はじめに
CVE-2024-47374は、WordPress向けのLiteSpeed Cacheプラグインに影響する、重大度の高い脆弱性です。このプラグインは、頻繁にアクセスされるデータをキャッシュに保存することでWebサイトのパフォーマンス向上を支援する、オールインワンのサイト高速化プラグインです。この格納型クロスサイトスクリプティング(XSS)の脆弱性は、6.5.0.2までのすべてのバージョン(同バージョンを含む)に影響します。この欠陥が悪用されると、悪意のある攻撃者が任意のJavaScriptコードを実行できる可能性があります。
技術的な詳細
この脆弱性は、プラグインがX-LSCACHE-VARY-VALUE HTTPヘッダーを処理する方法に起因しています。このヘッダーの値は、十分なサニタイズと出力時のエスケープが行われないまま解析されるため、javascriptコードを注入できてしまいます。この欠陥は、特にプラグインのCSS最適化機能に関係しています。
脆弱なコンポーネント
CCSSとUCSSの生成関数である_ccss()と_load()は、データを生成して保存するために必要なパラメーターとHTTPヘッダーを受け取ります。キューは次のコードによって作成されます。
litespeed-cache/src/css.cls.php内の_ccss()
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);
litespeed-cache/src/ucss.cls.php内のload()
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);
$vary変数は$this->save_queue関数を使って保存されることに注意してください。$vary変数はfinalize_full_varies関数によって構築され、そこにはX-LSCACHE-VARY-VALUE HTTPヘッダーからのデータが含まれます。
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;
}
管理パネルでキューを表示する際、($varyから導出される)Vary Groupの情報は、サニタイズやエスケープが一切行われないまま、管理ページに直接出力されます。
プラグインベンダーのWebサイトによれば、Vary Group機能はcache variesとuser rolesの考え方を統合したものです。ユーザーがHTTPヘッダーを通じてVary Groupを指定でき、それが適切なサニタイズなしに管理ページに表示されるため、脆弱性が生じます。
以下は、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>
このサニタイズの欠如により、攻撃者は管理パネルに悪意のあるスクリプトを注入できます。
悪用の要件
エクスプロイトを成功させるには、ページ最適化の次の2つの設定が有効になっている必要があります。
- CSS Combine:ON
- Generate UCSS:ON
CVE-2024-47374の影響
この脆弱性の影響は、いくつかの要因により重大です。
重大度が高い:CVSSスコアは7.2であり、この脆弱性は重大度「高」に分類されています。
広範なアタックサーフェス:LiteSpeed Cacheは人気のあるWordPressプラグインで、600万を超えるWordPressのWebサイトにインストールされています。
管理者アカウントの乗っ取り:管理者のセッションがハイジャックされると、攻撃者はWordPressサイトを完全に掌握できる可能性があります。これは最も深刻なシナリオであり、次のことが可能になります。
- 悪意のあるプラグインやテーマのインストール
- サイトのコンテンツの改ざん
- 機密データへのアクセスと持ち出し
- 侵害したサイトを、さらなる攻撃の足がかりとして利用すること
セッションハイジャック:この脆弱性を利用して認証済みユーザーのセッションをハイジャックし、攻撃者が被害者になりすまして操作を行える可能性があります。
LiteSpeed Cacheの格納型XSSの脆弱性により、攻撃者は任意のJavaScriptコードを注入して実行できます。当社の検出スクリプトは、プラグインのバージョンを確認することで脆弱なインスタンスを特定することに重点を置いています。その内容を見てみましょう。
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
]
バージョン識別の分析
VULNERABLE_VERSIONSリストには3つの正規表現が含まれており、それぞれが脆弱なバージョンの特定の範囲に一致するように設計されています。
r"(?P<major_0_to_5>[0-5])\.(?P<minor>[0-9]+)(\.(?P<patch>[0-9]+))?":
- この正規表現は、
0.0.0から5.x.xまでのすべてのバージョンに一致します。 (?P<major_0_to_5>[0-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]+))?":
- この正規表現は、バージョン
6.0.xから6.4.xまでをカバーします。 (?P<major_6_0_to_4>6)は、メジャーバージョン6に限定して一致します。\.(?P<minor_0_to_4>[0-4])は、マイナーバージョン0から4に一致します。- パッチバージョンは、ここでも任意です。
r"(?P<major_6_5>6)\.5\.0(\.(?P<patch_0_to_2>[0-2]))$":
- この正規表現は非常に限定的で、バージョン
6.5.0、6.5.0.1、6.5.0.2のみを対象とします。 (?P<major_6_5>6)\.5\.0は、バージョン6.5.0に厳密に一致します。(\.(?P<patch_0_to_2>[0-2]))$は、パッチバージョン0、1、2のみに一致し、それ以外には一致しないことを保証します。
この脆弱性は6.5.0.2までのすべてのバージョン(同バージョンを含む)に影響するため、このように正確にバージョンを照合することが極めて重要です。慎重に作成されたこれらの正規表現を使うことで、スクリプトは修正済みのバージョン6.5.1とそれ以降のリリースを除外しつつ、脆弱なバージョンを正確に特定できます。
対象の識別と脆弱性のチェック
スクリプトは、対象の識別と脆弱性のチェックのために、2つの主要なコンポーネントを使用します。
-
対象の識別:
python accept_pattern = [re.compile("LiteSpeed Cache")]このパターンは、readme.txtファイルにLiteSpeed Cacheが含まれているかどうかを確認します。対象のWordPressサイトにLiteSpeed Cacheプラグインがインストールされていることを確認するための、シンプルながら効果的な方法です。 -
脆弱性のチェック:
python match_pattern = [ re.compile(f"Stable tag: {version}") for version in VULNERABLE_VERSIONS ]このアプローチでは、正規表現パターンのリストを作成します。各パターンは、readme.txtファイル内で、脆弱なバージョンのいずれかに一致するバージョン番号を持つStable tag:の行を探します。これらのパターンを順に照合することで、スクリプトはインストールされているバージョンが脆弱かどうかをすばやく判定できます。
ファイルのリクエスト戦略
スクリプトは、/wp-content/plugins/litespeed-cache/readme.txtにあるreadme.txtファイルを対象とします。このファイルを選んだ理由は次のとおりです。
- WordPressのインストール環境では、通常、公開でアクセス可能であるため。
- 標準化された形式(
Stable tag: X.X.X)でバージョン情報が含まれているため。 - このファイルへのアクセスは非侵入的であり、対象のシステムを変更したり損傷させたりするリスクがないため。
これらの要素を組み合わせることで、スクリプトはCVE-2024-47374に対して脆弱なLiteSpeed Cacheのインスタンスを検出するための、堅牢で正確かつ安全な手法を提供します。
緩和策
この脆弱性を緩和するには、Webサイトの管理者はLiteSpeed Cacheプラグインをバージョン6.5.1以降に更新する必要があります。このバージョンは2024年9月25日にリリースされ、CVE-2024-47374の脆弱性に対処しています。
OXOを使った脆弱性のテスト
自社のインスタンスが脆弱ではないかと懸念される場合は、次の手順に従ってOXOツールでテストを実行してください。
pipでOXOをインストールします。
pip install -U ostorlab
OXOのエージェントストアからasteroidエージェントをインストールします。
oxo agent install agent/ostorlab/asteroid
次のコマンドで、asteroidエージェントを使ってスキャンを実行します。
oxo scan run --agent agent/ostorlab/asteroid link --url <target-URL> --method GET