深入理解 `wp_check_filetype()` 函数的源码,它如何通过文件内容而不是扩展名来判断文件类型,防止文件上传漏洞?

观众朋友们,早上好!我是你们今天的讲师,江湖人称“代码老中医”。今天咱们不把脉,专攻WordPress的“体检科”,聊聊wp_check_filetype()这个函数,看看它如何像孙悟空一样,练就火眼金睛,识别那些试图蒙混过关的“妖魔鬼怪”文件。

开场白:文件上传的“妖魔鬼怪”

在Web应用的世界里,文件上传就像一扇门,方便用户上传头像、文档、图片等等。但同时,也给黑客打开了一扇后门。他们会伪装恶意脚本成图片,或者把病毒藏在看似无害的文件里,一旦上传成功,轻则网站瘫痪,重则服务器被控制。

wp_check_filetype():WordPress的“体检医生”

WordPress为了保护自己,设置了一道防线,那就是wp_check_filetype()函数。它就像个经验丰富的“体检医生”,负责检查上传文件的“身份信息”,确保上传的文件真的是它声称的类型。

第一节课:wp_check_filetype()函数的基本用法

首先,咱们来认识一下wp_check_filetype()函数的基本用法。它主要接收三个参数:

  1. $filename: 要检查的文件名(包含路径)。
  2. $mimes: (可选)允许的MIME类型数组。
  3. $allowed_files: (可选)允许的文件扩展名数组,已弃用。

它会返回一个数组,包含以下三个元素:

  • ext: 文件的扩展名 (小写)。
  • type: 文件的MIME类型。
  • proper_filename: 更正后的文件名,如果需要的话。

代码示例:

$file = 'path/to/my/image.jpg';
$filetype = wp_check_filetype( $file );

echo "扩展名: " . $filetype['ext'] . "<br>";
echo "MIME类型: " . $filetype['type'] . "<br>";

第二节课:wp_check_filetype()的“体检”流程

wp_check_filetype()的“体检”流程可以概括为以下几步:

  1. 提取扩展名: 首先,它会从文件名中提取扩展名。
  2. MIME类型映射: 然后,它会根据扩展名,查找预定义的MIME类型映射表,获取对应的MIME类型。
  3. 根据文件内容判断: 这是最关键的一步! 为了防止黑客伪造扩展名,它会尝试读取文件内容,并使用PHP的wp_get_mime_types()函数及wp_check_for_uploads_error()函数,以及mime_content_type() (如果可用)或者finfo_open()函数(如果安装了Fileinfo扩展)来更准确地判断MIME类型。
  4. 返回结果: 最后,它会返回包含扩展名和MIME类型的数组。

第三节课:火眼金睛的秘密:基于文件内容的判断

