Commits

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.

Comments (0)

Files changed (1)

storages/backends/s3boto.py

 import os
-import posixpath
 import mimetypes
 
 try:
 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
 
 try:
     from boto.s3.connection import S3Connection, S3ResponseError
 if IS_GZIPPED:
     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."""
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.