CVE-2011-4104: Improper Input Validation (CWE-20)
value: The from_yaml method in serializers.py in Django Tastypie before 0.9.10 does not properly deserialize YAML data, which allows remote attackers to execute arbitrary Python code via vectors related to the yaml.load method.
CWE description: The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
Published: 2014-10-27T01:55Z
Repository: https://github.com/toastdriven/django-tastypie
Language: Python
Files changed: tastypie/serializers.py
Commit date: 2011-10-28 01:36:57 -0500
Commit message: Safer loading of YAML. Thanks to daveyss for the report!

Diff: diff --git a/tastypie/serializers.py b/tastypie/serializers.py
index 3448c46..5ff95d4 100644
--- a/tastypie/serializers.py
+++ b/tastypie/serializers.py
@@ -26,20 +26,20 @@ except ImportError:
 
 
 class Serializer(object):
     """
     A swappable class for serialization.
-    
+
     This handles most types of data as well as the following output formats::
-    
+
         * json
         * jsonp
         * xml
         * yaml
         * html
         * plist (see http://explorapp.com/biplist/)
-    
+
     It was designed to make changing behavior easy, either by overridding the
     various format methods (i.e. ``to_json``), by changing the
     ``formats/content_types`` options or by altering the other hook methods.
     """
     formats = ['json', 'jsonp', 'xml', 'yaml', 'html', 'plist']
@@ -49,103 +49,103 @@ class Serializer(object):
         'xml': 'application/xml',
         'yaml': 'text/yaml',
         'html': 'text/html',
         'plist': 'application/x-plist',
     }
-    
+
     def __init__(self, formats=None, content_types=None, datetime_formatting=None):
         self.supported_formats = []
         self.datetime_formatting = getattr(settings, 'TASTYPIE_DATETIME_FORMATTING', 'iso-8601')
-        
+
         if formats is not None:
             self.formats = formats
-        
+
         if content_types is not None:
             self.content_types = content_types
-        
+
         if datetime_formatting is not None:
             self.datetime_formatting = datetime_formatting
-        
+
         for format in self.formats:
             try:
                 self.supported_formats.append(self.content_types[format])
             except KeyError:
                 raise ImproperlyConfigured("Content type for specified type '%s' not found. Please provide it at either the class level or via the arguments." % format)
-    
+
     def get_mime_for_format(self, format):
         """
         Given a format, attempts to determine the correct MIME type.
-        
+
         If not available on the current ``Serializer``, returns
         ``application/json`` by default.
         """
         try:
             return self.content_types[format]
         except KeyError:
             return 'application/json'
-    
+
     def format_datetime(self, data):
         """
         A hook to control how datetimes are formatted.
-        
+
         Can be overridden at the ``Serializer`` level (``datetime_formatting``)
         or globally (via ``settings.TASTYPIE_DATETIME_FORMATTING``).
-        
+
         Default is ``iso-8601``, which looks like "2010-12-16T03:02:14".
         """
         if self.datetime_formatting == 'rfc-2822':
             return format_datetime(data)
-        
+
         return data.isoformat()
-    
+
     def format_date(self, data):
         """
         A hook to control how dates are formatted.
-        
+
         Can be overridden at the ``Serializer`` level (``datetime_formatting``)
         or globally (via ``settings.TASTYPIE_DATETIME_FORMATTING``).
-        
+
         Default is ``iso-8601``, which looks like "2010-12-16".
         """
         if self.datetime_formatting == 'rfc-2822':
             return format_date(data)
-        
+
         return data.isoformat()
-    
+
     def format_time(self, data):
         """
         A hook to control how times are formatted.
-        
+
         Can be overridden at the ``Serializer`` level (``datetime_formatting``)
         or globally (via ``settings.TASTYPIE_DATETIME_FORMATTING``).
-        
+
         Default is ``iso-8601``, which looks like "03:02:14".
         """
         if self.datetime_formatting == 'rfc-2822':
             return format_time(data)
-        
+
         return data.isoformat()
-    
+
     def serialize(self, bundle, format='application/json', options={}):
         """
         Given some data and a format, calls the correct method to serialize
         the data and returns the result.
         """
         desired_format = None
-        
+
         for short_format, long_format in self.content_types.items():
             if format == long_format:
                 if hasattr(self, "to_%s" % short_format):
                     desired_format = short_format
                     break
-        
+
         if desired_format is None:
             raise UnsupportedFormat("The format indicated '%s' had no available serialization method. Please check your ``formats`` and ``content_types`` on your Serializer." % format)
