Django ImageField / FileField 自定义 upload_to 函数,以及安全性

发布于 2024-08-07 05:29:37 字数 2746 浏览 7 评论 0原文

我有这样定义的模型的一部分:

logo_image = models.ImageField(upload_to=lambda i, fn: "logo_%s"%(fn), height_field="logo_image_height", width_field="logo_image_width")

并对 upload_to 函数有疑问。

根据 django 的 FileField 文档。 upload_to,第二个参数,filename 是“最初赋予该文件的文件名”。

现在,了解了 HTTP、文件上传等之后,最终用户的客户端就可以轻松伪造文件名。特别是,例如,最终客户端是否无法上传名为“/etc/passwd”的文件,然后如果我使用我的天真的代码 (lambda i, fn: "logo_%s"%(fn)< /code>),结果文件不会上传到/etc/passwd吗?我需要转义 filename 参数吗?

#using django's example of using full paths in settings module,
#MEDIA_ROOT="/tmp/media"
>>> os.path.join("/tmp/media/", "apple.jpg")
'/tmp/media/apple.jpg'
>>> os.path.join("/tmp/media/", "/etc/passwd")
'/etc/passwd'

感谢您的任何建议/答案/澄清。

编辑

要查看的重要方法是 在 files.py 中,第 272 行附近

272         def get_directory_name(self):
273             return os.path.normpath(force_unicode(datetime.datetime.now().strftime(smart_str(self.upload_to))))
274     
275         def get_filename(self, filename):
276             return os.path.normpath(self.storage.get_valid_name(os.path.basename(filename)))
277     
278         def generate_filename(self, instance, filename):
279             return os.path.join(self.get_directory_name(), self.get_filename(filename))

定义自定义 upload_to 替换 generate_filename(),如 此处

226             if callable(upload_to):
227                 self.generate_filename = upload_to

然后,在 save()方法

89      def save(self, name, content, save=True):
90          name = self.field.generate_filename(self.instance, name)
91          self.name = self.storage.save(name, content)

并将返回的文件名传递给存储类最终调用 _os.py util 模块中的 django 替换函数

该功能似乎减轻了我的恐惧:

24    def safe_join(base, *paths):
25      """
26      Joins one or more path components to the base path component intelligently.
27      Returns a normalized, absolute version of the final path.
28  
29      The final path must be located inside of the base path component (otherwise
30      a ValueError is raised).
31      """

I have part of a model defined like this:

logo_image = models.ImageField(upload_to=lambda i, fn: "logo_%s"%(fn), height_field="logo_image_height", width_field="logo_image_width")

and had a question about the upload_to function.

According to django's documentation for FileField.upload_to, the second paramater, filename is "The filename that was originally given to the file."

Now, knowing about HTTP, file uploads, etc, the end user's client can easily fake the filename. In particular, couldn't the end client upload a file called "/etc/passwd", for example, and then if I use my naive code (lambda i, fn: "logo_%s"%(fn)), wouldn't the resulting file be uploaded to /etc/passwd? Do I need to escape the filename parameter?

#using django's example of using full paths in settings module,
#MEDIA_ROOT="/tmp/media"
>>> os.path.join("/tmp/media/", "apple.jpg")
'/tmp/media/apple.jpg'
>>> os.path.join("/tmp/media/", "/etc/passwd")
'/etc/passwd'

Thanks for any suggestions / answers / clarification.

Edit

The important methods to look at are in files.py, near line 272:

272         def get_directory_name(self):
273             return os.path.normpath(force_unicode(datetime.datetime.now().strftime(smart_str(self.upload_to))))
274     
275         def get_filename(self, filename):
276             return os.path.normpath(self.storage.get_valid_name(os.path.basename(filename)))
277     
278         def generate_filename(self, instance, filename):
279             return os.path.join(self.get_directory_name(), self.get_filename(filename))

Defining a custom upload_to replaces generate_filename() as seen here:

226             if callable(upload_to):
227                 self.generate_filename = upload_to

Then, in the save() method:

89      def save(self, name, content, save=True):
90          name = self.field.generate_filename(self.instance, name)
91          self.name = self.storage.save(name, content)

And the returned filename is passed to the storage class which eventually calls a django replacement function in the _os.py util module safe_join.

That function appears to alleviate my fears:

24    def safe_join(base, *paths):
25      """
26      Joins one or more path components to the base path component intelligently.
27      Returns a normalized, absolute version of the final path.
28  
29      The final path must be located inside of the base path component (otherwise
30      a ValueError is raised).
31      """

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

您的好友蓝忘机已上羡 2024-08-14 05:29:37

我想你已经回答了你自己的问题。需要澄清的一点是,os.path.join() 的工作方式是去掉前面的目录(根据与 os.path 相关的 Python 文档)。因此,您在调用 os.path.join() 时观察到的行为与其描述的方式一致。

另一件需要注意的事情是: get_filename() 函数调用 os.path.basename() ,它将删除所有目录路径并仅返回基本名称。因此,如果没有 upload_to= 参数,就不存在这种可能性的危险。

但是,如果您使用自己的 upload_to 函数覆盖 ImageField(),则不会调用该函数,并且最好调用 os.path.basename()。首先,它也会避免将文件名保存为完整目录路径。因此,我发现最好在 upload_to 函数中调用 os.path.basename() 。还有其他人遇到过这个问题吗?

有关更多详细信息,请参阅:http://hustoknow.blogspot.com/ 2010/08/try-me-out.html

I think you've answered your own question. One point of clarification in that the way os.path.join() works is to strip out the preceding directories are tossed out (according to the Python docs related to os.path). So the behavior you observed in invoking os.path.join() is consistent with how it is described.

One other thing to note: The get_filename() function invokes os.path.basename(), which will strip out any directory paths and return back only the basename. So without an upload_to= parameter, there is no danger of this possibility.

However, if you override the ImageField() with your own upload_to function, this function will not be called and it may be better to invoke os.path.basename(). First, it will avoid saving the filename as the full directory path too. I've found it therefore preferable to also invoke os.path.basename() within my upload_to function. Has anyone else encoutnered this issue?

For more details, see: http://hustoknow.blogspot.com/2010/08/try-me-out.html

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文