#3168 closed defect (bug) (fixed)
Fix unescaped literal in $wpdb->prepare() call
| Reported by: |
|
Owned by: |
|
|---|---|---|---|
| Milestone: | 2.6 | Priority: | normal |
| Severity: | normal | Version: | trunk |
| Component: | API - Importers | Keywords: | has-patch commit |
| Cc: | johnjamesjacoby, jrf |
Description
As per the method documentation:
Literals (%) as parts of the query must be properly written as %%.
Attachments (1)
Change History (10)
#1
@
9 years ago
- Keywords commit added
- Milestone changed from Awaiting Review to 2.6
- Owner set to johnjamesjacoby
#3
@
9 years ago
@johnjamesjacoby I've looked at the fix now committed, AFAICS the original issue I reported is not addressed. At the same time, looking at the code of the wpdb methods involved, this may well be a shortcoming in the WP Core documentation instead of something that needs to be fixed here.
The wpdb->prepare() documentation states that a literal % need to be escaped to %%, however, it doesn't mention how to handle the %'s in LIKE statements and a quick test shows that the query here should work correctly the way it is now, which goes against the expectations created by the WP documentation (and I doubt it would work the same if the % was used at the start of an arbitrary phrase passed to LIKE).
Refs:
https://developer.wordpress.org/reference/classes/wpdb/esc_like/
https://developer.wordpress.org/reference/classes/wpdb/prepare/
#4
@
9 years ago
I had a really nice, in-depth reply crafted for you here, and Trac ate it and I couldn't recover it. :/
Long story short, I'm going to do a deeper dive into whether %% is actually correct or not, before considering this resolved.
WordPress core literally does not use the %% literal handler anywhere, even for LIKE queries. It might just be a carryover from wp_sprintf() which also is only circumstantially used.
#5
@
9 years ago
@johnjamesjacoby Sorry you lost the in-depth reply. I really appreciate that you're willing to deep dive into this.
Based on the hackedy test I ran your current code is correct (one % in this query), but the inconsistency with the documentation is curious and grating and may well be the cause of more issues in the wider WP plugin landscape, so it would be great to get more clarity about this.
I came across this query through a new sniff I created for WPCS for which I did a test run on bbPress - see https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/pull/1160 .
I largely based the sniff code on the documentation (inspired by the issues with the unsupported placeholders since the release of 4.8.2).
I will adjust the sniff behaviour based on your findings as I wouldn't like to be the cause of more confusion about this :-), so will also try and hold off the release of the next version of WPCS until there is clarity.
#6
@
9 years ago
Sounds good. And thank you for your diligence! Always nice to have a partner in crime. :)
#7
@
9 years ago
wp:changeset:41660 was committed today:
-
trunk/src/wp-includes/wp-db.php
a b 1164 1164 if ( func_num_args() === 1 && function_exists( '_deprecated_function' ) ) 1165 1165 _deprecated_function( __METHOD__, '3.6.0', 'wpdb::prepare() or esc_sql()' ); 1166 1166 if ( is_array( $data ) ) { 1167 1167 foreach ( $data as $k => $v ) { 1168 1168 if ( is_array( $v ) ) 1169 1169 $data[$k] = $this->escape( $v, 'recursive' ); 1170 1170 else 1171 1171 $data[$k] = $this->_weak_escape( $v, 'internal' ); 1172 1172 } 1173 1173 } else { 1174 1174 $data = $this->_weak_escape( $data, 'internal' ); 1175 1175 } 1176 1176 1177 1177 return $data; 1178 1178 } 1179 1179 1180 1180 /** 1181 1181 * Escapes content by reference for insertion into the database, for security 1182 1182 * 1183 1183 * @uses wpdb::_real_escape() 1184 1184 * 1185 1185 * @since 2.3.0 1186 1186 * 1187 1187 * @param string $string to escape 1188 1188 */ 1189 1189 public function escape_by_ref( &$string ) { 1190 1190 if ( ! is_float( $string ) ) 1191 1191 $string = $this->_real_escape( $string ); 1192 1192 } 1193 1193 1194 1194 /** 1195 1195 * Prepares a SQL query for safe execution. Uses sprintf()-like syntax. 1196 1196 * 1197 1197 * The following placeholders can be used in the query string: 1198 1198 * %d (integer) 1199 1199 * %f (float) 1200 1200 * %s (string) 1201 1201 * 1202 1202 * All placeholders MUST be left unquoted in the query string. A corresponding argument MUST be passed for each placeholder. 1203 1203 * 1204 * Literal percentage signs (%) in the query string must be written as %%. Percentage wildcards (for example, to use in LIKE syntax) 1205 * must be passed in the string argument, it cannot be inserted in the query string. 1204 * Literal percentage signs (%) in the query string must be written as %%. Percentage wildcards (for example, 1205 * to use in LIKE syntax) must be passed via a substitution argument containing the complete LIKE string, these 1206 * cannot be inserted directly in the query string. Also see {@see esc_like()}. 1206 1207 * 1207 1208 * This method DOES NOT support sign, padding, alignment, width or precision specifiers. 1208 1209 * This method DOES NOT support argument numbering or swapping. 1209 1210 * 1210 1211 * Arguments may be passed as individual arguments to the method, or as a single array containing all arguments. A combination 1211 1212 * of the two is not supported. 1212 1213 * 1213 1214 * Examples: 1214 1215 * $wpdb->prepare( "SELECT * FROM `table` WHERE `column` = %s AND `field` = %d OR `other_field` LIKE %s", array( 'foo', 1337, '%bar' ) ); 1215 1216 * $wpdb->prepare( "SELECT DATE_FORMAT(`field`, '%%c') FROM `table` WHERE `column` = %s", 'foo' ); 1216 1217 * 1217 1218 * @link https://secure.php.net/sprintf Description of syntax. 1218 1219 * @since 2.3.0 1219 1220 * 1220 1221 * @param string $query Query statement with sprintf()-like placeholders 1221 1222 * @param array|mixed $args The array of variables to substitute into the query's placeholders if being called with an array of arguments, 1222 1223 * or the first variable to substitute into the query's placeholders if being called with individual arguments. 1223 1224 * @param mixed $args,... further variables to substitute into the query's placeholders if being called wih individual arguments. 1224 1225 * @return string|void Sanitized query string, if there is a query to prepare. 1225 1226 */ 1226 1227 public function prepare( $query, $args ) { 1227 1228 if ( is_null( $query ) ) 1228 1229 return; 1229 1230 1230 1231 // This is not meant to be foolproof -- but it will catch obviously incorrect usage. 1231 1232 if ( strpos( $query, '%' ) === false ) { 1232 1233 _doing_it_wrong( 'wpdb::prepare', sprintf( __( 'The query argument of %s must have a placeholder.' ), 'wpdb::prepare()' ), '3.9.0' ); 1233 1234 } 1234 1235 1235 1236 $args = func_get_args(); 1236 1237 array_shift( $args ); 1237 1238 1238 1239 // If args were passed as an array (as in vsprintf), move them up 1239 1240 if ( is_array( $args[0] ) && count( $args ) == 1 ) { 1240 1241 $args = $args[0]; 1241 1242 } 1242 1243 1243 1244 foreach ( $args as $arg ) { 1244 1245 if ( ! is_scalar( $arg ) && ! is_null( $arg ) ) { 1245 1246 _doing_it_wrong( 'wpdb::prepare', sprintf( __( 'Unsupported value type (%s).' ), gettype( $arg ) ), '4.8.2' );
In 6715: