[wp-trac] [WordPress Trac] #52506: Add escaping method for table names in SQL queries

WordPress Trac noreply at wordpress.org
Fri Feb 12 05:34:55 UTC 2021


#52506: Add escaping method for table names in SQL queries
--------------------------+-----------------------------
 Reporter:  tellyworth    |      Owner:  (none)
     Type:  defect (bug)  |     Status:  new
 Priority:  normal        |  Milestone:  Awaiting Review
Component:  Database      |    Version:
 Severity:  normal        |   Keywords:  dev-feedback
  Focuses:                |
--------------------------+-----------------------------
 WordPress does not currently provide an explicit method for escaping SQL
 table names. This leads to potential security vulnerabilities, and makes
 reviewing code for security unnecessarily difficult.

 Core tables are of course implicitly escaped when referenced in queries
 like `SELECT * FROM $wpdb->posts`. That’s fine.

 When plugins create or reference custom table names, there are no API
 methods and no guidance as to how they should safely escape those names.
 Since `$wpdb->prepare()` surrounds `%s` references with `'` quotes in a
 way that is incompatible with MySQL table name syntax, it becomes
 necessary to use bare PHP variables in the first parameter of `prepare()`,
 which is inherently risky. Plugins use a variety of ways of mitigating
 that risk, including none at all. These are examples paraphrased from real
 code in popular plugins:

 {{{
 $wpdb->query( $wpdb->prepare( "SELECT * FROM my_table_name WHERE …" ) );
 // Literal string

 $wpdb->query( $wpdb->prepare( "SELECT * FROM $my_table_name WHERE …" ) );
 // Variable, which may or may not be assigned a literal or escaped in some
 way

 $wpdb->query( $wpdb->prepare( "SELECT * FROM $this->table_name WHERE …" )
 ); // Object property

 $wpdb->query( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}my_table_name
 WHERE …" ) ); // Variable prefix with literal string

 $wpdb->query( $wpdb->prepare( "SELECT * FROM {$this->get_table_name()}
 WHERE …" ) ); // Interpolated method call

 $wpdb->query( $wpdb->prepare( "SELECT * FROM " . sanitize_key( $table_name
 ) . " WHERE …" ) ); // Attempted escaping

 $wpdb->query( $wpdb->prepare( "SELECT * FROM " . preg_replace( '/\W/', '',
 $table_name ) . " WHERE …" ) ); // Hand-rolled escaping

 $wpdb->query( $wpdb->prepare( "SELECT * FROM " . self::TABLE_NAME . "
 WHERE …" ) ); // Class constant
 }}}

 Some variations use backticks around table names. And of course there are
 many other examples using similar queries without `prepare()` statements.
 Note that no guidance is given to plugin developers, so every one has had
 to separately decide how to handle this.

 Other than the literal string examples, none of these examples are
 verifiably secure. They might be fine, but it is impossible to be sure
 without reviewing the code path leading up to that point. Even in the case
 of a constant, it is possible that the constant might in some
 circumstances be initialized with data from an insecure source. In cases
 that make use of OO, it is possible that the table name is initialized in
 a parent class in a different file or module. It might also be overridden
 in child classes.

 All of this makes it difficult for a human code reviewer to be certain
 that a given query is secure, even though it uses `prepare()` statements.
 Static code analysis is similarly difficult. Developers who make use of
 wpcs and similar tools inevitably need to exclude their queries from
 sniffer rules because they will otherwise cause false positive errors.

 The solution would be for WordPress to provide an explicit method for
 safely and defensively escaping table names, as close as possible to the
 query itself.

 I can think of several options:

 An `esc_sql_table_name()` function, to be used like this:

 {{{
 $wpdb->query( $wpdb->prepare( "SELECT * FROM " . esc_sql_table_name(
 $my_table_name ) . " WHERE …" ) );
 }}}

 A special formatting character supported by `prepare()`, something like:

 {{{
 $wpdb->query( $wpdb->prepare( "SELECT * FROM %z WHERE …", $my_table_name )
 );
 }}}

 A modification to the `%s` character format that does not add `'` quotes
 when surrounded with backticks:

 {{{
 $wpdb->query( $wpdb->prepare( "SELECT * FROM `%s` WHERE …", $my_table_name
 ) );
 }}}

 A way to safely add sanitized table names to the `$wpdb` object:
 {{{
 $wpdb->add_table_name( 'my_table_name' );
 $wpdb->query( $wpdb->prepare( "SELECT * FROM $wpdb->my_table_name WHERE …"
 ) );
 }}}

 This last option does look attractive, but unfortunately doesn’t help with
 the use case where the table name is necessarily variable such as in a
 loop, which is somewhat common in things like backup plugins:

 {{{
 foreach ( get_list_of_table_names() as $table_name ) {
         $wpdb->query( $wpdb->prepare( "SELECT COUNT(*) FROM $table_name
 WHERE …" ) );
 }
 }}}

 I would therefore tend to favour both adding an `esc_sql_table_name()`
 function, and also supporting one of the special `prepare()` formatting
 options (which would of course make use of `esc_sql_table_name()` behind
 the scenes).

 Note that there is some obvious overlap with the very similar problem of
 escaping ''column'' names. I think that warrants a separate ticket, but
 the solution is probably similar as for table names.

-- 
Ticket URL: <https://core.trac.wordpress.org/ticket/52506>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform


More information about the wp-trac mailing list