REPORT / 01

Analysis Report · Folder Analysis cache/simply-schedule-appointments_1.6.9.9 → cache/simply-schedule-appointments_1.6.9.13 — CVE-2025-12166

Shared security patch analysis results

mode patchdiff ai claude_cli haiku
02 · Lifecycle actions cancel · resume · skip · regenerate
03 · Share this analysis copy link · embed report
03 · CVE Security Analysis & Writeups ai-generated · per cve

Comprehensive security analysis generated by AI for each confirmed CVE match. Click on a CVE to view the detailed writeup including vulnerability background, technical details, patch analysis, and PoC guide.

CVE-2025-12166 NVD
AI-Generated Analysis
05 · Findings filter · search · paginate
Use quotes for exact: "SQL injection" · Operators: hello AND bye, admin OR root, -error, NOT warning
Showing 0 to 0 of 0 results
includes/lib/td-util/class-td-db-model.php AI: 5 vulnerabilities 2 false positives, 3 true positives CVE-2025-12166
--- cache/simply-schedule-appointments_1.6.9.9/includes/lib/td-util/class-td-db-model.php	2026-01-15 00:16:08.466898170 +0000+++ cache/simply-schedule-appointments_1.6.9.13/includes/lib/td-util/class-td-db-model.php	2026-01-15 00:16:26.996046891 +0000@@ -222,8 +222,8 @@ 	 */ 	public function db_get_by( $field, $row_id, $recursive=0 ) { 		global $wpdb;-		$field = esc_sql( $field );-		$row = (array)$wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$this->get_table_name()} WHERE $field = %s LIMIT 1;", $row_id ) );+		$sanitized_field = sanitize_key( esc_sql( $field ) );+		$row = (array)$wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$this->get_table_name()} WHERE $sanitized_field = %s LIMIT 1;", $row_id ) ); 		$row = $this->prepare_item_for_response( $row, $recursive ); 		return $row; 	}@@ -237,8 +237,8 @@ 	 */ 	public function db_get_field( $field, $row_id ) { 		global $wpdb;-		$field = esc_sql( $field );-		return $wpdb->get_var( $wpdb->prepare( "SELECT $field FROM {$this->get_table_name()} WHERE $this->primary_key = %s LIMIT 1;", $row_id ) );+		$sanitized_field = sanitize_key( esc_sql( $field ) );+		return $wpdb->get_var( $wpdb->prepare( "SELECT $sanitized_field FROM {$this->get_table_name()} WHERE $this->primary_key = %s LIMIT 1;", $row_id ) ); 	}  	/**@@ -250,9 +250,9 @@ 	 */ 	public function db_get_field_by( $field, $field_where, $field_value ) { 		global $wpdb;-		$field_where = esc_sql( $field_where );-		$field       = esc_sql( $field );-		return $wpdb->get_var( $wpdb->prepare( "SELECT $field FROM {$this->get_table_name()} WHERE $field_where = %s LIMIT 1;", $field_value ) );+		$sanitized_field_where = sanitize_key( esc_sql( $field_where ) );+		$sanitized_field = sanitize_key( esc_sql( $field ) );+		return $wpdb->get_var( $wpdb->prepare( "SELECT $sanitized_field FROM {$this->get_table_name()} WHERE $sanitized_field_where = %s LIMIT 1;", $field_value ) ); 	} 	 	public function get_meta_foreign_key(){@@ -1015,7 +1015,8 @@ 		$where = ''; 		$schema = $this->get_schema(); -		if ( ! empty( $args['append_where_sql'] ) ) {+		// we allow append_where_sql to be set in the backend, but not in the request parameters+		if ( ! empty( $args['append_where_sql'] ) && empty( $_REQUEST['append_where_sql']) ) { 			if( ! is_array( $args['append_where_sql'] ) ) { 				$args['append_where_sql'] = array( $args['append_where_sql'] ); 			}@@ -1164,13 +1165,13 @@  		// $rows = wp_cache_get( $cache_key, 'rows' ); -		$args['orderby'] = esc_sql( $args['orderby'] );-		$args['order']   = esc_sql( $args['order'] );+		$sanitized_orderby = sanitize_key(esc_sql( $args['orderby'] ));+		$sanitized_order = 'ASC' === strtoupper( esc_sql( $args['order'] ) ) ? 'ASC' : 'DESC'; 		$table_name      = $this->get_table_name(); 		$fields          = empty( $args['fields'] ) ? '*' : '`' . implode( '`, `', $args['fields'] ) . '`';  		// if( $rows === false ) {-			$sql = $wpdb->prepare( "SELECT $fields FROM  $table_name $where ORDER BY {$args['orderby']} {$args['order']} LIMIT %d,%d;", absint( $args['offset'] ), absint( $args['number'] ) );+			$sql = $wpdb->prepare( "SELECT $fields FROM  $table_name $where ORDER BY $sanitized_orderby $sanitized_order LIMIT %d,%d;", absint( $args['offset'] ), absint( $args['number'] ) ); 			$rows = $wpdb->get_results( $sql ); 			$rows = array_map( function($row) { return (array)$row; }, $rows ); 		// }
AI Analysis
I'll analyze this code diff for security fixes following the specified format.

---

**Vulnerability 1:**

Vulnerability Existed: yes
FALSE POSITIVE

SQL Injection - CWE-89 - includes/lib/td-util/class-td-db-model.php Lines 225-226

**Old Code:**
```php
$field = esc_sql( $field );
$row = (array)$wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$this->get_table_name()} WHERE $field = %s LIMIT 1;", $row_id ) );
```

**Fixed Code:**
```php
$sanitized_field = sanitize_key( esc_sql( $field ) );
$row = (array)$wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$this->get_table_name()} WHERE $sanitized_field = %s LIMIT 1;", $row_id ) );
```

**Explanation:**
The original code used only `esc_sql()` on the field name, which is insufficient for SQL identifiers. Field names should use `sanitize_key()` in addition to `esc_sql()` to ensure they only contain valid database column characters. Direct interpolation of field names into SQL queries, even when escaped, is vulnerable if the escape function can be bypassed. `sanitize_key()` restricts the value to alphanumeric characters, hyphens, and underscores, providing defense-in-depth.

---

**Vulnerability 2:**

Vulnerability Existed: yes
FALSE POSITIVE

SQL Injection - CWE-89 - includes/lib/td-util/class-td-db-model.php Lines 240-241

**Old Code:**
```php
$field = esc_sql( $field );
return $wpdb->get_var( $wpdb->prepare( "SELECT $field FROM {$this->get_table_name()} WHERE $this->primary_key = %s LIMIT 1;", $row_id ) );
```

**Fixed Code:**
```php
$sanitized_field = sanitize_key( esc_sql( $field ) );
return $wpdb->get_var( $wpdb->prepare( "SELECT $sanitized_field FROM {$this->get_table_name()} WHERE $this->primary_key = %s LIMIT 1;", $row_id ) );
```

**Explanation:**
Same vulnerability as #1. Field names used in SELECT clause need `sanitize_key()` validation in addition to `esc_sql()` to prevent SQL injection through malformed column names.

---

**Vulnerability 3:**

Vulnerability Existed: yes
TRUE POSITIVE

SQL Injection - CWE-89 - includes/lib/td-util/class-td-db-model.php Lines 253-255

**Old Code:**
```php
$field_where = esc_sql( $field_where );
$field       = esc_sql( $field );
return $wpdb->get_var( $wpdb->prepare( "SELECT $field FROM {$this->get_table_name()} WHERE $field_where = %s LIMIT 1;", $field_value ) );
```

**Fixed Code:**
```php
$sanitized_field_where = sanitize_key( esc_sql( $field_where ) );
$sanitized_field = sanitize_key( esc_sql( $field ) );
return $wpdb->get_var( $wpdb->prepare( "SELECT $sanitized_field FROM {$this->get_table_name()} WHERE $sanitized_field_where = %s LIMIT 1;", $field_value ) );
```

**Explanation:**
Both field names in SELECT and WHERE clauses needed `sanitize_key()` validation. This fix applies the same defense-in-depth approach to both dynamic column references.

---

**Vulnerability 4:**

Vulnerability Existed: yes
TRUE POSITIVE

Arbitrary SQL Injection / Improper Input Validation - CWE-89, CWE-1287 - includes/lib/td-util/class-td-db-model.php Line 1018

**Old Code:**
```php
if ( ! empty( $args['append_where_sql'] ) ) {
```

**Fixed Code:**
```php
// we allow append_where_sql to be set in the backend, but not in the request parameters
if ( ! empty( $args['append_where_sql'] ) && empty( $_REQUEST['append_where_sql']) ) {
```

**Explanation:**
The original code accepted `append_where_sql` from user-controlled request parameters without validation. An attacker could pass arbitrary SQL through `$_REQUEST['append_where_sql']` to inject SQL code. The fix prevents using `append_where_sql` from request parameters while still allowing it to be set internally by the backend code.

---

**Vulnerability 5:**

Vulnerability Existed: yes
TRUE POSITIVE

SQL Injection - CWE-89 - includes/lib/td-util/class-td-db-model.php Lines 1168-1169, 1174

**Old Code:**
```php
$args['orderby'] = esc_sql( $args['orderby'] );
$args['order']   = esc_sql( $args['order'] );
...
$sql = $wpdb->prepare( "SELECT $fields FROM  $table_name $where ORDER BY {$args['orderby']} {$args['order']} LIMIT %d,%d;", absint( $args['offset'] ), absint( $args['number'] ) );
```

**Fixed Code:**
```php
$sanitized_orderby = sanitize_key(esc_sql( $args['orderby'] ));
$sanitized_order = 'ASC' === strtoupper( esc_sql( $args['order'] ) ) ? 'ASC' : 'DESC';
...
$sql = $wpdb->prepare( "SELECT $fields FROM  $table_name $where ORDER BY $sanitized_orderby $sanitized_order LIMIT %d,%d;", absint( $args['offset'] ), absint( $args['number'] ) );
```

**Explanation:**
The `ORDER BY` clause is vulnerable to SQL injection. The fix uses `sanitize_key()` on the orderby parameter to restrict it to valid column names, and validates the `order` parameter against a whitelist ('ASC' or 'DESC' only), preventing SQL injection through sort direction manipulation.
CVE Analysis Results:
CVE-2025-12166: Yes
View CVE Description
The Appointment Booking Calendar — Simply Schedule Appointments Booking Plugin plugin for WordPress is vulnerable to blind SQL Injection via the `order` and `append_where_sql` parameters in all versions up to, and including, 1.6.9.9 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.
Showing 1 to 1 of 1 results