#python #django
#python #django
Вопрос:
У меня довольно очевидный недостаток безопасности на моем веб-сайте — пользователи могут вставить путь к файлу в адресную строку и загрузить его. Я бы предпочел, чтобы этого не произошло.
Проблема в том, что, похоже, она не вызывается. Пользователь может загружать любые файлы на сервере, независимо от того, соответствует ли 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')
Одно очень интересное наблюдение, которое я должен отметить, заключается в том, что, когда я удалил оператор bottom return, 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. Я заменил sendfile() на render(‘forbidden.html ‘), так что если функция вообще вызывается, она должна возвращать if или else, верно? Я все же попробую. Как мне увидеть вывод инструкций печати из функций представления Django?
3. Вы используете производственный сервер или сервер разработчика? Разве вы не переопределили /media/ url в nginx?
4. Я использую только Apache2
5. @Gudmundur Чтобы увидеть результат печати, вы просто смотрите на вывод в строке cmd, из которой вы его запускаете
Ответ №1:
Весь ваш подход ошибочен. Проверка request.user.id
соответствия pk
переданному в URL-адресе просто означает, что идентификатор вошедшего в систему пользователя должен соответствовать идентификатору, который он ввел в URL.
Это не подтверждает, что этот пользователь на самом деле является «владельцем» файла.
Если пользователь A (id = 1) владеет foo.txt
, а пользователь B (id = 2) владеет bar.txt
, ничто не мешает пользователю A получить доступ bar.txt
по следующему URL:
/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>
Комментарии:
1. Очень приятно, большое спасибо!!! Но как мне запустить эту функцию? Не столкнусь ли я с той же проблемой, что функция не будет запускаться, потому что URL содержит /media/ ?
2. вам нужно будет опубликовать свой urls.py
3. Тьфу, я где-то что-то делаю не так. Файлы по-прежнему можно свободно загружать, как и раньше. Я отредактирую свой OP с информацией
4. В каком модуле находится ваше представление? Вторым аргументом url(…) должен быть полный путь к вашему представлению, например, если ваше представление находится
views.py
в пакетеapp
, это будетurl(r'^file/(?P<filename>.*)$', 'app.views.file', name="file")
5. О, конечно. Исправлено. Но все равно ничего не происходит. Имеет ли значение, что я работаю на localhost? Возможно, этот запрос вообще не проходит через Django, и кажется, что это не так. Всплывающее окно загрузки появляется мгновенно.