The Exploit
An unauthenticated attacker can inject arbitrary SQL into the WP Booking Calendar booking form by supplying a malformed CSV date string in the calendar_request_params[dates_ddmmyy_csv] parameter. Below is a live HTTP request:
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: target.local
Content-Type: application/x-www-form-urlencoded
action=wpbc_create_booking&calendar_request_params%5Bdates_ddmmyy_csv%5D=01.01.2024%2C02.01.2024%27%20UNION%20SELECT%20user_login%2Cuser_pass%20FROM%20wp_users%23&nonce=&_wpnonce_create_booking=
The attacker needs no authentication; the booking endpoint is accessible to anonymous users. When the payload lands, the application concatenates the unsanitized CSV string directly into a prepared statement without first validating that the value contains only valid dates. The attacker observes either a direct SQL error in the response (if WP_DEBUG is enabled) or successful exfiltration of user credentials via a UNION-based injection that the plugin then echoes back to the client in the availability response.
What the Patch Did
Before:
'dates_ddmmyy_csv' => array( 'validate' => 'strong', 'default' => '' ),
The plugin declared the dates_ddmmyy_csv parameter with a generic 'strong' validator, which performs only basic character filtering and does not understand date format semantics.
After:
'dates_ddmmyy_csv' => array( 'validate' => 'csv_dates', 'default' => '' ),
function wpbc_sanitize_csv_dates( $value ) {
if ( '' === $value ) { return $value; }
$value = str_replace( ';', ',', $value );
$array_of_nums = explode( ',', $value );
$result = array();
foreach ( $array_of_nums as $single_date ) {
$single_date = trim( $single_date );
$date_ymd = wpbc_sanitize_date( $single_date );
if ( '' !== $date_ymd ) {
$result[] = $date_ymd;
} else {
$date_dmy = wpbc_sanitize_date_dmy( $single_date );
if ( '' !== $date_dmy ) {
$result[] = $date_dmy;
}
}
}
$result = implode( ',', $result );
return $result;
}
The patch introduces a specialized whitelist validator csv_dates that parses the incoming string as a comma-separated list, validates each element against a strict date format (YYYY-MM-DD via wpbc_sanitize_date() or DD.MM.YYYY via wpbc_sanitize_date_dmy()), and discards any token that does not match. Only valid dates are reassembled and returned; SQL metacharacters, quotes, and other injection payloads are stripped by rejecting the entire malformed element. This is input whitelisting enforced at the validation layer, not output escaping.
Root Cause
CWE-20: Improper Input Validation
The dates_ddmmyy_csv parameter is user-supplied, enters the request handler in includes/_request/wpbc_request.php via the calendar_request_params array, and reaches the booking creation sink in includes/_capacity/create_booking.php without format validation. The old validator 'strong' performs only basic sanitization (removal of certain dangerous characters) rather than structural validation (confirming the value is a valid comma-separated date list). The plugin then passes this partially cleaned string into an SQL query context where remaining SQL metacharacters (single quotes, dashes, parentheses) can alter query semantics. Trust boundary: the HTTP request boundary. The attacker-controlled parameter crosses into the application's SQL generation logic without proof that it contains only date literals.
Why It Works
The load-bearing line is the 'validate' => 'csv_dates' declaration in the parameter configuration. This single change instructs the request handler to apply the new wpbc_sanitize_csv_dates() function before the value reaches any SQL query builder. The function works because it implements reject-by-default semantics: it does not attempt to escape or quote the input; it instead parses the CSV, tests each element against a known-good regex or format checker (via the two wpbc_sanitize_date*() calls), and returns only the valid dates. Any token that fails validation is discarded entirely, not escaped and passed through. The helper functions (wpbc_sanitize_date() and wpbc_sanitize_date_dmy()) are not shown in the patch but are clearly referenced in the new code; they are almost certainly simple regex validators that return an empty string on mismatch. Without the validator change, no amount of query parameterization or escaping downstream would have prevented the injection, because the input layer accepts the malicious string as-is. The patch prevents the injection at the point of ingestion, not at the point of query construction.
Hardening Checklist
-
Whitelist all structured request parameters at ingress: Do not rely on generic validators like
'strong'. Create specialized validators for dates, UUIDs, enum values, and other structured types. Use the WordPress Sanitize/Validate API (sanitize_text_field(), custom regex matchers) or a schema validation library (JSON Schema, custom validation classes) to enforce format before the parameter enters business logic. -
Reject by default in custom validators: When writing a validator function, return an empty string or throw an exception on format mismatch. Do not attempt to "fix" malformed input. This prevents confusion about what data has been seen by the application and ensures bad data never reaches a SQL query builder.
-
Apply parameterized queries (prepared statements) as defense-in-depth: Even with input validation in place, use
$wpdb->prepare()for all SQL queries. Validation alone is not sufficient; queries should be immune to injection via prepared statement binding (e.g.,$wpdb->prepare( 'SELECT * FROM table WHERE date_col IN ( %s )', implode( ',', $dates ) )— note the%splaceholder and separate bind argument). -
Audit existing request handlers for validation coverage: Search the plugin codebase for all request parameters declared in validation arrays. Ensure each parameter has a corresponding case statement in the sanitization switch (
wpbc_sanitize_csv_dates, etc.). A missing case is an unvalidated parameter. -
Log and alert on validation failures: When a parameter fails validation, log the failure with the raw input, request user ID, and timestamp. This helps detect attack attempts at scale and can trigger WAF rules or account suspension workflows.
References
- https://nvd.nist.gov/vuln/detail/CVE-2024-1207