-        
+
         serialized = getattr(self, "to_%s" % desired_format)(bundle, options)
         return serialized
-    
+
     def deserialize(self, content, format='application/json'):
         """
         Given some data and a format, calls the correct method to deserialize
         the data and returns the result.
         """
@@ -156,22 +156,22 @@ class Serializer(object):
         for short_format, long_format in self.content_types.items():
             if format == long_format:
                 if hasattr(self, "from_%s" % short_format):
                     desired_format = short_format
                     break
-        
+
         if desired_format is None:
             raise UnsupportedFormat("The format indicated '%s' had no available deserialization method. Please check your ``formats`` and ``content_types`` on your Serializer." % format)
-        
+
         deserialized = getattr(self, "from_%s" % desired_format)(content)
         return deserialized
 
     def to_simple(self, data, options):
         """
         For a piece of data, attempts to recognize it and provide a simplified
         form of something complex.
-        
+
         This brings complex Python data structures down to native types of the
         serialization format(s).
         """
         if isinstance(data, (list, tuple)):
             return [self.to_simple(item, options) for item in data]
@@ -291,11 +291,11 @@ class Serializer(object):
                     return True
                 else:
                     return False
             else:
                 return None
-            
+
     def to_json(self, data, options=None):
         """
         Given some Python data, produces JSON output.
         """
         options = options or {}
@@ -319,80 +319,80 @@ class Serializer(object):
     def to_xml(self, data, options=None):
         """
         Given some Python data, produces XML output.
         """
         options = options or {}
-        
+
         if lxml is None:
             raise ImproperlyConfigured("Usage of the XML aspects requires lxml.")
-        
+
         return tostring(self.to_etree(data, options), xml_declaration=True, encoding='utf-8')
-    
+
     def from_xml(self, content):
         """
         Given some XML data, returns a Python dictionary of the decoded data.
         """
         if lxml is None:
             raise ImproperlyConfigured("Usage of the XML aspects requires lxml.")
-        
+
         return self.from_etree(parse_xml(StringIO(content)).getroot())
-    
+
     def to_yaml(self, data, options=None):
         """
         Given some Python data, produces YAML output.
         """
         options = options or {}
-        
+
         if yaml is None:
             raise ImproperlyConfigured("Usage of the YAML aspects requires yaml.")
-        
+
         return yaml.dump(self.to_simple(data, options))
-    
+
     def from_yaml(self, content):
         """
         Given some YAML data, returns a Python dictionary of the decoded data.
         """
         if yaml is None:
             raise ImproperlyConfigured("Usage of the YAML aspects requires yaml.")
-        
-        return yaml.load(content)
-    
+
+        return yaml.safe_load(content)
+
     def to_plist(self, data, options=None):
         """
         Given some Python data, produces binary plist output.
         """
         options = options or {}
-        
+
         if biplist is None:
             raise ImproperlyConfigured("Usage of the plist aspects requires biplist.")
-        
+
         return biplist.writePlistToString(self.to_simple(data, options))
-    
+
     def from_plist(self, content):
         """
         Given some binary plist data, returns a Python dictionary of the decoded data.
         """
         if biplist is None:
             raise ImproperlyConfigured("Usage of the plist aspects requires biplist.")
-        
+
         return biplist.readPlistFromString(content)
-    
+
     def to_html(self, data, options=None):
         """
         Reserved for future usage.
-        
+
         The desire is to provide HTML output of a resource, making an API
         available to a browser. This is on the TODO list but not currently
         implemented.
         """
         options = options or {}
         return 'Sorry, not implemented yet. Please append "?format=json" to your URL.'
-    
+
     def from_html(self, content):
         """
         Reserved for future usage.
-        
+
         The desire is to handle form-based (maybe Javascript?) input, making an
         API available to a browser. This is on the TODO list but not currently
         implemented.
         """
         pass
@@ -400,11 +400,11 @@ class Serializer(object):
 def get_type_string(data):
     """
     Translates a Python data type into a string format.
     """
     data_type = type(data)
-    
+
     if data_type in (int, long):
         return 'integer'
     elif data_type == float:
         return 'float'
     elif data_type == bool:

Vulnerable code:     
    
    
    
        
        
        
        
    
        
    
        
        
        
    
        
        
        
    
        
        
        
    
        
        
        
    
        
        
        
            
        
        
    
        
    
        
        
    
        
        return yaml.load(content)
    
        
        
    
        
    
        
    
        
    
Fixed code: 







































        return yaml.safe_load(content)