我的网站上有一个相当明显的安全漏洞 - 用户可以将文件路径粘贴到地址栏并下载。我宁愿不这样做。
问题在于它似乎没有被调用。用户可以下载服务器上的任何文件,无论pk是否与request.user.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"),
答案 0 :(得分: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())
我建议选项2,因为在数据库中存储大量二进制数据就像可能不是一个好主意,但这实际上取决于很多因素。
在任何一种情况下,您还应该在响应中添加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>