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

时间:2011-07-08 09:59:37

标签: php image security zend-framework

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

我的队友写了一个控制器+操作,在一个图像文件上调用file_get_contents,该文件放在一个不可用于公共浏览的文件夹中(即服务器上的public_html之外),并通过一个回显它头。这是安全的,但由于我们使用Zend Framework,它也会慢速爬行 - 由于执行引导程序的查询,每次调用图像控制器都会花费大约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告诉谷歌不要索引目录)。该解决方案将文件(每个给定时刻的一些文件)放在一个可公开访问的目录中(如果知道文件名),但也只需要一个视图助手,因此不会启动引导程序:

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秒增益?

如果重要:我们正在建立一个拥有大约15,000注册用户的社区门户网站,其中的图库是一个经常使用的功能。

*我粘贴的代码是我们每个人提出的编辑简化版本 - 只是为了展示这两种方法的机制。

6 个答案:

答案 0 :(得分:6)

  

我有一个不同的方法:我更喜欢将原始图像保存在隐藏位置,但是一旦请求它们,请将它们复制到公共位置并提供指向它的链接

+1创意。

  

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

排序。是的,如果您有图像,只允许某些人看到,并且您将它们放入可公开访问的目录中,则会有其他人看到该图像的更改,这似乎是不合需要的。我也不认为(可能是错误的)它会在页面加载上获得10秒,因为你必须复制图像,这是一个相当密集的操作,而不是使用file_get_contents或readfile()。

  

这是安全的,但由于我们使用Zend Framework,它的爬行速度也很慢 - 由于执行引导程序的查询,每次调用图像控制器都会花费大约500ms的延迟。

如果我建议;这个具体案例的核心Zend框架。我也在使用Zend Framework作为一个相当大的网站,所以我知道引导程序可能比你想要的更长。如果你绕过Zend Framework,选择使用vanilla PHP,这将显着提高性能。

另外,使用readfile(),而不是file_get_contents()。 file_get_contents将在输出之前将整个文件加载到内存中,这有很大的不同,其中readfile可以更有效地执行此操作。

答案 1 :(得分:4)

只要您通过检查 MIME 魔术字节确保文件确实是图像文件,甚至尝试使用GD库加载图像应该没事。

使用不执行整个引导程序的专用引导程序可能会加快初始方法。只是加载图像的基本要素。

您还可以在公共文件夹中放置一个“普通” .php 文件,其中包含一个脚本,该脚本可以使用 GD 打开并输出非公用文件夹中的图像类似。只需用

之类的东西来调用它
<a href="image.php?image=foobar.jpg&width=320&height=240" />

在这里,您还必须确保不要盲目信任图像名称,但实际上检查这是图像目录中的图像而不是令人讨厌的图像。

我还建议您将图片存储在非公开文件夹中,以便网络服务器具有写入权限,然后创建符号链接,而无需写入权限, 公共文件夹中的目录列表到此图像文件夹并从那里加载图像。您的Web服务器可以直接为它们提供服务,而不必复制它们。

只是一些建议。

答案 2 :(得分:2)

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

数据库保存...文件名:'myfile.jpg',tempname:'uysdfnasdufhansvdufgnvasoeuvncas.jpg'

Access函数在数据库中查找tempname并相应地插入url

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

答案 3 :(得分:2)

好的,另一种方式怎么样(这也有点hacky,顺便说一下

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

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

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

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

image.php文件应打开包含该ID的列表,并检查其中是否存在myFile.jpg。如果是,请显示它,如果没有发出404。

不要忘记定期清理旧名单。

答案 4 :(得分:2)

几个星期前我想到了这样的问题,最后写了一个自己的Thumbnail ViewHelper,就像你一样。原始图像存储在非公共目录中。所以每次我打电话都是

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

ViewHelper会将Image复制为公共可访问的缓存目录,并将URL返回到caced图像:

/_cache/thumbs/3858f62230ac3c915f300c664312c63f.jpg

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

唯一要记住的是,即使无法从公共场所访问,您也不会使用原始图像显示目录的路径。然后,我会说这是安全的。

在启用缓存的情况下,脚本无需花费时间,因此可提供良好的用户体验。我希望你能用我的一些想法;)

答案 5 :(得分:1)

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

两种解决方案都没有解决问题。这两种方案都不能解决恶意软件的重新分发问题。两种解决方案(如所示)都不限制对内容项的访问。

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

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