PHP - 访问 Web 根目录之外的文件的安全性

Posted

技术标签:

【中文标题】PHP - 访问 Web 根目录之外的文件的安全性【英文标题】:PHP - Security in accessing files outside of web root 【发布时间】:2015-02-21 04:24:01 【问题描述】:

我有一个名为 gallery.php 的 php 文件,它是用户特定图片库的页面。为安全起见,用户图像存储在 Web 根目录之外。

为了为每个用户检索适当的文件,我使用了一个 getimage.php 文件,该文件从他们的位置提供图像。总而言之,目录结构是这样的:

用户图像 用户 1 user1 的图片列表 用户 2 user2 的图片列表 public_html gallery.php getimage.php

getimage.php 的写法如下:

$imgString = realpath('/UserImages/' . $_SESSION['username'] . '/' . $_GET['img']);
if (!startsWith($imgString, '/UserImages/' . $_SESSION['username'] . '/')
    || !(endsWith(strtolower($imgString), '.jpg') || endsWith(strtolower($imgString), '.jpeg')))

    header('HTTP/1.0 403 Forbidden');
    die();


$img = file_get_contents($imgString);
header("Content-type: image/jpeg");
echo($img);
exit();

我依靠 $_GET['img'] 来确定要检索哪个图像,这可能是一个安全漏洞(也是一个主要漏洞)。我可以预见目录遍历攻击,因此会使用 realpath,但我确信此脚本可能不包含其他攻击途径。

出于这个原因,如果我可以将 getimage.php 移到 webroot 之外,或者至少阻止它被直接访问(并且只能通过 gallery.php,发送的 img 参数在我的控制之下),我更愿意.

每当我尝试将 getimage.php 移出 public_html 时,即使我在gallery.php 中执行要求或包含,我似乎也无法再调用它。我访问 getimage.php 的方式是这样做的:

<img src=getimage.php?img=IMG_FILENAME.jpg />

但如果我将 getimage.php 移出 public_html 目录,它会失败。

所以,长话短说:我需要做些什么来防止 getimage.php 被滥用?

【问题讨论】:

因为图像要在网络上提供,我不认为将它们存储在网络根目录之外是一个好主意,因为为了提供它们,无论如何你都需要创建一个安全漏洞。相反,我会验证上传的文件是否真的是图像,然后让它们可以访问网络。 ***.com/questions/6755192/… 我不需要上传检查,因为提供的文件都是我自己制作的。用户只需要能够看到它们。如果我要将用户图像放在 web 根目录中,如何防止除 User1 之外的任何人访问 User1 的文件? 我对为什么需要保护某些图像感到困惑。对我来说,您的脚本似乎是这里的问题。为什么不将您的图像移动到子域(与您的应用程序完全分开)并从那里提供它们? 如何通过知道他人图像文件的 URL 来防止未经授权的用户访问他们不应该访问的图像? 【参考方案1】:

最好的安全方法是不使用 $_GET['img'] 传递图像 url,而是发送对图像的引用。

问题

首先,$_GET['img'] 可以是任何指向有害代码的外部 url,使用 file_get_contents 可以直接下载它。

其次,任何人都可以将您的服务器用作免费图片代理,只需在他自己的网站中使用以下网址:

<img src="http://www.yourwebsite.com/getimage.php?img=[external_image_url]">

正确的方法

1) 在您的数据库中创建包含用户图像路径的图像表。当您要提供图片时,请传递图片 ID 而不是 url:

$imageID = $_GET['image'];
.... your sql query here to retrieve the image....
$img = file_get_contents($imgString);

2) 因为您知道 $_GET['image'] 是一个 ID,您可以轻松地对其进行清理:

if( !preg_match("/^[0-9]+$/", $_GET['image']) )

    header('HTTP/1.1 404 Not Found');
    exit();    

3) 在你的 SQL 中不要忘记使用准备好的语句:

$sql = "SELECT image_path from images WHERE id = ?";

4) 只有图像应该被移到文档根文件夹之外,如果你想将 getimage.php 也移到根目录之外,你可以使用 Apache Alias 来提供它。在 Apache Virtualhost 配置中,您可以使用:

Alias /getimage.php /path/to/getimage.php

5) 使用 404 not found 代替 403 禁止,不要给攻击者暗示你抓住了他的动作。

【讨论】:

【参考方案2】:

您需要允许 Apache(假定)使用 Directory 指令 (http://httpd.apache.org/docs/2.2/mod/core.html#directory) 访问包含图像的文件夹。

或者,如果您想保护它们,您可以将图像移动到根目录下并使用 .htaccess 限制对它们的直接访问。

【讨论】:

我想使用这样的解决方案,结合登录页面,但我不知道如何。有没有办法将 .htaccess 与登录页面结合使用,而不是基于浏览器的弹出框? 当然可以。您可以使用 rewrite (httpd.apache.org/docs/2.4/rewrite/flags.html) 将所有请求(css、ico 和 js 除外)转发到您的页面,您可以在其中使用登录名和脚本来提供图像,例如:“RewriteRule !\.(js|ico|css) $ index.php" 在你的 .htaccess【参考方案3】:

使用目录结构执行此操作的唯一方法是清除 $_GET['img'] 变量并确保它没有任何不需要的字符。

您可以使用正则表达式来过滤除字母、数字、下划线、连字符和点之外的所有内容。

尝试用此替换代码的第一行...

$imgString = realpath('/UserImages/' . $_SESSION['username'] . '/' . preg_replace("/[^a-zA-Z0-9._-]/", "", $_GET['img']));

或者,您可以在代码之前使用这个简单的 If 语句检查 $_GET['img'] 变量是否包含错误字符。

if(preg_replace("/[^a-zA-Z0-9._-]/", "", $_GET['img']) != $_GET['img'])
    // Throw an error
    exit();

PS:在阅读之前不要忘记检查你的文件是否存在,否则你会得到错误。您可以使用文件存在作为额外的安全检查。

【讨论】:

以上是关于PHP - 访问 Web 根目录之外的文件的安全性的主要内容,如果未能解决你的问题,请参考以下文章

基于 web 访问 tomcat 目录之外的文件列表

使用PHP / MySQLI / Apache时,在哪里安全地存储证书/密钥?

PHP获取文件根目录之外的文件

处理安全问题的基本php表单

03:基础入门-搭建安全拓展

织梦dedecms系统将data目录迁移出web的方法