将上传的图像放在公共文件夹中是否安全?


Is it secure to place uploaded images in a public folder?

我刚刚和我的队友讨论了用户上传的图片在图片库中的位置。我想对我们建议的方法有更广泛的了解。

我的队友写了一个控制器+动作,调用file_get_contents上的图像文件放在一个文件夹,不可用于公共浏览(即,在服务器上的public_html之外),并通过一个头回显它。这是安全的,但是由于我们使用的是Zend框架,所以它的爬行速度也很慢——由于执行bootstrap的查询,每次调用图像控制器都会花费我们大约500ms的延迟。这很烦人,因为图片库视图同时显示超过20张图片。

简而言之,相关代码为:

class ImageController extends Zend_Controller_Action {
    public function showAction () {
        $filename = addslashes($this->_getParam('filename'));
        if(!is_file($filename)) {
            $filename = APPLICATION_PATH.'/../public/img/nopicture.jpg';
        }
        $this->_helper->viewRenderer->setNoRender(true);
        $this->view->layout()->disableLayout();
        $img = file_get_contents($filename);
        header('Content-Type: image/jpeg');
        $modified = new Zend_Date(filemtime($filename));
        $this->getResponse()
             ->setHeader('Last-Modified',$modified->toString(Zend_Date::RFC_1123))
             ->setHeader('Content-Type', 'image/jpeg')
             ->setHeader('Expires', '', true)
             ->setHeader('Cache-Control', 'public', true)
             ->setHeader('Cache-Control', 'max-age=3800')
             ->setHeader('Pragma', '', true);
        echo $img;
    }
}

然后,在视图中,我们只需调用:

<img src="<?php echo $this->url(array('controller' => 'image', 'action' => 'show', 'filename' => PATH_TO_HIDDEN_LOCATION.'/filename.jpg')); ?>" />

我有一个不同的方法:我更喜欢保持原始图像在一个隐藏的位置,但一旦他们被要求,将它们复制到一个公共位置,并提供一个链接到它(与一个额外的机制,由cron运行,擦除公共图像目录每隔一段时间,为了不浪费空间,和一个robots.txt告诉谷歌不要索引目录)。该解决方案将文件(在每个给定时刻都有几个)放在一个可公开访问的目录中(前提是知道文件名),但也只需要一个视图帮助器,因此不会启动bootstrap:

class Zend_View_Helper_ShowImage extends Zend_View_Helper_Abstract {
    public function showImage ($filename) {
        if (!file_exists(PUBLIC_PATH."/img/{$filename}")) {
            if (!copy(PATH_TO_HIDDEN_FILES."/{$filename}",PUBLIC_PATH."/img/{$filename}"))
                $url = PUBLIC_PATH.'/img/nopicture.jpg';
            else
                $url = PUBLIC_PATH."/img/{$filename}";
        } else {
            $url = PUBLIC_PATH."/img/{$filename}"
        }
        return "{$url}";
    }
}
在这个帮助器的帮助下,视图中的调用非常简单:
<img src="<?php echo $this->showImage('filename.jpg'); ?>" />

问题:我的方法是否像我的同事所说的那样构成安全威胁?这样做的潜在风险是什么?而且,最重要的是,安全威胁(如果有的话)是否超过了页面加载时间增加的10秒?

如果它很重要:我们正在开发一个拥有大约15K注册用户的社区门户,其中画廊是一个非常常用的功能。

*我粘贴的代码是经过编辑的,简化了我们每个人的想法的版本——只是为了展示两种方法的机制。

我有一个不同的方法:我喜欢把原始图像保存在一个隐藏的位置,但一旦他们被要求,复制到一个公共位置,并提供一个链接到它

创意+1

我的方法是否像我的同事说的那样对安全构成威胁?这样做的潜在风险是什么?而且,最重要的是,安全威胁(如果有的话)是否超过了页面加载时间增加的10秒?

。是的,如果您的图像只允许某些人看到,并且您将它们放入可公开访问的目录中,那么其他人就可以看到该图像,这似乎是不希望看到的。我也不认为(可能是错误的),它将获得10秒的页面加载,因为你将不得不复制图像,这是一个相当密集的操作,比使用file_get_contents或readfile()。

