在允许用户上传图片的场景下,如何防止用户恶意上传一些包含恶意脚本的文件上传并执行是必须考虑的问题。
如果上传文件放在第三方文件api上,比如aws或阿里云专门的文件存储api或者七牛云存储,服务商一般会帮助处理这类问题;如果文件是放在服务器上,就不得不自己防范这类风险了。
这里记录一下实践中使用过的比较靠谱的一种做法:使用 X-Accel-Redirect。
关于Nginx的此特性,其它服务容器如apache也有类似的,不过作者没用过,大家需要自己去尝试,这里只以nginx为例说明。
用户要访问自己上传的文件,最简单的做法莫过于把文件放在一个web可访问的目录里,直接通过构造url路径访问。但这样做一方面不能保护比较隐密的数据,比如用户的实名认证图片,另一方面也容易被攻击,比如:
Nginx默认是以CGI的方式支持PHP解析的,普遍的做法是在Nginx配置文件中通过正则匹配设置SCRIPT_FILENAME。当访问http://192.168.1.103/phpinfo.jpg/1.php这个URL时,$fastcgi_script_name会被设置为“phpinfo.jpg/1.php”,然后构造成SCRIPT_FILENAME传递给PHP CGI。如果PHP中开启了fix_pathinfo这个选项,PHP会认为SCRIPT_FILENAME是phpinfo.jpg,而1.php是PATH_INFO,所以就会将phpinfo.jpg作为PHP文件来解析了。
如果让所有访问过后台代码,这样可以加上权限控制,但是就需要php代码来读取图片返回给客户端,会给服务器造成很大的io压力。这时 X-Accel-Redirect 就可以派上用场了。
X-Accel-Redirect 的功能很简单,就是在用户需要访问静态内容的时候,通过后台设置header,将静态资源通过nginx直接返回到客户端。因为不过后台代码,所以即便文件内容中包含恶意脚本,也不可能会执行。要访问静态文件直接被访问到,还需要配合设置
location /filedir/ {
root /home/foo/yourroot;
internal;
}
把静态文件所在的目录设置为inernal。
这样再配合后台的权限控制逻辑,就可以实现安全的文件访问了。示例代码如下:
$forbidden = false; $mimeType = array( 'png' => 'image/png', 'gif' => 'image/gif', 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'pdf' => 'application/pdf', ); if (!isset($_GET['path']) || !isset($_GET['type'])) { $path = ''; $forbidden = true; } else { $path = 'filedir/'.trim($_GET['path'] . '.' . $_GET['type'], '/');// 补全路径 if (!file_exists($path) || !isset($mimeType[$_GET['type']])) { $forbidden = true; } else { // 权限验证 todo } } header("Cache-Control: max-age=60");// 设置缓存时间 if (!$forbidden && !empty($path)) { $contentType = $mimeType[$_GET['type']]; header("Content-Type: {$contentType}"); // 严格限制内容类型 header("X-Accel-Redirect: /{$path}"); } else { header("Content-Type: image/gif"); header("Location: /filedir/404.gif"); }在生产中使用,效果良好。如果有其它问题,欢迎大家批评指出,一起讨论。