咱们重点说说wp_check_filetype()如何通过文件内容来判断文件类型。这才是它真正的“杀手锏”。

  • MIME类型映射表: WordPress维护着一个MIME类型映射表,将常见的扩展名和MIME类型关联起来。这个映射表可以在wp-includes/functions.php文件中找到,通过get_allowed_mime_types()进行过滤,允许插件进行修改。

    代码示例:

    function get_allowed_mime_types() {
        $mime_types = array(
            'jpg|jpeg|jpe' => 'image/jpeg',
            'gif'          => 'image/gif',
            'png'          => 'image/png',
            'bmp'          => 'image/bmp',
            'tiff|tif'     => 'image/tiff',
            'ico'          => 'image/x-icon',
            'asf|asx|wax|wmv|wmx' => 'video/asf',
            'avi'          => 'video/avi',
            'divx'         => 'video/divx',
            'mov|qt'       => 'video/quicktime',
            'mpeg|mpg|mpe' => 'video/mpeg',
            'mp4|m4v'      => 'video/mp4',
            'ogv'          => 'video/ogg',
            'webm'         => 'video/webm',
            'mkv'          => 'video/x-matroska',
            '3gp|3gpp'     => 'video/3gpp', // Can also be audio
            '3g2|3gp2'     => 'video/3gpp2', // Can also be audio
            'txt|asc|c|cc|h' => 'text/plain',
            'csv'          => 'text/csv',
            'rtx'          => 'text/richtext',
            'css'          => 'text/css',
            'htm|html'     => 'text/html',
            'vtt'          => 'text/vtt',
            'dfxp'         => 'application/ttaf+xml',
            'mp3|m4a|m4b'  => 'audio/mpeg',
            'mpga'         => 'audio/mpeg',
            'oga|ogg|spx'  => 'audio/ogg',
            'wav'          => 'audio/wav',
            'wma'          => 'audio/x-ms-wma',
            'mid|midi'     => 'audio/midi',
            'js'           => 'application/javascript',
            'pdf'          => 'application/pdf',
            'swf'          => 'application/x-shockwave-flash',
            'class'        => 'application/java',
            'tar'          => 'application/x-tar',
            'zip'          => 'application/zip',
            'gz|gzip'      => 'application/x-gzip',
            'rar'          => 'application/rar',
            '7z'           => 'application/x-7z-compressed',
            'exe'          => 'application/x-msdownload',
            'psd'          => 'image/vnd.adobe.photoshop',
            'ai'           => 'application/postscript',
            'xla|xls|xlt|xlw' => 'application/vnd.ms-excel',
            'xlsx'         => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            'doc|dot'      => 'application/msword',
            'docx'         => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
            'pptx'         => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
            'ppd'          => 'application/vnd.cups-ppd',
            'odp'          => 'application/vnd.oasis.opendocument.presentation',
            'ods'          => 'application/vnd.oasis.opendocument.spreadsheet',
            'odt'          => 'application/vnd.oasis.opendocument.text',
            'rtf'          => 'application/rtf',
            'wp|wpd'       => 'application/wordperfect',
            'key|keynote'  => 'application/vnd.apple.keynote',
            'numbers'      => 'application/vnd.apple.numbers',
            'pages'        => 'application/vnd.apple.pages',
        );
    
        /**
         * Filters the list of allowed mime types.
         *
         * @since 2.0.0
         *
         * @param string[] $mime_types Key is the file extension with the dot (`.`) removed, value is the mime type.
         */
        return apply_filters( 'upload_mimes', $mime_types );
    }
  • mime_content_type()函数(已弃用,但仍可能存在): 这个函数尝试根据文件的魔术字节(magic bytes)来判断文件类型。魔术字节是文件开头几个字节,它们是特定文件类型的标识。例如,JPEG文件的开头通常是FF D8 FF。 但是,mime_content_type() 函数在某些服务器配置下可能不可靠,甚至可能被禁用,因为它依赖于服务器上的 magic.mime 文件,而且容易受到攻击。

  • Fileinfo扩展: 如果服务器安装了Fileinfo扩展,wp_check_filetype()会使用它来判断文件类型。Fileinfo扩展提供了更准确和安全的文件类型检测方法。它也是基于魔术字节的,但它的实现更健壮,并且不会依赖于外部的 magic.mime 文件。

    代码示例:

    if ( function_exists( 'finfo_open' ) ) {
        $finfo = finfo_open( FILEINFO_MIME_TYPE );
        $mime  = finfo_file( $finfo, $file, FILEINFO_MIME_TYPE );
        finfo_close( $finfo );
    }
  • 自定义魔术字节检测(在WordPress核心中没有直接实现,但插件可以扩展): 虽然WordPress核心没有提供完整的自定义魔术字节检测,但插件开发者可以通过upload_mimes过滤器,以及自定义函数来扩展wp_check_filetype()的功能,添加对特定文件类型的支持,甚至可以自定义魔术字节检测逻辑。

第四节课:实战演练:破解文件上传漏洞

现在,咱们来模拟一个黑客攻击场景,看看wp_check_filetype()是如何发挥作用的。

场景: 黑客想要上传一个包含恶意PHP代码的文件,但是他知道WordPress会检查文件类型。于是,他把文件命名为evil.jpg,试图蒙混过关。

黑客的“障眼法”:

  1. 伪造扩展名: 黑客将恶意PHP代码保存为evil.jpg
  2. 修改文件头(不一定能成功): 有些黑客会尝试在文件开头添加一些JPEG的魔术字节,例如FF D8 FF,试图让mime_content_type()或Fileinfo扩展误判。

