From 06f2b3f5bcca56275b18ae194d981f2741a9aaeb Mon Sep 17 00:00:00 2001 From: Jonathan Desrosiers Date: Tue, 18 Dec 2018 16:34:17 +0000 Subject: [PATCH] Media: Improve verification of MIME file types. Merges [43988] to trunk. git-svn-id: https://develop.svn.wordpress.org/trunk@44292 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/functions.php | 50 ++++++++++++++++++++++++++----- tests/phpunit/tests/functions.php | 24 +++++++++++++-- 2 files changed, 64 insertions(+), 10 deletions(-) diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index f3070315f6..fdaa2e13e1 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -2545,17 +2545,51 @@ function wp_check_filetype_and_ext( $file, $filename, $mimes = null ) { $real_mime = finfo_file( $finfo, $file ); finfo_close( $finfo ); - /* - * If $real_mime doesn't match what we're expecting, we need to do some extra - * vetting of application mime types to make sure this type of file is allowed. - * Other mime types are assumed to be safe, but should be considered unverified. - */ - if ( $real_mime && ( $real_mime !== $type ) && ( 0 === strpos( $real_mime, 'application' ) ) ) { - $allowed = get_allowed_mime_types(); + // fileinfo often misidentifies obscure files as one of these types + $nonspecific_types = array( + 'application/octet-stream', + 'application/encrypted', + 'application/CDFV2-encrypted', + 'application/zip', + ); - if ( ! in_array( $real_mime, $allowed ) ) { + /* + * If $real_mime doesn't match the content type we're expecting from the file's extension, + * we need to do some additional vetting. Media types and those listed in $nonspecific_types are + * allowed some leeway, but anything else must exactly match the real content type. + */ + if ( in_array( $real_mime, $nonspecific_types, true ) ) { + // File is a non-specific binary type. That's ok if it's a type that generally tends to be binary. + if ( ! in_array( substr( $type, 0, strcspn( $type, '/' ) ), array( 'application', 'video', 'audio' ) ) ) { $type = $ext = false; } + } elseif ( 0 === strpos( $real_mime, 'video/' ) || 0 === strpos( $real_mime, 'audio/' ) ) { + /* + * For these types, only the major type must match the real value. + * This means that common mismatches are forgiven: application/vnd.apple.numbers is often misidentified as application/zip, + * and some media files are commonly named with the wrong extension (.mov instead of .mp4) + */ + + if ( substr( $real_mime, 0, strcspn( $real_mime, '/' ) ) !== substr( $type, 0, strcspn( $type, '/' ) ) ) { + $type = $ext = false; + } + } else { + if ( $type !== $real_mime ) { + /* + * Everything else including image/* and application/*: + * If the real content type doesn't match the file extension, assume it's dangerous. + */ + $type = $ext = false; + } + } + } + + // The mime type must be allowed + if ( $type ) { + $allowed = get_allowed_mime_types(); + + if ( ! in_array( $type, $allowed ) ) { + $type = $ext = false; } } diff --git a/tests/phpunit/tests/functions.php b/tests/phpunit/tests/functions.php index 2557f1f92a..f76c342c2f 100644 --- a/tests/phpunit/tests/functions.php +++ b/tests/phpunit/tests/functions.php @@ -1301,8 +1301,8 @@ class Tests_Functions extends WP_UnitTestCase { DIR_TESTDATA . '/formatting/big5.txt', 'big5.jpg', array( - 'ext' => 'jpg', - 'type' => 'image/jpeg', + 'ext' => false, + 'type' => false, 'proper_filename' => false, ), ), @@ -1316,6 +1316,26 @@ class Tests_Functions extends WP_UnitTestCase { 'proper_filename' => false, ), ), + // Non-image file not allowed even if it's named like one. + array( + DIR_TESTDATA . '/export/crazy-cdata.xml', + 'crazy-cdata.jpg', + array( + 'ext' => false, + 'type' => false, + 'proper_filename' => false, + ), + ), + // Non-image file not allowed if it's named like something else. + array( + DIR_TESTDATA . '/export/crazy-cdata.xml', + 'crazy-cdata.doc', + array( + 'ext' => false, + 'type' => false, + 'proper_filename' => false, + ), + ), ); // Test a few additional file types on single sites.