A new security issue — CVE-2024-39330 — was discovered in popular Python web framework Django (5. before 5..7 and 4.2 before 4.2.14). If you use custom storage classes with Django files, your project might be at risk from a directory traversal vulnerability. This flaw lets attackers write files outside the intended upload folder, threatening the safety of your app and underlying server files.
In this article, we’ll break down the vulnerability, show a real-world vulnerable code snippet, explain how to exploit it, and provide steps to fix it, using simple, direct language.
What is Directory Traversal?
Directory traversal happens when a user can save or access files outside a restricted folder by sneaking in things like ../ into a file path. For web apps, this can mean leaking sensitive files or even overwriting important system files.
The Vulnerability: Custom Storage's generate_filename()
In Django, you can handle how and where files are saved by making your own storage class. Let's say you build it off django.core.files.storage.Storage. This class normally checks paths to make sure files only go where you expect.
But some developers override generate_filename() in their derived classes without repeating the parent’s validation code. That means an attacker could slip a sneaky filename into a file upload and save their file anywhere they want — not just the upload folder!
Key point:
Here’s an example of a vulnerable storage class (do NOT use this in your app!)
from django.core.files.storage import Storage
from pathlib import Path
class BadStorage(Storage):
def generate_filename(self, filename):
# Custom logic, but no validation!
return "uploads/" + filename
def _save(self, name, content):
# Simulate saving file, reuse default Django logic
with open(self.generate_filename(name), "wb") as f:
for chunk in content.chunks():
f.write(chunk)
return self.generate_filename(name)
How is this a problem? If a user uploads a file named ../../evil.txt, your app will try to write to uploads/../../evil.txt — which (depending on server config) works out to somewhere above your uploads directory!
Let’s say you have a Django view letting users upload profile pictures
def upload_picture(request):
if request.method == "POST":
f = request.FILES['file']
storage = BadStorage()
storage.save(f.name, f)
return HttpResponse("Uploaded!")
If the attacker uploads a file with this crafted name:
../../manage.py
Your app will write all the file data over (or next to) Django’s own manage.py script! This could let hackers place malicious code, or just totally mess up your app.
What an attacker would do
- Use a web proxy or custom client to POST a file named "../../static/evil.jpg"
- The Django process, using the vulnerable generate_filename(), saves the file outside the intended directory
How Can I Fix It?
Short answer: *Upgrade Django, or make your own checks!*
1. Upgrade Django
- Django >= 5..7 and Django >= 4.2.14 fix this for people using generate_filename() in their own code.
- Django security advisory
2. Update Your Storage Classes
If you must support old versions, copy the validation from Django’s own Storage.generate_filename():
from django.core.files.storage import Storage, filepath_to_uri
import os
class SafeStorage(Storage):
def generate_filename(self, filename):
# This path sanitation is from Django
filename = os.path.normpath(filename).replace("\\", "/")
if filename.startswith("../") or filename.startswith("/"):
raise ValueError("Suspicious file path: %s" % filename)
# Now safe to use!
return "uploads/" + filename
Official References
- Original CVE record for CVE-2024-39330
- Django security release
- Django code diff for 5..7 fixing generate_filename
TL;DR
- CVE-2024-39330 lets attackers exploit poorly written custom storage backends in Django to write files outside the uploads directory.
Fix: Upgrade Django, and always use safe path handling in your file storage logic.
Protect your app now — don’t wait for an attacker to try!
Timeline
Published on: 07/10/2024 05:15:12 UTC
Last modified on: 11/01/2024 15:35:16 UTC