Django中的视图函数未被调用

-1 投票
1 回答
2506 浏览
提问于 2025-04-18 11:33

我在自己的网站上发现了一个明显的安全漏洞——用户可以在地址栏中粘贴文件路径并下载文件。我希望这种情况不要发生。

问题是,这个检查似乎没有被调用。用户可以下载服务器上的任何文件,无论文件的拥有者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 个回答

2

你的整个方法是有问题的。检查 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-LengthLast-Modified 头部,以及正确的mime类型。我就不多说了,留给你自己去练习!

最后,你需要在你的 urls.py 中调用这个视图:

url(r'^file/(?P<filename>.*)$', 'file', name="file")

在你的模板中,假设 my_file 是一个 File 对象:

<a href="{% url file my_file.filename %}">Download</a>

撰写回答