Post

CVE-2026-14224: Easy Appointments Subscriber+ IDOR Enables Cross-User Notification Redirection

Technical write-up for CVE-2026-14224, an IDOR in Easy Appointments that allowed a Subscriber-level user to reuse a valid appointment-edit nonce while targeting another user's appointment metadata.

CVE-2026-14224: Easy Appointments Subscriber+ IDOR Enables Cross-User Notification Redirection

CVE-2026-14224: Easy Appointments Subscriber+ IDOR Enables Cross-User Notification Redirection

Overview

Easy Appointments contained a cross-user authorization flaw in its appointment customer-data update workflow.

The vulnerable path looked protected at first glance: the request used an authenticated WordPress session and an Easy Appointments edit nonce. The problem was that the nonce was not bound to the appointment selected by the request. A Subscriber could obtain a valid edit nonce from an appointment they were allowed to access, then reuse that nonce while changing appointment_id to another user’s appointment.

Once the server accepted the substituted identifier, the workflow deleted and replaced custom metadata for the targeted appointment. In the validated scenario, the attacker replaced the victim appointment’s email metadata. When an administrator later changed the appointment status, Easy Appointments used the modified metadata and sent the customer notification to the attacker-controlled address.

The issue is tracked as CVE-2026-14224 and WPVDB ID c39226d5-1b6f-4f08-903c-7ecc67d17eb0. Public advisory metadata describes the affected range as Easy Appointments before 3.12.28.

Affected Versions

  • Product: Easy Appointments WordPress Plugin
  • Affected versions: < 3.12.28
  • Validated vulnerable version: 3.12.26
  • Fixed version: 3.12.28
  • Required access: Subscriber+
  • Vulnerability type: IDOR / Missing Authorization
  • CWE: CWE-639 / CWE-862

The Vulnerable Workflow

The interesting request was the AJAX action that updated appointment customer metadata:

1
action=ea_update_customer_data

The server accepted an appointment_id from the request. That value was security-sensitive because it selected which appointment’s metadata would be modified.

The core mismatch was:

1
2
3
Authority source: nonce from attacker-owned appointment
Target object:   victim-owned appointment_id supplied in the request
Sensitive sink:  delete and replace appointment metadata

A nonce can show that a user participated in an appointment-edit workflow. It does not, by itself, prove that the user may update every appointment ID they place in the request body.

How I Found It

I found this during a source-first review of WordPress AJAX handlers that changed application state. The pattern I was looking for was simple: a request-controlled object ID flowing into a mutation after authentication or nonce validation, but without an object-level authorization decision.

The suspicious part was that the endpoint performed a meaningful state change against appointment_id, but the authorization appeared to stop at the generic edit nonce. The next question was whether the nonce was tied to the same appointment that would later be modified.

A two-user lab confirmed the issue. The attacker used a nonce from Appointment A, then submitted a request for Appointment B. The plugin accepted the request and updated Appointment B’s metadata.

Execution Flow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Subscriber opens own appointment
        |
        v
Valid ea_edit_appointment_nonce obtained
        |
        v
ea_update_customer_data request
        |
        v
appointment_id replaced with victim appointment
        |
        v
nonce accepted
        |
        v
no ownership check for supplied appointment_id
        |
        v
victim appointment metadata replaced
        |
        v
later notification uses attacker-controlled email metadata

The issue lived in the appointment metadata boundary: the current user was authenticated, but the selected appointment was attacker-controlled request input.

Proof of Concept

The PoC used two Subscriber accounts and two appointments.

The attacker first opened the edit popup for their own appointment and extracted a valid ea_edit_appointment_nonce. The attacker then sent an AJAX request similar to the following while replacing appointment_id with the victim appointment ID:

1
2
3
4
5
6
7
8
curl -i -s -b /tmp/a.cookies -X POST \
  "$BASE/wp-admin/admin-ajax.php?action=ea_update_customer_data" \
  -d "ea_edit_appointment_nonce=$NONCE" \
  -d "appointment_id=$VICTIM_APPOINTMENT_ID" \
  -d "email=pwned-recipient@example.local" \
  -d "name=User B" \
  -d "phone=2222222222" \
  -d "description=Victim appointment"

The vulnerable response was:

1
{"success":true}

After an administrator changed the victim appointment from pending to confirmed with customer notifications enabled, the mail log showed the notification being delivered to the attacker-controlled address rather than the original customer address.

Impact

The demonstrated impact was cross-user modification of appointment customer metadata and notification redirection.

An attacker with Subscriber-level access could cause future appointment notifications for another user’s appointment to be delivered to an attacker-controlled email address. Depending on the notification template, this may disclose appointment details and interfere with customer communication integrity.

The issue does not require administrator privileges from the attacker. The administrator action in the reproduction was only the legitimate later status change that caused the application to use the attacker-modified metadata.

Patch and Defensive Takeaways

The fix should bind the authenticated user, the requested operation, and the supplied appointment ID before metadata is deleted or replaced.

A safe path should look like this:

1
2
3
4
validate nonce
    -> resolve appointment_id
        -> verify current user may edit that appointment
            -> update metadata only if authorized

Object-specific nonces can reduce confusion, but they should not replace server-side ownership checks. The endpoint still needs to prove that the current user may modify the appointment selected by appointment_id.

Research Credit

  • Researcher: Duy Tran
  • Disclosure: Responsible disclosure through the WordPress vulnerability ecosystem
  • Coordinator / advisory source: WPScan / WPVDB

References

Responsible Use Notice

This write-up documents a publicly assigned CVE in an out-of-date WordPress plugin version.

Testing should be limited to systems you own or are explicitly authorized to assess. The proof of concept is included to explain the broken authorization boundary and support defensive validation, not to encourage testing against third-party WordPress sites.

This post is licensed under CC BY 4.0 by the author.
Support my research