wp_check_filetype()的“反击”:

  1. 提取扩展名: wp_check_filetype()首先提取到扩展名.jpg
  2. MIME类型映射: 它会查表,发现.jpg对应的MIME类型是image/jpeg
  3. 文件内容判断: 关键来了!wp_check_filetype()会读取evil.jpg的内容,并使用Fileinfo扩展(假设服务器安装了)或者mime_content_type()函数来判断真正的MIME类型。如果文件内容不是有效的JPEG图像数据,Fileinfo扩展会返回text/x-php或其他表示PHP代码的MIME类型。
  4. 判断失败: wp_check_filetype()发现实际的MIME类型与扩展名对应的MIME类型不一致,因此可以判断这是一个伪造的文件。
  5. 阻止上传: WordPress会阻止上传,或者给出警告。

代码示例(简化版):

function custom_file_upload_handler( $file ) {
    $filetype = wp_check_filetype( $file['tmp_name'], null );

    if ( $filetype['ext'] == 'jpg' && $filetype['type'] != 'image/jpeg' ) {
        $file['error'] = '上传的文件不是有效的JPEG图像。';
        return $file;
    }

    return $file;
}
add_filter( 'wp_handle_upload_prefilter', 'custom_file_upload_handler' );

第五节课:wp_check_filetype()的局限性与安全建议

虽然wp_check_filetype()很强大,但它并非万无一失。

  • 魔术字节伪造: 一些高级黑客可能会精心构造文件,在文件开头添加合法的魔术字节,然后在后面隐藏恶意代码。
  • MIME类型绕过: 某些MIME类型可能存在安全风险,例如image/svg+xml,它可以包含JavaScript代码。
  • 依赖服务器配置: mime_content_type()函数和Fileinfo扩展的可用性取决于服务器配置。

安全建议:

  1. 不要仅仅依赖wp_check_filetype() 它只是第一道防线。
  2. 验证文件内容: 对上传的图片进行二次验证,例如使用GD库或ImageMagick库重新生成图片。
  3. 限制上传文件类型: 只允许上传必要的文件类型。
  4. 重命名上传文件: 避免黑客通过文件名执行恶意代码。
  5. 将上传文件存储在非Web可访问目录: 防止直接访问上传的文件。
  6. 禁用可执行文件的上传: 绝对不要允许上传.php.exe等可执行文件。
  7. 及时更新WordPress和插件: 修复已知的安全漏洞。
  8. 配置正确的服务器MIME类型: 确保服务器返回正确的MIME类型。
  9. 使用Web应用防火墙(WAF): WAF可以检测和阻止恶意请求。

第六节课:拓展与自定义

wp_check_filetype()函数可以通过过滤器进行扩展和自定义,以满足特定的需求。

  • upload_mimes过滤器: 允许修改允许上传的MIME类型。

    代码示例:

    function custom_upload_mimes( $existing_mimes = array() ) {
        $existing_mimes['svg'] = 'image/svg+xml'; // 允许上传SVG文件
        return $existing_mimes;
    }
    add_filter( 'upload_mimes', 'custom_upload_mimes' );
  • wp_check_filetype_args过滤器: 允许修改传递给wp_check_filetype()的参数。

    代码示例:

    function custom_check_filetype_args( $args, $file, $filename ) {
        // 可以根据文件名或其他条件修改参数
        return $args;
    }
    add_filter( 'wp_check_filetype_args', 'custom_check_filetype_args', 10, 3 );
  • 自定义文件类型检测函数: 可以通过钩子函数,在wp_check_filetype()之后执行自定义的文件类型检测逻辑。

总结:

wp_check_filetype()是WordPress文件上传安全的重要组成部分。它通过扩展名和文件内容双重验证,有效地防止了恶意文件的上传。但是,它并非完美无缺,需要结合其他安全措施,才能构建一个更安全的Web应用。

记住,安全是一个持续的过程,没有一劳永逸的解决方案。我们需要不断学习和更新知识,才能应对日益复杂的网络安全威胁。

今天的课程就到这里,感谢大家的收听!希望大家以后在开发WordPress网站的时候,能够更加重视文件上传安全,保护好自己的“家园”。

下课!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注