1. Olly Smith
  2. django-storages


Ian Lewis  committed b1c97b5

Fixed security issue where a user could create a path that would write to a file outside the base location defined by AWS_LOCATION.

  • Participants
  • Parent commits 43d7909
  • Branches default

Comments (0)

Files changed (1)

File storages/backends/s3boto.py

View file
 import os
-import posixpath
 import mimetypes
 from django.conf import settings
 from django.core.files.base import File
 from django.core.files.storage import Storage
-from django.core.exceptions import ImproperlyConfigured
+from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation
+from django.utils.encoding import force_unicode
     from boto.s3.connection import S3Connection, S3ResponseError
     from gzip import GzipFile
+def safe_join(base, *paths):
+    """
+    A version of django.utils._os.safe_join for S3 paths.
+    Joins one or more path components to the base path component intelligently.
+    Returns a normalized version of the final path.
+    The final path must be located inside of the base path component (otherwise
+    a ValueError is raised).
+    Paths outside the base path indicate a possible security sensitive operation.
+    """
+    from urlparse import urljoin
+    base_path = force_unicode(base)
+    paths = map(lambda p: force_unicode(p), paths)
+    final_path = urljoin(base_path + ("/" if not base_path.endswith("/") else ""), *paths)
+    # Ensure final_path starts with base_path and that the next character after
+    # the final path is '/' (or nothing, in which case final_path must be
+    # equal to base_path).
+    base_path_len = len(base_path)
+    if not final_path.startswith(base_path) \
+       or final_path[base_path_len:base_path_len+1] not in ('', '/'):
+        raise ValueError('the joined path is located outside of the base path'
+                         ' component')
+    return final_path
 class S3BotoStorage(Storage):
     """Amazon Simple Storage Service using Boto"""
         return os.path.normpath(name).replace('\\', '/')
     def _normalize_name(self, name):
-        return posixpath.join(self.location, name).lstrip('/')
+        try:
+            return safe_join(self.location, name).lstrip('/')
+        except ValueError:
+            raise SuspiciousOperation("Attempted access to '%s' denied." % name)
     def _compress_content(self, content):
         """Gzip a given string."""