PHP图片上传安全方法

12

我开发了一个 PHP 脚本来替换当前的脚本,这将在各种市场/国家得到大量使用。该脚本提供了照片上传功能。

在大量阅读有关问题后,我遵循了下面描述的方法。非常感谢您对其安全性的评论。

  1. 照片上传至网站根目录之外的私有 777 文件夹。
  2. 检查白名单扩展名(仅允许 jpg、gif、png),删除其他所有文件。
  3. 使用 getimagesize 检查最小-最大尺寸和照片有效性。
  4. 检查 mimetype 和文件扩展名是否匹配。
  5. 调整上传的照片大小以适合标准尺寸 (使用 imagecopyresampled)。
  6. 保存创建的文件为 jpg。
  7. 删除原始文件。
  8. 将照片另存为新名称(不是随机名称),例如 img51244.jpg。
  9. 将新照片移动到公共文件夹(777 权限)的可变子目录中,根据不可预测的算法。例如,img10000.jpg 将存储在 photos/a/f/0/img10000.jpg 中,而 img10001.jpg 将存储在 photos/0/9/3/img10001.jpg 中。这是出于其他原因(使用子域名为静态内容提供服务或使用 CDN)。

该脚本将在 Linux 专用服务器上运行。


除了文件夹上的777权限以外,这里没有什么让我感到不安的地方-据我所知,如果它拥有777权限,那就不是私有的。但据我所知,只有在您的服务器被攻破时(在我看来,通过此脚本至少看起来不太可能)才会真正关心这一点。 - totallyNotLizards
2
777听起来不太安全。可能与https://dev59.com/pXA65IYBdhLWcg3wxBmr有关。 - ajreal
对我来说听起来很不错,除了可能过于宽松的权限。也许它们可以在某种程度上受到限制?否则,这做了我能想到的所有必要的事情,包括删除EXIF数据。 - Pekka
我将上传文件夹称为“private”,与公共文件夹(位于Web根文件夹下)相对应。 - Alex
3个回答

4
  1. 一个以chmod 0777设置的目录,按定义是公开的,对于其他登录到你的服务器的用户来说是不私密的。正确的权限应该是700,并且被apache(或者你的web服务器使用的任何用户)所拥有。我不确定为什么你不会在这里使用php默认的临时目录,因为它通常位于网站根目录之外。
  2. 白名单是个好主意。要小心正确地实现。例如,正则表达式/.png/实际上匹配了apng.php
  3. 这一步是个好主意。它基本上检查文件的魔数。
  4. 不是严格必须的。在前面两个步骤中,我们已经确定了扩展名和文件格式是正确的。如果你需要客户端指定正确的MIME类型,你还应该检查给定的MIME类型和上述确定的MIME类型是否相等。

第5到8步与安全无关。

第9步:我假设你的网站允许所有人看到每张照片。如果不是这样的话,你应该有一个URL方案,其中URL会更长得多(比如图片哈希值)。


1
-1 777在网页可访问性方面并不意味着公共访问——这就是他所说的。而且你的正则表达式示例暗示了这根本没有任何重要性……顺便说一下,exe是Windows扩展名…… :-/ - Raffael
@Raffael1984 我觉得phihag知道这一点。就“让其他用户在同一个Web服务器上访问”的方面而言,它仍然是公开的,这一个问题。但是如果您正确执行3),我同意步骤2)是不必要的。 - Pekka
@Raffael1984 确实。我不确定为什么需要那一步。已更新答案。 - phihag
@Raffael1984 在示例中更新了结尾为.php,虽然这只是一个不应该被忽略的例子。再次更新答案以澄清HTTP可用性和系统范围的公共目录。 - phihag
@Rook image 不是一个文件,而是一个目录。因此,+x 是必要的,以允许用户遍历子目录 - phihag

3

你还应该检查上传文件的大小,因为getimagesize有时可能会超出可用的RAM内存。此外,最好假设你的脚本在任何时候都可能崩溃(例如,当电力中断时),因此你应该实现一些清理程序来删除剩余的不必要的文件。


1
上传文件的最大大小已经在php.ini中限制了。 - Maerlyn
是的,但这并不意味着它足以避免超出RAM限制。 - Sebastian Nowak
Sebastian有一点道理:我可以上传一个大小为500千字节的100000 x 100000像素JPG。 - Pekka
如果你真的认为可以依赖系统管理员,我为和你一起工作的人感到抱歉。有一句古老而经典的话:“优秀的程序员在通过单行道时也会向两边看。” 是的,我们无法避免使用getimagesize函式,除非涉及外部脚本,这就是为什么我提到了清理程序。 - Sebastian Nowak
@Sebastian Nowak。脚本的默认设置是最大上传大小为5.5MB(平均10MPixel照片)和128MB的php内存限制。 - Alex
显示剩余5条评论

-1

这是一个相当完整的方法,但我没有看到任何代码执行预防机制。

您应该确保图像内容永远不会被包含(使用include或require调用)或通过eval()执行。

否则,文件末尾包含的php代码可能会被执行。

您还可以尝试检测图像内容中的php代码(使用file_get_contents,然后使用正则表达式搜索“<?php”),但我找不到一种100%安全的方法来消除可疑代码而不破坏某些(有效的)图像。


我不明白除非你的服务器配置错误,否则PHP代码怎么会在JPG文件中执行? - Pekka
这没有任何意义,因为很明显没有人会在图像上调用eval或include。我想不出任何情况下有人会考虑这样做(除非他刚开始使用PHP)。 - Sebastian Nowak
原始文件在重新采样照片出现时立即被删除。它仅按以下 PHP 函数顺序使用:move_uploaded_file、filesize、getimagesize、imagecreatefromjpeg、imagecopyresampled。 - Alex
@Sebastian Nowak:你不能假定开发人员不会犯错误。已知有攻击广泛使用的系统(如phpBB)的案例,攻击者利用上传图片(例如头像)进行代码注入。说没有人会再犯这个错误只会导致漏洞被遗忘,直到有人再次利用它。我在以下链接中找到了一些有趣的利用此类漏洞的方法(评论也很好,其中包括使用null字符进行文件名扩展名为.php的注入示例):http://ha.ckers.org/blog/20070604/passing-malicious-php-through-getimagesize/ - Benjamin Dubois

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接