
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을 통해 하나의 예약에 대한 소유권을 검증합니다:
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. 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 공개 |