Making WordPress.org

Changeset 10177


Ignore:
Timestamp:
08/19/2020 12:17:39 AM (6 years ago)
Author:
dd32
Message:

Theme Directory: API: Be far more strict and defensive with inputs.

Someone out there is sending a lot of requests to the API with junk data causing PHP warnings.

This validates input and rejects the junk.

Location:
sites/trunk/wordpress.org/public_html/wp-content/plugins/theme-directory
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/theme-directory/class-themes-api.php

    r9935 r10177  
    7979        private $cache_life = 600; // 10 minutes.
    8080
     81        /**
     82         * Flag the input as having been malformed.
     83         *
     84         * @var bool
     85         */
     86        public $bad_input = false;
    8187
    8288        /**
     
    8894        public function __construct( $action = '', $request = array() ) {
    8995                $this->request = (object) $request;
     96
     97                // Filter out bad inputs.
     98                $scalar_only_fields = [
     99                        'author',
     100                        'browse',
     101                        'locale',
     102                        'per_page',
     103                        'slug',
     104                        'search',
     105                        'theme',
     106                        'wp_version',
     107                ];
     108                foreach ( $scalar_only_fields as $field ) {
     109                        if ( isset( $this->request->$field ) && ! is_scalar( $this->request->$field ) ) {
     110                                unset( $this->request->$field );
     111                                $this->bad_input = true;
     112                        }
     113                }
     114
     115                $array_of_string_fields = [
     116                        'fields',
     117                        'slugs',
     118                        'tag',
     119                ];
     120                foreach ( $array_of_string_fields as $field ) {
     121                        if ( isset( $this->request->$field ) ) {
     122                                $this->request->$field = $this->array_of_strings( $this->request->$field );
     123
     124                                // If the resulting field is invalid, ignore it entirely.
     125                                if ( ! $this->request->$field ) {
     126                                        unset( $this->request->$field );
     127                                        $this->bad_input = true;
     128                                }
     129                        }
     130                }
    90131
    91132                // The locale we should use is specified by the request
     
    113154         */
    114155        function filter_locale( $locale ) {
    115                 return isset( $this->request->locale ) ? $this->request->locale : $locale;
     156                if ( ! empty( $this->request->locale ) ) {
     157                        $locale = (string) $this->request->locale;
     158                }
     159
     160                return $locale;
    116161        }
    117162
     
    135180                } elseif ( 'php' === $format ) {
    136181                        return serialize( $response );
     182                } elseif ( 'api_object' === $format ) {
     183                        return $this;
    137184                } else { // 'raw' === $format, or anything else.
    138185                        return $response;
     
    167214
    168215                if ( ! empty( $this->request->number ) ) {
    169                         $this->response = array_slice( $this->response, 0, $this->request->number );
     216                        $this->response = array_slice( $this->response, 0, (int) $this->request->number );
    170217                }
    171218        }
     
    237284                // The 1.2+ api expects a `wp_version` field to be sent and does not use the UA.
    238285                if ( defined( 'THEMES_API_VERSION' ) && THEMES_API_VERSION >= 1.2 ) {
    239                         if ( isset( $this->request->wp_version ) ) {
    240                                 $wp_version = $this->request->wp_version;
     286                        if ( ! empty( $this->request->wp_version ) ) {
     287                                $wp_version = (string) $this->request->wp_version;
    241288                        }
    242289                } elseif ( preg_match( '|WordPress/([^;]+)|', $_SERVER['HTTP_USER_AGENT'], $matches ) ) {
     
    325372                }
    326373
    327                 $slugs = is_array( $this->request->slugs ) ? $this->request->slugs : explode( ',', $this->request->slugs );
    328                 $slugs = array_unique( array_map( 'trim', $slugs ) );
     374                $slugs = (array) $this->request->slugs;
    329375
    330376                if ( count( $slugs ) > 100 ) {
     
    357403
    358404                // Theme slug to identify theme.
    359                 if ( empty( $this->request->slug ) ) {
     405                if ( empty( $this->request->slug ) || ! trim( $this->request->slug ) ) {
    360406                        $this->response = (object) array( 'error' => 'Slug not provided' );
    361407                        return;
    362408                }
     409
     410                $this->request->slug = trim( $this->request->slug );
    363411
    364412                // Set which fields wanted by default:
     
    382430                }
    383431
    384                 if ( empty( $this->request->fields ) ) {
    385                         $this->request->fields = array();
    386                 }
     432                $this->request->fields = (array) $this->request->fields ?? [];
     433
    387434                $this->fields = array_merge( $this->fields, $defaults, (array) $this->request->fields );
    388435
     
    448495                }
    449496
    450                 if ( isset( $this->request->fields ) ) {
    451                         $this->fields = array_merge( $this->fields, $defaults, (array) $this->request->fields );
    452                 } else {
    453                         $this->fields = array_merge( $this->fields, $defaults );
    454                 }
     497                $this->request->fields = (array) $this->request->fields ?? [];
     498
     499                $this->fields = array_merge( $this->fields, $defaults, $this->request->fields );
    455500
    456501                // If there is a cached result, return that.
     
    461506
    462507                $this->result = $this->perform_wp_query();
    463 
    464                 if ( empty( $this->request->fields ) ) {
    465                         $this->request->fields = array();
    466                 }
    467508
    468509                // Basic information about the request.
     
    496537                }
    497538                if ( isset( $this->request->per_page ) ) {
    498                         $this->query['posts_per_page'] = min( $this->request->per_page, 999 );
     539                        $this->query['posts_per_page'] = min( (int) $this->request->per_page, 999 );
    499540                }
    500541
    501542                // Views
    502543                if ( ! empty( $this->request->browse ) ) {
    503                         $this->query['browse'] = $this->request->browse;
     544                        $this->query['browse'] = (string) $this->request->browse;
    504545
    505546                        if ( 'featured' == $this->request->browse ) {
    506547                                $this->cache_life = HOUR_IN_SECONDS;
    507548                        } elseif ( 'favorites' == $this->request->browse ) {
    508                                 $this->query['favorites_user'] = $this->request->user;
     549                                $this->query['favorites_user'] = (string) $this->request->user;
    509550                        }
    510551
     
    513554                // Tags
    514555                if ( ! empty( $this->request->tag ) ) {
    515                         if ( ! is_array( $this->request->tag ) ) {
    516                                 $this->request->tag = explode( ',', $this->request->tag );
    517                         }
    518                         $this->request->tag = array_unique( $this->request->tag );
     556                        $this->request->tag = (array) $this->request->tag;
    519557
    520558                        // Replace updated tags.
     
    541579                // Search
    542580                if ( ! empty( $this->request->search ) ) {
    543                         $this->query['s'] = $this->request->search;
     581                        $this->query['s'] = (string) $this->request->search;
    544582                }
    545583
    546584                // Direct theme
    547585                if ( ! empty( $this->request->theme ) ) {
    548                         $this->query['name'] = $this->request->theme;
     586                        $this->query['name'] = (string) $this->request->theme;
    549587
    550588                        add_filter( 'parse_query', array( $this, 'direct_theme_query' ) );
     
    553591                // Author
    554592                if ( ! empty( $this->request->author ) ) {
    555                         $this->query['author_name'] = $this->request->author;
     593                        $this->query['author_name'] = (string) $this->request->author;
    556594                }
    557595
     
    868906                return $description;
    869907        }
     908
     909        /**
     910         * Helper method to return an array of trimmed strings.
     911         */
     912        protected function array_of_strings( $input ) {
     913                if ( is_string( $input ) ) {
     914                        $input = explode( ',', $input );
     915                }
     916
     917                if ( ! $input || ! is_array( $input ) ) {
     918                        return [];
     919                }
     920
     921                foreach ( $input as $k => $v ) {
     922                        if ( ! is_scalar( $v ) ) {
     923                                unset( $input[ $k ] );
     924                        } elseif ( is_string( $v ) ) {
     925                                // Don't affect non-strings such as int's and bools.
     926                                $input[ $k ] = trim( $v );
     927                        }
     928                }
     929
     930                return array_unique( $input );
     931        }
    870932}
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/theme-directory/theme-directory.php

    r10084 r10177  
    857857 * @param $method string The Method being called. Valid values: 'query_themes', 'theme_information', 'hot_tags', 'feature_list', and 'get_commercial_shops'
    858858 * @param $args   array  The arguements for the call.
    859  * @param $format string The format to return the data in. Valid values: 'json', 'php', 'raw' (default)
     859 * @param $format string The format to return the data in. Valid values: 'json', 'php', 'api_object', 'raw' (default)
    860860 */
    861861function wporg_themes_query_api( $method, $args = array(), $format = 'raw' ) {
Note: See TracChangeset for help on using the changeset viewer.