这是安全的,但由于我们使用Zend框架,它也爬行缓慢-每次调用图像控制器花费我们大约500ms的延迟,由于引导的查询正在执行。

如果我可以建议;nukzend框架为这个特定的情况。我使用Zend框架为一个相当大的网站,以及,所以我知道引导可能需要比你想要的更长时间。如果您绕过Zend框架,选择普通的PHP,这将大大提高性能。

同样,使用readfile(),而不是file_get_contents()。file_get_contents将在输出之前将整个文件加载到内存中,这是一个很大的区别,而readfile在这方面做得更有效。

只要通过检查MIMEmagic bytes,甚至尝试使用GD库加载图像,确保文件确实是一个图像文件,就应该没问题。

最初的方法可能会通过使用不执行整个引导的专门引导来加速。这是加载图像所需的基本内容。

您还可以在公共文件夹中放置一个"正常"的.php文件,该文件包含一个脚本,该脚本使用GD或类似的方法从非公共文件夹打开并输出图像。只要用

这样的东西来命名它
<a href="image.php?image=foobar.jpg&width=320&height=240" />

在这里,你还必须确保你不会盲目地相信图像名称,而是实际上检查这是你的图像目录中的图像,而不是一些讨厌的东西。

我还可以建议将您的图像存储在web服务器具有写入访问非公共文件夹中,然后在公共文件夹中创建一个没有写入访问权限的符号链接和目录列表到此图像文件夹并从那里加载图像。你的web服务器可以直接为它们提供服务,而不必复制它们。

只是一些建议

如何将所有图像放在一个公共目录中,配置为不具有目录列表,并使用在数据库中跟踪的非常长且随机的文件名(例如使用md5从id和一些salt生成),并且每次访问后更改文件名。

数据库保存…文件名:‘myfile.jpg’,tempname:"uysdfnasdufhansvdufgnvasoeuvncas.jpg"

访问函数在数据库中查找tempname并插入相应的url

访问后,filename保持不变,tempname更改并将文件重命名为新的tempname

好,那么另一种方法(顺便说一下,这也是一个有点hack的)

当您进入图库页面时,您将知道需要将哪些图像发送给用户。我假设它们有一个标识符(文件名/id/其他)。生成此请求所需的所有文件的列表,并将其存储在脚本可以轻松访问的地方,例如,存储在服务器上的文本文件中(不可公开访问)。给这个文件一个id。

现在,有另一个PHP文件,它不加载完整的Zend框架。它应该从URL中获得一些参数:被请求的图像文件和列表的id。

将url写入img标记,如下所示:

<img src="image.php?file=myFile.jpg&list=12345" />

image.php文件应该打开具有该id的列表并检查myFile.jpg是否存在于其中。如果是,显示它,否则发出404。

别忘了定期清理你的旧清单。

几个星期前我就在考虑这样一个问题,最后我写了一个自己的Thumbnail ViewHelper,就像你做的那样。原始图像存储在一个非公开的目录。所以每次我调用

echo $this->thumb(array('url' => HIDDEN_DIR . 'foo/bar.jpg'));

ViewHelper将把图片复制到一个公共的可访问的缓存目录,并返回url到缓存的图片:

/_cache/thumbs/3858f62230ac3c915f300c664312c63f.jpg

另外,如果需要,脚本可以裁剪和调整图像大小。此外,我还添加了一个功能,每50次调用从缓存中清理1000个图像…

唯一要记住的是,永远不要显示带有原始图像的目录的路径,即使它不能从公共通道访问。那么,我想说它是安全的。

在启用缓存的情况下,脚本不会占用明显的时间,因此它提供了良好的用户体验。我希望你能使用我的一些想法;)

你的安全考虑是什么?你的威胁模型是什么?这些解决方案如何解决这个问题?

两个解决方案都没有解决吸血问题。这两种解决方案都无法解决恶意软件的再分发问题。这两种解决方案都不限制对内容项的访问。

你的队友的解决方案是有缺陷的,因为它不必要地将整个图像加载到PHP内存中。

您的解决方案不是很有效,因为它需要2次读取和一次写入操作来代替一次读取,并且您只是减少了无限制访问内容的机会窗口。