4

I find myself in an odd situation only when deployed (debug == false): My model throws a path traversal attempt exception. I want to create a directory for every file uploaded and save the file within the directory (some.zip) used in example. In my dev environment I have no problems and everything works just fine.

models.py:

class Template(models.Model):
    def get_folder(self, filename):
        filename_PATH = Path(filename)
        template_dir = filename_PATH.stem
        return Path(settings.TEMPLATES_FOLDER).joinpath(template_dir, filename)

    name = models.CharField("template", max_length=32, unique=True)
    file = models.FileField("templatefile", upload_to=get_folder, null=True, max_length=260, storage=OverwriteStorage())
    

class OverwriteStorage(FileSystemStorage): #this is actually above
    def get_available_name(self, name, max_length=None):
        self.delete(name)
        return name

forms.py:

class TemplateAdminForm(forms.ModelForm):
    def __init__(self,*args,**kwargs):
        super().__init__(*args, **kwargs)

    class Meta:
        model = Template
        fields = ["name", "file", ]

    def clean(self):
        cleaned_data = super().clean()

        upFile = Path(str(cleaned_data["file"]))
        if upFile.suffix == ".zip":
            path = self.instance.get_folder(cleaned_data["name"])
            logging.error(f"{path}")
            unpack_zip(path) ## works! the directory is created/filled  
        else:
            raise forms.ValidationError("unknown file type ...")
        logging.error("DONE!") # I see this output 
        return cleaned_data  

## signal to see when the error might be happening:
@receiver(post_save, sender = Template)
def testing(sender, **kwargs):
    logging.error("we never get here")

settings.py:

TEMPLATES_FOLDER = PATH(MEDIA_ROOT).joinpath("TEMPLATES")

but:

ERROR:django.security.SuspiciousFileOperation:Detected path traversal attempt in '/opt/project/media_root/TEMPLATES/some/some' WARNING:django.request:Bad Request: /admin/appName/template/add/

Edit:

Because of this discussion it might be important, this is happening on django 3.2.8

xtlc
  • 1,070
  • 1
  • 15
  • 41

1 Answers1

2

I get the same error on Django 3.2.6 when opening a file with mode "wb" at an absolute path name, when I'm not using a temporary file which I have read is recommened in order to avoid this problem so I will link this answer in case it helps you deploy it and share my experience.

Here's where it's been advised: answer

One possible solution would be to move that directory under the django project root folder and address it with a relative path. I'd try to use this too in order to understand how you could achieve this:

import os
print("WORKING DIRECTORY: " + os.getcwd())

An article on this topic suggests to use the following code (when dealing with an image file in that case): link

from django.core.files.temp import NamedTemporaryFile
from django.core import files
image_temp_file = NamedTemporaryFile(delete=True)

in_memory_image = open('/path/to/file', 'rb')
# Write the in-memory file to the temporary file
# Read the streamed image in sections

for block in in_memory_image.read(1024 * 8):
    
    # If no more file then stop
    if not block:
        break    # Write image block to temporary file
    image_temp_file.write(block)


file_name = 'temp.png'  # Choose a unique name for the file
image_temp_file.flush()
temp_file = files.File(image_temp_file, name=file_name)

Lets go through the code:

  • Create a NamedTemporaryFile instead of TemporaryFile as Django’s ImageField requires file name.
  • Iterate over your in-memory file and write blocks of data to the NamedTemporaryFile object.
  • Flush the file to ensure the file is written to the storage.
  • Change the temporary file to a Django’s File object.

You can assign this file to Django models directly and save it.

>>> from blog.models import Blog
>>> b = Blog.objects.first()
>>> b.image = temp_file
>>> b.save()

I personally solved my SuspiciousFileOperation problems by addressing my directory with "BASE_DIR" from settings.py as the beginning of the path (nothing above that level in the filesystem), using a NamedTemporaryFile and by using the model FileField save() method appropriately like this:

# inside a model class save(self, *args, **kwargs) method
# file_name is the file name alone, no path to the file
self.myfilefield.save(file_name, temporary_file_object, save=False) # and then call the super().save(*args, **kwargs) inside the save() method of your model
H3x
  • 47
  • 6