CVE-2017-14723: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') (CWE-89)
value: Before version 4.8.2, WordPress mishandled % characters and additional placeholder values in $wpdb->prepare, and thus did not properly address the possibility of plugins and themes enabling SQL injection attacks.
CWE description: The product constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component. Without sufficient removal or quoting of SQL syntax in user-controllable inputs, the generated SQL query can cause those inputs to be interpreted as SQL instead of ordinary user data.
CVSS v3 base score: 9.8
Published: 2017-09-23T20:29Z
Repository: https://github.com/WordPress/WordPress
Language: PHP
Files changed: wp-includes/version.php, wp-includes/wp-db.php
Commit date: 2017-09-19 17:56:45 +0000
Commit message: Database: Hardening to bring `wpdb::prepare()` inline with documentation.

`wpdb::prepare()` supports %s, %d, and %F as placeholders in the query string. Any other non-escaped % will be escaped.


Built from https://develop.svn.wordpress.org/trunk@41496


git-svn-id: http://core.svn.wordpress.org/trunk@41329 1a063a9b-81f0-0310-95a4-ce76da25c4cd

Diff: diff --git a/wp-includes/version.php b/wp-includes/version.php
index 88d62fc71be..63bf8c80ddf 100644
--- a/wp-includes/version.php
+++ b/wp-includes/version.php
@@ -2,11 +2,11 @@
 /**
  * The WordPress version string
  *
  * @global string $wp_version
  */
-$wp_version = '4.9-alpha-41483';
+$wp_version = '4.9-alpha-41496';
 
 /**
  * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.
  *
  * @global int $wp_db_version
diff --git a/wp-includes/wp-db.php b/wp-includes/wp-db.php
index c300e240f2e..9d125d008d7 100644
--- a/wp-includes/wp-db.php
+++ b/wp-includes/wp-db.php
@@ -1250,10 +1250,11 @@ class wpdb {
 
 		$query = str_replace( "'%s'", '%s', $query ); // in case someone mistakenly already singlequoted it
 		$query = str_replace( '"%s"', '%s', $query ); // doublequote unquoting
 		$query = preg_replace( '|(?<!%)%f|' , '%F', $query ); // Force floats to be locale unaware
 		$query = preg_replace( '|(?<!%)%s|', "'%s'", $query ); // quote the strings, avoiding escaped strings like %%s
+		$query = preg_replace( '/%(?:%|$|([^dsF]))/', '%%\\1', $query ); // escape any unescaped percents 
 		array_walk( $args, array( $this, 'escape_by_ref' ) );
 		return @vsprintf( $query, $args );
 	}
 
 	/**
@@ -2819,11 +2820,12 @@ class wpdb {
 							$connection_charset = mysql_client_encoding();
 						}
 					}
 
 					if ( is_array( $value['length'] ) ) {
-						$queries[ $col ] = $this->prepare( "CONVERT( LEFT( CONVERT( %s USING $charset ), %.0f ) USING $connection_charset )", $value['value'], $value['length']['length'] );
+						$length = sprintf( '%.0f', $value['length']['length'] );
+						$queries[ $col ] = $this->prepare( "CONVERT( LEFT( CONVERT( %s USING $charset ), $length ) USING $connection_charset )", $value['value'] );
 					} else if ( 'binary' !== $charset ) {
 						// If we don't have a length, there's no need to convert binary - it will always return the same result.
 						$queries[ $col ] = $this->prepare( "CONVERT( CONVERT( %s USING $charset ) USING $connection_charset )", $value['value'] );
 					}
 

Vulnerable code: $wp_version = '4.9-alpha-41483';
						$queries[ $col ] = $this->prepare( "CONVERT( LEFT( CONVERT( %s USING $charset ), %.0f ) USING $connection_charset )", $value['value'], $value['length']['length'] );
Fixed code: $wp_version = '4.9-alpha-41496';
		$query = preg_replace( '/%(?:%|$|([^dsF]))/', '%%\\1', $query ); // escape any unescaped percents 
						$length = sprintf( '%.0f', $value['length']['length'] );
						$queries[ $col ] = $this->prepare( "CONVERT( LEFT( CONVERT( %s USING $charset ), $length ) USING $connection_charset )", $value['value'] );