大家好,欢迎来到今天的“文件类型识别大冒险”讲座!我是你们的向导,准备好一起探索WordPress神秘的 wp_check_filetype()
函数,看看它如何像福尔摩斯一样,通过文件头来判断文件类型,而不是简单地看一眼扩展名。
开场白:扩展名的伪装舞会
想象一下,你在参加一个化装舞会,每个人都穿着奇装异服。有人穿着牛仔服,你以为他是牛仔,结果他掏出了一把激光枪,原来他是星际牛仔!扩展名就像这些服装,可以随意改变,但文件头就像人的DNA,很难伪造。
所以,仅仅依靠扩展名来判断文件类型是很危险的。恶意用户可以把一个邪恶的PHP脚本伪装成无害的image.jpg
,然后你的网站就可能被攻陷。
正题:wp_check_filetype()
的解剖
wp_check_filetype()
函数是 WordPress 用于检查文件类型的核心函数之一。它藏身在 wp-includes/functions.php
文件中。让我们深入看看它的源码,一步步揭开它的神秘面纱。
/**
* Retrieve file type based on extension name.
*
* @since 2.0.0
*
* @param string $file Full path to the file.
* @param string $mimes Optional. Array of mime types keyed by the file extension regex corresponding to those types.
* If not provided, {@see get_allowed_mime_types()} will be used.
* @return array An array of information about the file.
* array(
* 'ext' => string File extension determined from filename.
* 'type' => string File mime type.
* )
*/
function wp_check_filetype( $file, $mimes = null ) {
$defaults = array(
'ext' => false,
'type' => false,
);
if ( empty( $file ) ) {
return $defaults;
}
$type = false;
$ext = false;
$file_parts = pathinfo( $file );
if ( isset( $file_parts['extension'] ) ) {
$ext = strtolower( $file_parts['extension'] );
}
if ( empty( $mimes ) ) {
$mimes = get_allowed_mime_types();
}
if ( isset( $mimes[ $ext ] ) ) {
$type = $mimes[ $ext ];
}
return compact( 'ext', 'type' );
}
这个函数首先获取文件扩展名,然后在一个允许的 MIME 类型数组($mimes
)中查找对应的 MIME 类型。如果找到了,它就返回扩展名和 MIME 类型。
但是,请注意,这个函数 仅仅根据扩展名来判断文件类型。 这就是我们之前说的,扩展名可以伪装,所以这并不是最安全的方法。
更安全的姿势:wp_check_filetype_and_ext()
WordPress 提供了一个更强大的函数:wp_check_filetype_and_ext()
。 让我们看看它的源码:
/**
* Retrieve file type and optionally filename extension information.
*
* Determines file type using a number of checks.
*
* @since 3.0.0
*
* @param string $file Full path to the file.
* @param string $mimes Optional. Array of mime types keyed by the file extension regex corresponding to those types.
* If not provided, {@see get_allowed_mime_types()} will be used.
* @return array An array of information about the file.
* array(
* 'ext' => string|false File extension determined from filename.
* 'type' => string|false File mime type.
* 'proper_filename' => string|false Sanitized filename, if supplied filename was inadequate.
* )
*/
function wp_check_filetype_and_ext( $file, $mimes = null ) {
$proper_filename = false;
$real_mime = false;
// We're going to look through the extension check first,
// because if wp_check_filetype() says it's bad, then we don't
// need to run fileinfo.
$filetype = wp_check_filetype( $file, $mimes );
$ext = $filetype['ext'];
$type = $filetype['type'];
// If wp_check_filetype() says the file is invalid, stop processing.
if ( ! $type ) {
return array(
'ext' => false,
'type' => false,
'proper_filename' => false,
);
}
// Use MIME type from wp_check_filetype() to look for
// it in the list of allowed MIME types.
$allowed_types = get_allowed_mime_types();
if ( ! in_array( $type, $allowed_types, true ) ) {
return array(
'ext' => false,
'type' => false,
'proper_filename' => false,
);
}
/**
* Filter the flags used by finfo_open() to detect file types.
*
* @since 3.0.0
*
* @param int $flags A bitmask of `FILEINFO` constants.
*/
$finfo_flags = apply_filters( 'wp_check_filetype_and_ext_flags', FILEINFO_MIME_TYPE | FILEINFO_MIME_ENCODING );
/**
* Filter the location of the MIME database used by finfo_open().
*
* @since 3.0.0
*
* @param string|null $mime_db_path Path to the MIME database, or null to use the system default.
*/
$finfo_db = apply_filters( 'wp_check_filetype_and_ext_mime_db_path', null );
if ( function_exists( 'finfo_open' ) ) {
$finfo = finfo_open( $finfo_flags, $finfo_db );
if ( $finfo ) {
$real_mime = finfo_file( $finfo, $file );
finfo_close( $finfo );
}
} elseif ( function_exists( 'mime_content_type' ) && @ini_get( 'safe_mode' ) == '' ) {
$real_mime = mime_content_type( $file );
}
// If $real_mime does not match the extension, process the file name.
if ( $real_mime && $real_mime != $type ) {
/*
* Check for file names like 'example.jpeg.png' where the earlier extension
* is incorrect. We need to remove the incorrect extension.
*/
$mime_parts = explode( '/', $real_mime );
$mime_type = $mime_parts[0];
$possible_extensions = get_allowed_mime_types();
$found_extension = false;
foreach ( $possible_extensions as $extension => $possible_type ) {
if ( strpos( $possible_type, $mime_type . '/' ) === 0 ) {
$found_extension = $extension;
break;
}
}
if ( $found_extension ) {
// Only replace the extension if there's one to replace.
if ( ! empty( $ext ) ) {
$proper_filename = str_replace( ".$ext", ".$found_extension", $file );
} else {
$proper_filename = $file . ".$found_extension";
}
$file = $proper_filename;
// Reset $ext and $type to match the corrected filename.
$ext = $found_extension;
$type = $real_mime;
} else {
// The MIME type does not match any allowed extension, so this is an invalid file.
return array(
'ext' => false,
'type' => false,
'proper_filename' => false,
);
}
}
// Finally, return the findings.
return array(
'ext' => $ext,
'type' => $type,
'proper_filename' => $proper_filename,
);
}
这个函数做了以下事情:
- 先用
wp_check_filetype()
检查扩展名:这算是一个初步的检查,快速过滤掉一些明显不符合规则的文件。 - 使用
finfo_open()
或mime_content_type()
获取真实 MIME 类型:finfo_open()
是一个更强大的函数,它通过读取文件头来判断文件类型。如果finfo_open()
不可用,它会尝试使用mime_content_type()
。 - 比较扩展名和真实 MIME 类型:如果扩展名和真实 MIME 类型不一致,它会尝试根据真实 MIME 类型找到一个合适的扩展名,并修改文件名。如果找不到合适的扩展名,它会认为文件无效。
文件头的魔法:finfo_open()
和 mime_content_type()
finfo_open()
函数利用了 fileinfo
扩展,它通过读取文件的前几个字节(文件头)来判断文件类型。不同的文件类型有不同的文件头。 例如:
- JPEG 文件通常以
FF D8 FF
开头。 - PNG 文件通常以
89 50 4E 47 0D 0A 1A 0A
开头。 - GIF 文件通常以
47 49 46 38 37 61
或47 49 46 38 39 61
开头。
mime_content_type()
函数的功能类似,但它可能没有 finfo_open()
准确,而且在某些环境中可能被禁用。
用代码说话:一个简单的例子
假设我们有一个文件 evil.php.jpg
,它的内容是一个简单的 PHP 脚本。
<?php
echo "Hacked!";
?>
如果我们用 wp_check_filetype()
检查它,它会认为这是一个 JPEG 文件,因为它的扩展名是 .jpg
。
$file = 'evil.php.jpg';
$filetype = wp_check_filetype( $file );
echo "扩展名: " . $filetype['ext'] . "n";
echo "MIME 类型: " . $filetype['type'] . "n";
//输出:
//扩展名: jpg
//MIME 类型: image/jpeg
但是,如果我们用 wp_check_filetype_and_ext()
检查它,它会发现文件头不是 JPEG 的文件头,因此会认为文件无效。
$file = 'evil.php.jpg';
$filetype = wp_check_filetype_and_ext( $file );
echo "扩展名: " . $filetype['ext'] . "n";
echo "MIME 类型: " . $filetype['type'] . "n";
echo "正确文件名: " . $filetype['proper_filename'] . "n";
//输出:
//扩展名:
//MIME 类型:
//正确文件名:
或者,如果开启了PHP的 fileinfo
扩展,它可能会识别出文件是 PHP 脚本,并尝试找到一个合适的扩展名。 但是在默认配置下,WordPress通常不会允许上传PHP脚本,所以这个文件仍然会被拒绝。
get_allowed_mime_types()
:MIME 类型的白名单
get_allowed_mime_types()
函数返回一个允许上传的 MIME 类型数组。这个数组定义了哪些文件类型是被允许的。
/**
* Returns the allowed mime types for file uploads.
*
* @since 2.0.0
*
* @param int|WP_User|null $user Optional. User to check against, defaults to the current user.
* @return array Array of allowed mime types keyed by their file extension regex.
*/
function get_allowed_mime_types( $user = null ) {
$t = wp_get_mime_types();
/**
* Filters the list of allowed mime types for file uploads.
*
* @since 2.0.0
*
* @param array $mime_types Mime types keyed by the file extension regex corresponding to those types.
* @param int|WP_User|null $user User to check against, defaults to the current user.
*/
return apply_filters( 'upload_mimes', $t, $user );
}
默认情况下,这个数组包含一些常见的 MIME 类型,如 image/jpeg
、image/png
、application/pdf
等。你可以使用 upload_mimes
过滤器来修改这个数组,添加或删除允许的 MIME 类型。
表格总结:函数对比
函数 | 作用 | 安全性 | 依赖 |
---|---|---|---|
wp_check_filetype() |
根据扩展名判断文件类型 | 低 | 无 |
wp_check_filetype_and_ext() |
根据扩展名和文件头判断文件类型 | 高 | fileinfo 扩展(推荐)或 mime_content_type() 函数 |
get_allowed_mime_types() |
返回允许上传的 MIME 类型数组 | N/A | 无 |
安全建议:最佳实践
- 始终使用
wp_check_filetype_and_ext()
函数: 这是更安全的选择,因为它会检查文件头。 - 严格控制允许上传的 MIME 类型: 只允许上传你需要的 MIME 类型,并定期审查
get_allowed_mime_types()
函数返回的数组。 - 禁用不必要的 PHP 函数: 如果你不需要
mime_content_type()
函数,可以禁用它,以减少潜在的安全风险。 - 使用安全的文件存储目录: 将上传的文件存储在一个不能执行 PHP 脚本的目录中。
- 对上传的文件进行安全扫描: 使用杀毒软件或安全扫描工具对上传的文件进行扫描,以检测潜在的恶意代码。
- 不要相信用户输入: 永远不要相信用户上传的文件名或任何其他用户提供的数据。
- 定期更新 WordPress 和插件: 保持 WordPress 和插件的最新版本,以修复已知的安全漏洞。
高级话题:自定义文件类型检测
如果你需要支持 WordPress 默认不支持的文件类型,你可以使用 upload_mimes
过滤器来添加新的 MIME 类型。你还可以编写自己的文件类型检测函数,并将其集成到 WordPress 中。
例如,假设你要支持上传 .svg
文件:
function my_custom_mime_types( $mimes ) {
$mimes['svg'] = 'image/svg+xml';
return $mimes;
}
add_filter( 'upload_mimes', 'my_custom_mime_types' );
这段代码会将 image/svg+xml
MIME 类型添加到允许上传的 MIME 类型列表中,并将其与 .svg
扩展名关联起来。
真实案例:WordPress 插件漏洞
很多 WordPress 插件都存在文件上传漏洞,因为它们没有正确地验证文件类型。攻击者可以利用这些漏洞上传恶意文件,从而控制整个网站。
一个典型的案例是,插件使用 wp_check_filetype()
函数来验证文件类型,但没有检查文件头。攻击者可以上传一个伪装成图像的 PHP 脚本,然后通过直接访问该脚本来执行恶意代码。
结束语:安全之路,永无止境
文件类型识别是网站安全的重要组成部分。通过深入理解 wp_check_filetype()
和 wp_check_filetype_and_ext()
函数的源码,我们可以更好地保护我们的网站免受恶意攻击。
记住,安全之路,永无止境。我们需要不断学习新的安全技术,并保持警惕,才能确保我们的网站安全。
感谢大家的参与!希望这次“文件类型识别大冒险”能帮助大家更好地理解 WordPress 的文件类型检测机制,并提高网站的安全性。 下次再见!