
CVE-2026-16540 — Simply Schedule Appointments < 1.6.12.6 未認証の予約データ開示および一括削除
purge エンドポイントによる一括削除| フィールド | 詳細 |
|---|---|
| CVE | CVE-2026-16540 |
| プラグイン | Simply Schedule Appointments |
| 影響を受けるバージョン | < 1.6.12.6 |
| 修正バージョン | 1.6.12.6 |
| アクティブインストール数 | ~70,000 |
| CVSS 3.1 | 6.5 (中) — CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N |
| CWE | CWE-863 (不適切な認可) |
| OWASP | A5: アクセス制御の不備 |
| WPVDB | c3829294-c388-4151-9e25-a3eac7b1f1c6 |
| 研究者 | Suleyman Huseynov (@huseyn0vs__) |
| 公開日 | 2026-07-22 |
Simply Schedule Appointments の GET /wp-json/ssa/v1/appointments/purge REST エンドポイントは、単一の予約の所有権を証明する id_token を検証する認可チェック (get_items_permissions_check) を適用しています。しかし、ハンドラー (purge_appointments()) は、トークンがどの予約に属しているかを完全に無視し、代わりに指定された条件に一致するサイト全体のすべての予約に対して操作を行います。
認証されていない攻撃者は、公開予約フォームから任意の単一予約を予約すると id_token を受け取ります。このトークンを使用して、攻撃者は以下のことが可能です:
削除はコミット済みトランザクション内で6つの依存データベーステーブルにカスケードされ、標準の元に戻す機能はありません。
ファイル: includes/class-appointment-model.php
権限ゲートとハンドラーは、完全に異なるスコープで動作します:
// Route registration
register_rest_route( $namespace, '/' . $base . '/purge', array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'purge_appointments' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
),
) );
権限チェック — id_token を介して1つの予約の所有権を検証します:
public function get_items_permissions_check( $request ) {
// ...
if ( true === $this->id_token_permissions_check( $request ) ) {
return true; // grants access if caller proves ownership of one appointment
}
// ...
}
ハンドラー — トークンの予約を無視し、サイト全体に対して操作します:
public function purge_appointments( WP_REST_Request $request ) {
$params = $request->get_params();
// No customer_id or appointment_id scoping — deletes ALL matching appointments
if ( isset( $params['purge_past_appointments'] ) && 'true' === $params['purge_past_appointments'] ) {
$conditions[] = $wpdb->prepare( 'end_date < %s', $date_modified_max->format( 'Y-m-d' ) );
}
$sql = 'SELECT * FROM ' . $this->get_table_name()
. ' WHERE ' . implode( ' OR ', $conditions )
. ' ORDER BY id ASC LIMIT 5000';
$list = $wpdb->get_results( $sql, ARRAY_A );
// Cascade-deletes across 6 tables in committed transactions
}
id_token は、単一の予約の id と date_created から導出される HMAC-MD5 です。このトークンを保持していることは、呼び出し元が特定の1つの予約を予約したことのみを証明し、サイト全体の権限を一切付与するものではありません。
1. Attacker visits the booking page and books any free appointment
→ receives confirmation email containing: appointment_id + id_token
2. GET /wp-json/ssa/v1/appointments/purge
?id=<appointment_id>
&token=<id_token>
&purge_past_appointments=true
3. Permission check passes (valid token for attacker's own appointment)
4. Handler selects ALL past appointments site-wide → returns PII in response
→ permanently deletes them across 6 tables (on premium editions)
PoC は 2026-08-05 に公開予定です(協調開示エンバーゴに従います)。
エンバーゴ期間終了後は poc/ ディレクトリを参照してください。
| シナリオ | 結果 |
|---|---|
| データ開示 | すべての顧客の名前、メールアドレス、電話番号、予約詳細 |
| 一括削除(プレミアム) | 6つのテーブルにわたって、すべての過去・将来の予約が恒久的に破壊される |
| 復旧手段なし | 完全なデータベースバックアップの復元が必要 |
| 日付 | イベント |
|---|---|
| 2026-07-10 | 脆弱性を発見 |
| 2026-07-10 | WPScan に提出 |
| 2026-07-22 | CVE-2026-16540 割り当て |
| 2026-07-22 | WPScan で公開開示 |
| 2026-08-05 | PoC 公開 |