在这篇文章中,我想描述我的思考过程,从写PHP脚本以将图像文件上传到我的网站。
使用PHP保存上传的文件非常容易,有关新文件的所有信息都在SuperGlobal Array $ _FILES
中提供现在,我想确保我的脚本安全使用,以便例如另一个经过身份验证的用户可以将文件上传到网站,而不必担心恶意用户将恶意文件放置在服务器中,以完成什么恶意目标。
我开始感到害怕的TBH,因为当用户将文件发送到Web服务器时,我对您作为Web开发人员的控制很少感到震惊。实际上,我了解到您甚至无法阻止用户向Web服务器发送文件。,这取决于:如何验证上传的文件?
我经过了很多思考,得出的结论是,您可以使用限制来为您的网站提供安全性。我限制上传文件的属性越多,我想获得的安全性越多,我想。
让我们用图像文件做到这一点。
所以在我的PHP脚本中,我抓住了有关图像文件的所有信息,只是上传并根据我的设定限制进行检查。
目的是确保图像文件是声称的。
1.文件大小
$maxFileSize = 1024 * 1024 * 10; // Max. 10 MB
$maxImgSize = 4000; // Max. 4000px (for width and height)
我会承认,我宁愿不限制太多,我相信今天的每个智能手机都会以至少 2000px和3-4 mbs拍摄图像,并且可以播放。
2.文件扩展和MIME/媒体类型
我只想上传JPG,PNG,GIF,BMP和WebP图像,我认为这覆盖了足够的地面。
在此数组中,我将文件扩展程序及其各自的MIME/媒体类型一起设置。
$imgWhiteList = array("jpg" => "image/jpeg",
"jpeg" => "image/jpeg",
"gif" => "image/gif",
"bmp" => "image/bmp",
"png" => "image/png",
"webp" => "image/webp");
现在,我使用以下功能从上传文件的名称中获取文件扩展名。如果给定的文件扩展名未列入白色,则此功能返回false。因此,调用此功能也是一个验证步骤。该代码是自我解释的:
function getFileExtension($name):string|false
{
// split file name by dots
$arr = explode('.', strval($name));
// last array element has to be the file extension
$ext = array_pop($arr);
$ext = mb_strtolower(strval($ext));
// Return file extension string if whitelisted
if(array_key_exists($ext, $GLOBALS["imgWhiteList"])) {
return $ext;
}
return FALSE;
}
if(!$ext = getFileExtension($_FILES["file"]["name"])) {
die("Invalid file type");
}
// $ext is now your file extension
// Check the mime type like this:
if($imgWhiteList[$ext] != mime_content_type($_FILES["file"]["tmp_name"]))
{
die("Invalid media type");
}
但是我真的需要检查Mime媒体类型吗?
此时,我的研究开始加剧,我开始了解您可以检测到文件的扩展和媒体类型,
但是仍然可以操纵数据。此外,php函数mime_content_type()在您的php安装中使用“ magic.mime”文件来确定文件。
我不知道那是什么。
官方的PHP文档和下面的顶级评论使我有些启发:
https://www.php.net/manual/en/function.mime-content-type
,但我仍然不确定这种技术的真正可靠性。
但是我不放弃,即使我必须使用魔术!
,我也想拥有一个安全可靠的上传脚本!3.魔术字节
因此,当我不懈地搜索诸如“帮助如何防止病毒档案php上传”之类的东西时,我最终遇到了通常被称为魔术数字或魔术字节的东西。似乎二进制文件都包含一种签名。文件的“真实”类型在其前几个字节内可读取。现在,此签名的长度在不同的文件类型之间有所不同。但幸运的是,我们生活在知识时代,而维基百科则提供了这一点:
https://en.wikipedia.org/wiki/List_of_file_signatures
现在要快,我制定了一个计划:
- 从Wikipedia获取所需文件类型的签名信息
- 读取Uploaed文件的前几个字节,并根据Wikipedia的签名进行检查
- 编写执行此操作的布尔函数,并最终使用它们来检查文件是否声称是。
这是GIF图像的示例。根据Wikipedia的说法,GIF映像文件必须从6个字节的字节签名开始:
Either "47 49 46 38 37 61" (GIF87a)
or "47 49 46 38 39 61" (GIF89a)
所以技巧很简单,对于GIF文件读取前六个字节,并检查它们是否是上述值之一。
function magicBytesGIF($file):bool
{
if(!$handle = fopen($file, 'r')) return FALSE;
if(!$readBytes = fread($handle, 6)) return FALSE;
$readBytes = mb_strtoupper(bin2hex($readBytes));
if($readBytes === "474946383761"
OR $readBytes === "474946383961") {
return TRUE;
}
return FALSE;
}
繁荣。一个可靠的布尔功能,可以检查上传的image.gif是否真的是gif而不是 -
我什至不知道我要保护自己的东西,我只是不希望我的网站被黑客入侵。
哦,我的上帝阅读网络安全可以使男人偏执!
无论如何。
这是JPG的另一个示例,现在JPG文件可以具有5个不同的字节签名,如下:
1. "FF D8 FF DB" (4 bytes)
2. "FF D8 FF E0 00 10 4A 46 49 46 00 01" (12 bytes)
3. "FF D8 FF EE" (4 bytes)
4. "FF D8 FF E1 ?? ?? 45 78 69 66 00 00" (12 bytes)
5. "FF D8 FF E0" (4 bytes)
因此,我需要读取4个字节和12个字节,然后检查读取字节是否与上面的5个匹配。
在变体4中,问号意味着它可以是该位置的任何值。不要问我为什么,我觉得自己想破译古代著作。但是我不会害怕,这是用简单的正则解决的。
function magicBytesJPG($file):bool
{
if(!$handle = fopen($file, 'r')) return FALSE;
if(!$readBytes12 = fread($handle, 12)
OR !$readBytes4 = fread($handle, 4)) {
return FALSE;
}
fclose($handle);
$readBytes12 = mb_strtoupper(bin2hex($readBytes12));
$readBytes4 = mb_strtoupper(bin2hex($readBytes4));
// It must be one of these:
if($readBytes4 == "FFD8FFDB" OR $readBytes4 == "FFD8FFEE"
OR $readBytes4 == "FFD8FFE0"
OR $readBytes12 == "FFD8FFE000104A4649460001"
OR preg_match("/FFD8FFE1[A-F0-9]{4}457869660000/", $readBytes12)) {
return TRUE;
}
return FALSE;
}
现在有人可能会问,为什么我需要一次阅读4个字节和12个字节?一开始,我刚刚读取了前20个字节或每个文件的内容,无论哪种类型并试图将值与已知签名进行比较。
但是我得到的结果同样出乎意料且令人困惑。
事实证明,字节值(或至少六十进制翻译)会根据您读取的字节数量而变化。
我不知道为什么,工程师现在可能会嘲笑我,但是我还在学习!
我们完成之前的最后一件事:位图是相当简单的。
function magicBytesBMP($file):bool
{
if(!$handle = fopen($file, 'r')) return FALSE;
if(!$readBytes = fread($handle, 2)) return FALSE;
// file signature bitmap "42 4D" (2 Bytes always)
if(mb_strtoupper(bin2hex($readBytes)) == "424D") {
return TRUE;
}
return FALSE;
}
现在,我学习了一种新技术,如何更好地控制正在上传到服务器的文件。
我很好奇别人的想法,魔术字节有用吗?
您现在可能会想,我到底读了什么?这是一个教程,演示或详细的问题吗?
我会告诉你,这是我的第一篇博客文章。希望你喜欢。