Django中的视图函数未被调用
我在自己的网站上发现了一个明显的安全漏洞——用户可以在地址栏中粘贴文件路径并下载文件。我希望这种情况不要发生。
问题是,这个检查似乎没有被调用。用户可以下载服务器上的任何文件,无论文件的拥有者ID是否与请求的用户ID匹配。
这是我project/urls.py
中相关的部分。
urlpatterns = patterns('',
url(r'^admin/', include(admin.site.urls)),
#security
url(r'^media/uploads/(?P<pk>[^/]+)', 'notendur.views.permit'),
url(r'^media', 'notendur.views.permit'),
)
这是一个视图函数,应该用来检查用户是否是请求文件的拥有者。我把所有内容都包括进来,是为了给你提供尽可能多的信息。
在井号(#)上面的代码是用来清理请求的地址的。我知道这个函数没有被调用,因为我把sendfile()
函数换成了简单的render('forbidden.html')
,结果什么都没有发生。
正如你所看到的,这个函数检查链接中的<pk>
是否与request.user.id
相同,如果相同就提供文件。
def permit(request, pk):
path = request.path
path_list = path.split("/")
s = ""
for dir in path_list:
if dir != "media" and path_list.index(dir) > path_list.index("media"):
s += dir + "/"
s = s[:-1]
# The for loop would add a "/" to the filename.
# The system would think the file was a directory, and not a file.
system_path = settings.MEDIA_ROOT + s
if int(request.user.id) == int(pk) and int(request.user.id) >= 1:
return sendfile(request, system_path)
else:
return render_to_response('forbidden.html')
return HttpResponseRedirect('/notendur/list')
我应该提到一个非常有趣的观察,就是当我去掉了底部的返回语句后,localhost
就不再指向localhost/notendur/list
了。
新的models.py:
class Document(models.Model):
filename = models.CharField(max_length=255, blank=False, null=False, default="")
user = models.ForeignKey(User, related_name='files', null=False)
docfile = models.FileField(upload_to=_upload_path)
user_id = user.primary_key
options = models.IntegerField(default=0)
name = models.TextField(default=0)
def get_upload_path(self,filename):
return "uploads/"+str(self.user.id) + '/' + str(date.today()) + '/' + filename
我的视图
View:
@login_required
def file(request, filename):
file = get_object_or_404(File, user=request.user, filename=filename)
return HttpResponse(open(file.filename, 'rb').read())
urls.py:
url(r'^file/(?P<filename>.*)$', 'file', name="file"),
1 个回答
你的整个方法是有问题的。检查 request.user.id
是否和网址中传入的 pk
匹配,只是意味着登录用户的ID必须和他们在网址中输入的ID一致。
这并不能验证这个用户是否真的是某个文件的“拥有者”。
比如说,如果用户A(ID=1)拥有 foo.txt
,而用户B(ID=2)拥有 bar.txt
,那么用户A并没有任何限制可以通过以下网址访问 bar.txt
:
/media/bar.txt/2/
这段代码也特别不安全:
system_path = settings.MEDIA_ROOT + s
因为没有任何东西可以阻止我访问你服务器上可以访问的任何文件:
/media/../settings.py/1/
最后,使用 /media/
目录是行不通的,因为它根本不是通过django视图提供的(除了在开发服务器上)。在实际部署中,/media/
通常是静态提供的。
有几种方法可以实现这个目标。
将数据保存在django模型中:
models.py:
class File(models.model): user = models.ForeignKey(User, related_name='files', null=False) filename = models.CharField(max_length=255, blank=False, null=False) data = models.BinaryField()
views.py:
from django.shortcuts import get_object_or_404 from django.contrib.auth.decorators import login_required @login_required def file(request, filename): file = get_object_or_404(File, user=request.user, filename=filename) return HttpResponse(file.data)
将数据保存在文件系统中,但使用模型来跟踪它:
models.py:
class File(models.model): user = models.ForeignKey(User, related_name='files', null=False) filename = models.CharField(max_length=255, blank=False, null=False)
views.py:
from django.shortcuts import get_object_or_404 from django.contrib.auth.decorators import login_required @login_required def file(request, filename): file = get_object_or_404(File, user=request.user, filename=filename) return HttpResponse(open(file.filename, 'rb').read())
我推荐第二种选择,因为像这样在数据库中存储大量二进制数据 可能 不是个好主意,但这实际上取决于很多因素。
无论哪种情况,你还应该在响应中添加 Content-Length
和 Last-Modified
头部,以及正确的mime类型。我就不多说了,留给你自己去练习!
最后,你需要在你的 urls.py
中调用这个视图:
url(r'^file/(?P<filename>.*)$', 'file', name="file")
在你的模板中,假设 my_file
是一个 File
对象:
<a href="{% url file my_file.filename %}">Download</a>