CVE-2011-4104 (published 2014-10-27T01:55Z)

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.

Severity: HIGH
CVSS v2 base score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)

Weakness information:
cwe_id: CWE-20
cwe_name: Improper Input Validation
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.
extended_description: 
	   Input validation is a frequently-used technique
	   for checking potentially dangerous inputs in order to
	   ensure that the inputs are safe for processing within the
	   code, or when communicating with other components.  When
	   software does not validate input properly, an attacker is
	   able to craft the input in a form that is not expected by
	   the rest of the application. This will lead to parts of the
	   system receiving unintended input, which may result in
	   altered control flow, arbitrary control of a resource, or
	   arbitrary code execution.
	   Input validation is not the only technique for
	   processing input, however.  Other techniques attempt to
	   transform potentially-dangerous input into something safe, such
	   as filtering (CWE-790) - which attempts to remove dangerous
	   inputs - or encoding/escaping (CWE-116), which attempts to
	   ensure that the input is not misinterpreted when it is included
	   in output to another component. Other techniques exist as well
	   (see CWE-138 for more examples.)
	   Input validation can be applied to:
	   
	     raw data - strings, numbers, parameters, file contents, etc.
	     metadata - information about the raw data, such as headers or size
	   
	   Data can be simple or structured.  Structured data
	   can be composed of many nested layers, composed of
	   combinations of metadata and raw data, with other simple or
	   structured data.
	   Many properties of raw data or metadata may need
	   to be validated upon entry into the code, such
	   as:
	   
	     specified quantities such as size, length, frequency, price, rate, number of operations, time, etc.
	     implied or derived quantities, such as the actual size of a file instead of a specified size
	     indexes, offsets, or positions into more complex data structures
	     symbolic keys or other elements into hash tables, associative arrays, etc.
	     well-formedness, i.e. syntactic correctness - compliance with expected syntax 
	     lexical token correctness - compliance with rules for what is treated as a token
	     specified or derived type - the actual type of the input (or what the input appears to be)
	     consistency - between individual data elements, between raw data and metadata, between references, etc.
	     conformance to domain-specific rules, e.g. business logic 
	     equivalence - ensuring that equivalent inputs are treated the same
	     authenticity, ownership, or other attestations about the input, e.g. a cryptographic signature to prove the source of the data
	   
	   Implied or derived properties of data must often
	   be calculated or inferred by the code itself.  Errors in
	   deriving properties may be considered a contributing factor
	   to improper input validation.
	   
	   Note that "input validation" has very different
	   meanings to different people, or within different
	   classification schemes.  Caution must be used when
	   referencing this CWE entry or mapping to it.  For example,
	   some weaknesses might involve inadvertently giving control
	   to an attacker over an input when they should not be able
	   to provide an input at all, but sometimes this is referred
	   to as input validation.
	   Finally, it is important to emphasize that the
	   distinctions between input validation and output escaping
	   are often blurred, and developers must be careful to
	   understand the difference, including how input validation
	   is not always sufficient to prevent vulnerabilities,
	   especially when less stringent data types must be
	   supported, such as free-form text. Consider a SQL injection
	   scenario in which a person's last name is inserted into a
	   query. The name "O'Reilly" would likely pass the validation
	   step since it is a common last name in the English
	   language. However, this valid name cannot be directly
	   inserted into the database because it contains the "'"
	   apostrophe character, which would need to be escaped or
	   otherwise transformed. In this case, removing the
	   apostrophe might reduce the risk of SQL injection, but it
	   would produce incorrect behavior because the wrong name
	   would be recorded.
       
         

Fix information:
repo_url: https://github.com/toastdriven/django-tastypie
commit_details: repo_url: https://github.com/toastdriven/django-tastypie
author: Daniel Lindsley
author_date: 2011-10-28 01:36:57-05:00
msg: Safer loading of YAML. Thanks to daveyss for the report!
repository: repo_url: https://github.com/toastdriven/django-tastypie
repo_name: toastdriven/django-tastypie
description: Creating delicious APIs for Django apps since 2010.
owner: toastdriven
file_changes: filename: serializers.py
change_type: ModificationType.MODIFY
diff: @@ -28,16 +28,16 @@ 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.
@@ -51,30 +51,30 @@ class Serializer(object):
         '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.
         """
@@ -82,68 +82,68 @@ class Serializer(object):
             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
@@ -158,10 +158,10 @@ class Serializer(object):
                 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
 
@@ -169,7 +169,7 @@ class Serializer(object):
         """
         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).
         """
@@ -293,7 +293,7 @@ class Serializer(object):
                     return False
             else:
                 return None
-            
+
     def to_json(self, data, options=None):
         """
         Given some Python data, produces JSON output.
@@ -321,76 +321,76 @@ class Serializer(object):
         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.
@@ -402,7 +402,7 @@ 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:

programming_language: Python