I bumped my Django project to the latest 3.2.4 release from 3.2. All tests passed (OK, I didn’t have a test for this endpoint) and I was happy as can be. I was testing my application which is a JS SPA + Django API and I noticed that media upload to AWS which goes via my Django backend were suddenly getting 400 responses. Specifically, I was getting:
SuspiciousFileOperation: Detected path traversal attempt in '/case_data/dogs/01000/dogs_1000_1622660073141.jpeg'
I took a look at the 3.2.4 release notes and saw that there was a patch for a potential directory traversal vulnerability and I started to suspect it might be hitting my code.
The exception is raised when I return
instance in the below code:
def create_media(self, file, filename, user, short_name=None, annotation=None): try: instance = self.model( file=filename, uploaded_by=user, short_name=short_name, annotation=annotation ) instance.save() instance.file.save(filename, file) return instance except IntegrityError:
And this is my model which inherits from a Meta class, also below.
class Media(models.Model): secure_file = models.CharField(null=True, max_length=512) key = models.CharField(max_length=512, null=True) uploaded_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True, blank=True ) class Meta: abstract = True class Image(Media): id = models.UUIDField(primary_key=True, default=uuid.uuid4) file = models.FileField(upload_to="images", unique=True) annotation = models.CharField(max_length=1024, null=True, blank=True) short_name = models.CharField(max_length=256, null=True, blank=True) url = models.URLField(null=True, blank=True) created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name="images")
So far, at least to my eye, everything looks very standard.
The Django source code which is causing the problem is:
# My own comment: This is from 3.2.4 source, # but it is identical to 3.2 source as best I can tell if allow_relative_path: # Use PurePosixPath() because this branch is checked only in # FileField.generate_filename() where all file paths are expected to be # Unix style (with forward slashes). path = pathlib.PurePosixPath(name) if path.is_absolute() or '..' in path.parts: raise SuspiciousFileOperation( "Detected path traversal attempt in '%s'" % name )
What is perhaps most interesting is that my file path which begins with a
/ is true when executing
>>> import pathlib >>> name = '/case_data/snakes/01001/snakes_1001_1622659350659.jpeg' >>> path = pathlib.PurePosixPath(name) >>> path.is_absolute() True >>> '..' in path.parts False >>> name = 'snakes_1001_1622659350659.jpeg' >>> path = pathlib.PurePosixPath(name) >>> path.is_absolute() False >>> name = 'case_data/snakes/01001/snakes_1001_1622659350659.jpeg' >>> path = pathlib.PurePosixPath(name) >>> path.is_absolute() False >>>
And finally, my question:
Given my file path string,
'/case_data/dogs/01000/dogs_1000_1622660073141.jpeg' is the same when I run 3.2 and 3.2.4 code and I can reliably reproduce the above described behaviour, why am I not getting the
SuspiciousFileOperation in 3.2 but I am in 3.2.4?
I considered opening a ticket, but I’m loathe to do so without checking here first lest I’m missing something obvious.
As always, thank you for your help.