Making WordPress.org

Changeset 10177


Ignore:
Timestamp:
08/19/2020 12:17:39 AM (5 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.