WordPress.org

Make WordPress Core

Ticket #40175: 40175.diff

File 40175.diff, 11.3 KB (added by blobfolio, 22 months ago)

Implements an explicit "greylist" for file types requiring additional validation, rather than scrutinizing every application/* type.

  • src/wp-includes/functions.php

    diff --git src/wp-includes/functions.php src/wp-includes/functions.php
    index ebdc12344c..73d9b6e6d1 100644
    function _wp_upload_dir( $time = null ) { 
    20172017}
    20182018
    20192019/**
     2020 * Assign a new extension to a filename.
     2021 *
     2022 * @since 4.8.1
     2023 *
     2024 * @param string $filename The original filename.
     2025 * @param string $ext      The new extension.
     2026 * @return string The renamed file.
     2027 */
     2028function wp_update_filename_extension( $filename, $ext ) {
     2029        $ext = strtolower( $ext );
     2030        $ext = rtrim( $ext, '.' );
     2031        $ext = ltrim( $ext, '.' );
     2032
     2033        $filename_parts = explode( '.', $filename );
     2034
     2035        // Remove the old extension.
     2036        if ( count( $filename_parts ) > 1 ) {
     2037                array_pop( $filename_parts );
     2038        }
     2039
     2040        // Add the new extension.
     2041        if ( strlen( $ext ) ) {
     2042                $filename_parts[] = $ext;
     2043        }
     2044
     2045        return implode( '.', $filename_parts );
     2046}
     2047
     2048/**
    20202049 * Get a filename that is sanitized and unique for the given directory.
    20212050 *
    20222051 * If the filename is not unique, then a number will be added to the filename
    function wp_check_filetype( $filename, $mimes = null ) { 
    22612290function wp_check_filetype_and_ext( $file, $filename, $mimes = null ) {
    22622291        $proper_filename = false;
    22632292
    2264         // Do basic extension validation and MIME mapping
     2293        // Do basic extension validation and MIME mapping.
    22652294        $wp_filetype = wp_check_filetype( $filename, $mimes );
    22662295        $ext = $wp_filetype['ext'];
    22672296        $type = $wp_filetype['type'];
    22682297
    2269         // We can't do any further validation without a file to work with
     2298        // We can't do any further validation without a file to work with.
    22702299        if ( ! file_exists( $file ) ) {
    22712300                return compact( 'ext', 'type', 'proper_filename' );
    22722301        }
    function wp_check_filetype_and_ext( $file, $filename, $mimes = null ) { 
    22762305        // Validate image types.
    22772306        if ( $type && 0 === strpos( $type, 'image/' ) ) {
    22782307
    2279                 // Attempt to figure out what type of image it actually is
     2308                // Attempt to figure out what type of image it actually is.
    22802309                $real_mime = wp_get_image_mime( $file );
    22812310
    22822311                if ( $real_mime && $real_mime != $type ) {
    function wp_check_filetype_and_ext( $file, $filename, $mimes = null ) { 
    22882317                         * @param  array $mime_to_ext Array of image mime types and their matching extensions.
    22892318                         */
    22902319                        $mime_to_ext = apply_filters( 'getimagesize_mimes_to_exts', array(
    2291                                 'image/jpeg' => 'jpg',
    2292                                 'image/png'  => 'png',
    2293                                 'image/gif'  => 'gif',
    2294                                 'image/bmp'  => 'bmp',
    2295                                 'image/tiff' => 'tif',
     2320                                'image/jpeg'      => 'jpg',
     2321                                'image/png'       => 'png',
     2322                                'image/gif'       => 'gif',
     2323                                'image/bmp'       => 'bmp',
     2324                                'image/x-ms-bmp'  => 'bmp',
     2325                                'image/tiff'      => 'tif',
    22962326                        ) );
    22972327
    2298                         // Replace whatever is after the last period in the filename with the correct extension
     2328                        // Rename the file with the correct extension.
    22992329                        if ( ! empty( $mime_to_ext[ $real_mime ] ) ) {
    2300                                 $filename_parts = explode( '.', $filename );
    2301                                 array_pop( $filename_parts );
    2302                                 $filename_parts[] = $mime_to_ext[ $real_mime ];
    2303                                 $new_filename = implode( '.', $filename_parts );
     2330                                $new_filename = wp_update_filename_extension( $filename, $mime_to_ext[ $real_mime ] );
    23042331
    23052332                                if ( $new_filename != $filename ) {
    2306                                         $proper_filename = $new_filename; // Mark that it changed
     2333                                        $proper_filename = $new_filename; // Mark that it changed.
    23072334                                }
    23082335                                // Redefine the extension / MIME
    23092336                                $wp_filetype = wp_check_filetype( $new_filename, $mimes );
    function wp_check_filetype_and_ext( $file, $filename, $mimes = null ) { 
    23172344        }
    23182345
    23192346        // Validate files that didn't get validated during previous checks.
    2320         if ( $type && ! $real_mime && extension_loaded( 'fileinfo' ) ) {
     2347        if (
     2348                $type &&
     2349                ! $real_mime &&
     2350                extension_loaded( 'fileinfo' ) &&
     2351                defined( 'FILEINFO_MIME_TYPE' )
     2352        ) {
    23212353                $finfo = finfo_open( FILEINFO_MIME_TYPE );
    23222354                $real_mime = finfo_file( $finfo, $file );
    23232355                finfo_close( $finfo );
    23242356
    23252357                /*
    23262358                 * If $real_mime doesn't match what we're expecting, we need to do some extra
    2327                  * vetting of application mime types to make sure this type of file is allowed.
     2359                 * vetting of greylisted mime types to make sure this type of file is allowed.
    23282360                 * Other mime types are assumed to be safe, but should be considered unverified.
    23292361                 */
    2330                 if ( $real_mime && ( $real_mime !== $type ) && ( 0 === strpos( $real_mime, 'application' ) ) ) {
    2331                         $allowed = get_allowed_mime_types();
     2362                if ( $real_mime && ( $real_mime !== $type ) ) {
     2363                        // Get the true file extension for a greylisted filetype.
     2364                        $greylist = get_greylisted_mime_types();
     2365                        $real_mime = strtolower( sanitize_mime_type( $real_mime ) );
     2366                        $real_ext = false;
     2367                        foreach ( $greylist as $exts => $greylist_types ) {
     2368                                if ( in_array( $real_mime, $greylist_types, true ) ) {
     2369                                        $real_ext = $exts;
     2370                                        break;
     2371                                }
     2372                        }
     2373
     2374                        if ( $real_ext ) {
     2375                                // This MIME type is greylisted, so make sure the extension is allowed.
     2376                                $allowed = get_allowed_mime_types();
     2377                                $found = false;
     2378                                foreach ( $allowed as $exts => $allowed_type ) {
     2379                                        $exts = explode( '|', $exts );
     2380                                        if ( in_array( $real_ext, $exts, true ) ) {
     2381                                                // Rename the file with the correct extension.
     2382                                                if ( $ext !== $real_ext ) {
     2383                                                        $new_filename = wp_update_filename_extension( $filename, $real_ext );
     2384
     2385                                                        if ( $new_filename != $filename ) {
     2386                                                                $proper_filename = $new_filename; // Mark that it changed.
     2387                                                        }
     2388
     2389                                                        $ext = $real_ext;
     2390                                                }
    23322391
    2333                         if ( ! in_array( $real_mime, $allowed ) ) {
    2334                                 $type = $ext = false;
     2392                                                // Always prefer the MIME type from get_allowed_mimes().
     2393                                                $type = $allowed_type;
     2394
     2395                                                $found = true;
     2396                                                break;
     2397                                        }
     2398                                }
     2399
     2400                                // Unauthorized file.
     2401                                if ( ! $found ) {
     2402                                        $ext = $type = false;
     2403                                }
    23352404                        }
    23362405                }
    23372406        }
    function wp_get_ext_types() { 
    25402609}
    25412610
    25422611/**
     2612 * Retrieve list of greylisted mime types and file extensions.
     2613 *
     2614 * These are file types deserving of extra validation during uploads. Unlike
     2615 * get_allowed_mime_types, each entry consists of a single file extension
     2616 * with multiple MIME types.
     2617 *
     2618 * @since 4.8.1
     2619 *
     2620 * @return array Array of mime types keyed by the file extension corresponding
     2621 *               to those types.
     2622 */
     2623function get_greylisted_mime_types() {
     2624        $greylist = array(
     2625                'air' => array(
     2626                        'application/adobe.air-application-installer-package+zip',
     2627                        'application/vnd.adobe.air-application-installer-package+zip',
     2628                        'application/x-adobe.air-application-installer-package+zip',
     2629                ),
     2630                'fla' => array(
     2631                        'application/dtg.local.flash',
     2632                        'application/vnd.dtg.local.flash',
     2633                        'application/x-dtg.local.flash',
     2634                ),
     2635                'flv' => array(
     2636                        'application/flash-video',
     2637                        'application/vnd.flash-video',
     2638                        'application/x-flash-video',
     2639                        'flv-application/octet-stream',
     2640                        'video/flv',
     2641                        'video/x-flv',
     2642                ),
     2643                'swf' => array(
     2644                        'application/adobe.flash.movie',
     2645                        'application/futuresplash',
     2646                        'application/shockwave-flash',
     2647                        'application/vnd.adobe.flash.movie',
     2648                        'application/vnd.futuresplash',
     2649                        'application/vnd.shockwave-flash',
     2650                        'application/x-adobe.flash.movie',
     2651                        'application/x-futuresplash',
     2652                        'application/x-shockwave-flash',
     2653                ),
     2654                'spl' => array(
     2655                        'application/adobe.flash.movie',
     2656                        'application/futuresplash',
     2657                        'application/shockwave-flash',
     2658                        'application/vnd.adobe.flash.movie',
     2659                        'application/vnd.futuresplash',
     2660                        'application/vnd.shockwave-flash',
     2661                        'application/x-adobe.flash.movie',
     2662                        'application/x-futuresplash',
     2663                        'application/x-shockwave-flash',
     2664                ),
     2665        );
     2666
     2667        /**
     2668         * Filters the greylist results.
     2669         *
     2670         * @since 4.8.1
     2671         *
     2672         * @param array $greylist Array of mime types keyed by the file extension corresponding
     2673         *                        to those types.
     2674         */
     2675        return apply_filters( 'get_greylisted_mime_types', $greylist );
     2676}
     2677
     2678/**
    25432679 * Retrieve list of allowed mime types and file extensions.
    25442680 *
    25452681 * @since 2.8.6
  • new file tests/phpunit/data/uploads/test.swf

    diff --git tests/phpunit/data/uploads/test.swf tests/phpunit/data/uploads/test.swf
    new file mode 100644
    index 0000000000000000000000000000000000000000..c6195c41eebe7b3b2006eff39a268320e973feac
    GIT binary patch
    literal 140
    zcmZ<@4`%OSU|^_VV2x*B;9tPNz{AL3&zuVs>d;|eVaQD_E>28OWk@bcO)N<bNv$Yx
    z%S_ElVJHEz7(7yQa`F|z^NVs)6d9&zF|Zo}wXvrF2{s@G12}`75y<CYU<V0sFxrYw
    UV*>JIrm-+^FmW*ZGdKW+0pm9pO8@`>
  • tests/phpunit/tests/functions.php

    literal 0
    HcmV?d00001
    
    diff --git tests/phpunit/tests/functions.php tests/phpunit/tests/functions.php
    index 252d890bbb..4205f8a139 100644
    class Tests_Functions extends WP_UnitTestCase { 
    122122                );
    123123        }
    124124
     125        /**
     126         * @dataProvider _wp_update_filename_extension
     127         */
     128        function test_wp_update_filename_extension( $filename, $ext, $expected ) {
     129                $this->assertEquals( $expected, wp_update_filename_extension( $filename, $ext ) );
     130        }
     131
    125132        function test_wp_unique_filename() {
    126133
    127134                $testdir = DIR_TESTDATA . '/images/';
    class Tests_Functions extends WP_UnitTestCase { 
    9941001        }
    9951002
    9961003        /**
     1004         * @ticket 39550
     1005         *
     1006         * @dataProvider _wp_check_filetype_and_ext_with_filtered_swf
     1007         */
     1008        function test_wp_check_filetype_and_ext_with_filtered_swf( $file, $filename, $expected ) {
     1009                if ( ! extension_loaded( 'fileinfo' ) ) {
     1010                        $this->markTestSkipped( 'The fileinfo PHP extension is not loaded.' );
     1011                }
     1012
     1013                if ( is_multisite() ) {
     1014                        $this->markTestSkipped( 'Test does not run in multisite' );
     1015                }
     1016
     1017                add_filter( 'upload_mimes', array( $this, '_filter_mime_types_swf' ) );
     1018                $this->assertEquals( $expected, wp_check_filetype_and_ext( $file, $filename ) );
     1019
     1020                // Cleanup.
     1021                remove_filter( 'upload_mimes', array( $this, '_test_add_mime_types_swf' ) );
     1022        }
     1023
     1024        /**
    9971025         * Data profider for test_wp_get_image_mime();
    9981026         */
    9991027        public function _wp_get_image_mime() {
    class Tests_Functions extends WP_UnitTestCase { 
    10801108                                        'proper_filename' => false,
    10811109                                ),
    10821110                        ),
     1111                        // Flash file with invalid extension.
     1112                        array(
     1113                                DIR_TESTDATA . '/uploads/test.swf',
     1114                                'big5.jpg',
     1115                                array(
     1116                                        'ext' => false,
     1117                                        'type' => false,
     1118                                        'proper_filename' => false,
     1119                                ),
     1120                        ),
     1121                        // Flash file with valid extension.
     1122                        array(
     1123                                DIR_TESTDATA . '/uploads/test.swf',
     1124                                'test.swf',
     1125                                array(
     1126                                        'ext' => false,
     1127                                        'type' => false,
     1128                                        'proper_filename' => false,
     1129                                ),
     1130                        ),
    10831131                );
    10841132
    10851133                // Test a few additional file types on single sites.
    class Tests_Functions extends WP_UnitTestCase { 
    11101158
    11111159                return $data;
    11121160        }
     1161
     1162        public function _filter_mime_types_swf( $mimes ) {
     1163                $mimes['swf'] = 'application/x-shockwave-flash';
     1164                return $mimes;
     1165        }
     1166
     1167        public function _wp_update_filename_extension() {
     1168                $data = array(
     1169                        array(
     1170                                'test.jpg',
     1171                                'png',
     1172                                'test.png',
     1173                        ),
     1174                        array(
     1175                                'test',
     1176                                'png',
     1177                                'test.png',
     1178                        ),
     1179                        array(
     1180                                'test',
     1181                                '.png',
     1182                                'test.png',
     1183                        ),
     1184                        array(
     1185                                'test.jpg',
     1186                                '',
     1187                                'test',
     1188                        ),
     1189                );
     1190
     1191                return $data;
     1192        }
     1193
     1194        public function _wp_check_filetype_and_ext_with_filtered_swf() {
     1195                $data = array(
     1196                        // Correctly named SWF.
     1197                        array(
     1198                                DIR_TESTDATA . '/uploads/test.swf',
     1199                                'test.swf',
     1200                                array(
     1201                                        'ext' => 'swf',
     1202                                        'type' => 'application/x-shockwave-flash',
     1203                                        'proper_filename' => false,
     1204                                ),
     1205                        ),
     1206                        // Incorrectly named SWF.
     1207                        array(
     1208                                DIR_TESTDATA . '/uploads/test.swf',
     1209                                'test.jpg',
     1210                                array(
     1211                                        'ext' => 'swf',
     1212                                        'type' => 'application/x-shockwave-flash',
     1213                                        'proper_filename' => 'test.swf',
     1214                                ),
     1215                        ),
     1216                );
     1217
     1218                return $data;
     1219        }
    11131220}