WordPress.org

Make WordPress Core

Ticket #37757: ticket-37757.3.patch

File ticket-37757.3.patch, 8.2 KB (added by seuser, 3 years ago)

Updated patch with filter and tests for filter.

  • src/wp-includes/functions.php

    diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php
    index 03c6126..9c5c058 100644
    a b function get_weekstartend( $mysqlstring, $start_of_week = '' ) { 
    320320/**
    321321 * Unserialize value only if it was serialized.
    322322 *
     323 * Even if options like 'allowed_classes' are used, there is no guarentee that
     324 * vulnerabilities will be avoided. Inputs should always be validated and
     325 * serialization should be avoided.
     326 *
    323327 * @since 2.0.0
     328 * @since 4.9.0 Added the `$options` parameter.
     329 *
     330 * @see https://secure.php.net/manual/en/function.unserialize.php
    324331 *
    325332 * @param string $original Maybe unserialized original, if is needed.
     333 * @param array  $options  Optional. Array of options to pass to unserialize if the PHP version is greater than 7.0.
    326334 * @return mixed Unserialized data can be any type.
    327335 */
    328 function maybe_unserialize( $original ) {
    329         if ( is_serialized( $original ) ) // don't attempt to unserialize data that wasn't serialized going in
    330                 return @unserialize( $original );
    331         return $original;
     336function maybe_unserialize( $original, $options = array() ) {
     337        // don't attempt to unserialize data that wasn't serialized going in.
     338        if ( ! is_serialized( $original ) ) {
     339                return $original;
     340        }
     341
     342        if ( defined( 'PHP_MAJOR_VERSION' ) && PHP_MAJOR_VERSION >= 7 ) {
     343
     344                /**
     345                 * Filters the unserialize options array.
     346                 *
     347                 * Allows for global filtering of all usage of maybe_unserialize
     348                 * options. Note that using options like 'allowed_classes' does not
     349                 * avoid the vulnerabilities in unserialization.
     350                 *
     351                 * @since 4.9.0
     352                 *
     353                 * @see https://secure.php.net/manual/en/function.unserialize.php
     354                 *
     355                 * @param array $options The options being passed to unserialize.
     356                 * @param string $original The contents to be unserialized.
     357                 */
     358                $options = apply_filters( 'unserialization_options', $options, $original );
     359
     360                return @unserialize( $original, $options );
     361        }
     362        return @unserialize( $original );
    332363}
    333364
    334365/**
  • new file tests/phpunit/data/classes/class-foo.php

    diff --git a/tests/phpunit/data/classes/class-foo.php b/tests/phpunit/data/classes/class-foo.php
    new file mode 100644
    index 0000000..555363f
    - +  
     1<?php
     2/**
     3 * Class types to run tests against.
     4 *
     5 * This is for instance to test unserialization results against.
     6 *
     7 * @package WordPress
     8 */
     9
     10require_once( dirname( __FILE__ ) . '/class-foobase.php' );
     11require_once( dirname( __FILE__ ) . '/class-foointerface.php' );
     12
     13/**
     14 * Foo class.
     15 */
     16class Foo extends FooBase implements FooInterface { }
  • new file tests/phpunit/data/classes/class-foobase.php

    diff --git a/tests/phpunit/data/classes/class-foobase.php b/tests/phpunit/data/classes/class-foobase.php
    new file mode 100644
    index 0000000..75c0ffc
    - +  
     1<?php
     2/**
     3 * Foo base class.
     4 *
     5 * This is for instance to test unserialization results against.
     6 *
     7 * @package WordPress
     8 */
     9
     10/**
     11 * Foo class.
     12 */
     13class FooBase { }
  • new file tests/phpunit/data/classes/class-foointerface.php

    diff --git a/tests/phpunit/data/classes/class-foointerface.php b/tests/phpunit/data/classes/class-foointerface.php
    new file mode 100644
    index 0000000..36101b5
    - +  
     1<?php
     2/**
     3 * Foo interface class.
     4 *
     5 * This is for instance to test unserialization results against.
     6 *
     7 * @package WordPress
     8 */
     9
     10/**
     11 * Foo class.
     12 */
     13interface FooInterface { }
  • new file tests/phpunit/tests/functions/class-tests-functions-maybeunserialize.php

    diff --git a/tests/phpunit/tests/functions/class-tests-functions-maybeunserialize.php b/tests/phpunit/tests/functions/class-tests-functions-maybeunserialize.php
    new file mode 100644
    index 0000000..889ac8f
    - +  
     1<?php
     2
     3require_once( ABSPATH . '../tests/phpunit/data/classes/class-foo.php' );
     4
     5/**
     6 * Test maybe_unserialize.
     7 *
     8 * @group functions.php
     9 */
     10class Tests_Functions_MaybeUnserialize extends WP_UnitTestCase {
     11
     12        /**
     13         * Object list for testing against.
     14         *
     15         * @var array
     16         */
     17        var $object_list = array();
     18
     19        /**
     20         * Array list for testing against.
     21         *
     22         * @var array
     23         */
     24        var $array_list = array();
     25
     26        /**
     27         * Setup for tests.
     28         */
     29        function setUp() {
     30                parent::setUp();
     31                $this->array_list['foo'] = array(
     32                        'name' => 'foo',
     33                        'id' => 'f',
     34                        'field1' => true,
     35                        'field2' => true,
     36                        'field3' => true,
     37                        'field4' => array(
     38                                'red',
     39                        ),
     40                );
     41                $this->array_list['bar'] = array(
     42                        'name' => 'bar',
     43                        'id' => 'b',
     44                        'field1' => true,
     45                        'field2' => true,
     46                        'field3' => false,
     47                        'field4' => array(
     48                                'green',
     49                        ),
     50                );
     51                foreach ( $this->array_list as $key => $value ) {
     52                        $this->object_list[ $key ] = (object) $value;
     53                }
     54        }
     55
     56        /**
     57         * Test unserialization default functionality.
     58         */
     59        public function test_maybe_unserialize() {
     60
     61                // Unserialization of array.
     62                $serialized_array = serialize( $this->array_list );
     63                $unseralized_array = maybe_unserialize( $serialized_array );
     64                $this->assertEquals( $unseralized_array, $this->array_list );
     65
     66                // Unserialization of object.
     67                $serialized_object = serialize( $this->object_list );
     68                $unseralized_object = maybe_unserialize( $serialized_object );
     69                $this->assertEquals( $unseralized_object, $this->object_list );
     70        }
     71
     72        /**
     73         * Test unserialization no classes allowed.
     74         *
     75         * @requires PHP 7.0
     76         */
     77        public function test_maybe_unserialize_php7_no_classes() {
     78                $foo = new Foo();
     79                $serialized_foo = serialize( $foo );
     80                $unseralized_foo = maybe_unserialize( $serialized_foo, array(
     81                        'allowed_classes' => false,
     82                ) );
     83                $this->assertEquals( get_class( $unseralized_foo ), '__PHP_Incomplete_Class' );
     84        }
     85
     86        /**
     87         * Test unserialization different classes allowed.
     88         *
     89         * @requires PHP 7.0
     90         */
     91        public function test_maybe_unserialize_php7_different_classes() {
     92                $foo = new Foo();
     93                $serialized_foo = serialize( $foo );
     94                $unseralized_foo = maybe_unserialize( $serialized_foo, array(
     95                        'allowed_classes' => array(
     96                                'bar',
     97                        ),
     98                ) );
     99                $this->assertEquals( get_class( $unseralized_foo ), '__PHP_Incomplete_Class' );
     100        }
     101
     102        /**
     103         * Test unserialization class this class implements not allowed.
     104         *
     105         * @requires PHP 7.0
     106         */
     107        public function test_maybe_unserialize_php7_implement_classes() {
     108                $foo = new Foo();
     109                $serialized_foo = serialize( $foo );
     110                $unseralized_foo = maybe_unserialize( $serialized_foo, array(
     111                        'allowed_classes' => array(
     112                                'FooInterface',
     113                        ),
     114                ) );
     115                $this->assertEquals( get_class( $unseralized_foo ), '__PHP_Incomplete_Class' );
     116        }
     117
     118        /**
     119         * Test unserialization class this class extends not allowed.
     120         *
     121         * @requires PHP 7.0
     122         */
     123        public function test_maybe_unserialize_php7_extend_classes() {
     124                $foo = new Foo();
     125                $serialized_foo = serialize( $foo );
     126                $unseralized_foo = maybe_unserialize( $serialized_foo, array(
     127                        'allowed_classes' => array(
     128                                'FooBase',
     129                        ),
     130                ) );
     131                $this->assertEquals( get_class( $unseralized_foo ), '__PHP_Incomplete_Class' );
     132        }
     133
     134        /**
     135         * Test unserialization with classes allowed.
     136         *
     137         * @requires PHP 7.0
     138         */
     139        public function test_maybe_unserialize_php7_allowed_classes() {
     140                $foo = new Foo();
     141                $serialized_foo = serialize( $foo );
     142                $unseralized_foo = maybe_unserialize( $serialized_foo, array(
     143                        'allowed_classes' => array(
     144                                'Foo',
     145                        ),
     146                ) );
     147                $this->assertEquals( get_class( $unseralized_foo ), 'Foo' );
     148        }
     149
     150        /**
     151         * Test unserialization with classes allowed.
     152         *
     153         * @requires PHP 7.0
     154         */
     155        public function test_maybe_unserialize_php7_filter_options() {
     156                $foo = new Foo();
     157                $serialized_foo = serialize( $foo );
     158                add_filter( 'unserialization_options', array( $this, 'filter__maybe_unserialize_allowed_classes' ), 10, 3 );
     159                $unseralized_foo = maybe_unserialize( $serialized_foo );
     160                $this->assertEquals( get_class( $unseralized_foo ), 'Foo' );
     161        }
     162
     163        /**
     164         * Filter callback for maybe_unserialize options.
     165         *
     166         * @param array  $options The options to be passed to the php unserialize().
     167         * @param string $original The contents to be unserialized.
     168         *
     169         * @return array The options to pass to the php unserialize().
     170         */
     171        public function filter__maybe_unserialize_allowed_classes( $options, $original ) {
     172                $options = array(
     173                        'allowed_classes' => array(
     174                                'Foo',
     175                        ),
     176                );
     177                return $options;
     178        }
     179
     180}