CWE 502 - Deserialization of Untrusted Data

Disclaimer
  • The code provided is for **educational purposes** only and should not be used in a **production environment** without proper review and testing.

  • The provided code should be regarded as **best practice**. There are also several ways to remediate the Vulnerabilities.

  • The code provided **may contain vulnerabilities** that could be exploited by attackers, and is intended to be used as a learning tool to help improve security awareness and best practices.

  • The mitigations provided are intended to address **specific vulnerabilities** in the code, but may not be effective against all potential attack vectors or scenarios.

  • The code provided is not **guaranteed to be secure or free** from all vulnerabilities, and should be reviewed and tested thoroughly before being used in a production environment.

  • The code provided is **not a substitute for professional security** advice or guidance, and users should consult with a qualified security professional before implementing any security measures.

  • The authors and contributors of the code provided **cannot be held responsible** for any damages or losses resulting from the use of this code.

  • The code provided is provided "as is" without any warranties, express or implied, including but not limited to the implied warranties of merchantability and fitness for a particular purpose.

  • We do not have any sponsors or financial interests in any specific products or services, and the information provided is based solely on our own knowledge and experience.

  • The vulnerable and mitigated code samples provided on our platform are generated with the help of AI and sourced from the internet. We have made every effort to ensure that the code is accurate and up-to-date, but we cannot guarantee its correctness or completeness. If you notice that any of the code samples belong to you and you wish for it to be removed, please contact us and we will take appropriate action. We also apologize for any inconvenience caused and are committed to giving appropriate credit to the respective authors.

About CWE ID 502

Deserialization of Untrusted Data

This Vulnerability occurs when an application deserializes untrusted data without properly validating or sanitizing it, which can allow attackers to execute arbitrary code or trigger unexpected behavior in the application.

Impact

  • Arbitrary Code Execution

  • Denial of Service (DoS)

  • Information Disclosure

  • Privilege Escalation

Example with Code Explanation

JAVA

Let us consider an example case and understand the CWE-502 with context of Vulnerable code and Mitigated code.

Vulnerable Code

import java.io.*;

public class DeserializationExample implements Serializable {

    public static void main(String[] args) throws Exception {
        String serializedData = "rO0ABXNyACxqYXZhLnNtaW4uUHJvZ3Jlc3NlZEFjY291bnRJbXBsLkltcGxlbWVudGF0aW9uVG9TdHJpbmcAAAAAAAAAAQIAAHhyAAtvcmcuaW5wdXRTZXJpYWxpemFibGVMaXN0AAAAAAAAAAECAAJMAApjb2RlZHJhdGV0ADhMVGFOemM4VjRtT05JZnlMUkZKeWpHSWw0TTJsczR1RlB5Wm9ua2hvb1g0eTVNPQ==";

        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(serializedData)));
        Object obj = in.readObject();

        System.out.println("Deserialized object: " + obj.toString());
    }
}

This code is Vulnerable to Deserialization of untested data as the DeserializationExample class deserializes an object from a Base64-encoded string (serializedData) without properly validating or sanitizing the input.

  • Some of the ways the vulnerable code can be mitigated is:

    • Input validation and sanitization: Before deserializing any input data, validate and sanitize it to ensure that it conforms to expected formats and does not contain any malicious or unexpected content.

    • Secure deserialization techniques: Use secure deserialization techniques, such as whitelisting allowed classes and signatures, to prevent the deserialization of malicious objects.

    • Avoid using deserialization to transmit data between untrusted parties: Deserialization should be avoided as a method of transmitting data between untrusted parties, as it can be used to execute arbitrary code or perform other malicious actions.

    • Carefully consider the security implications of third-party libraries and frameworks: If you are using third-party libraries or frameworks that use deserialization, it's important to carefully consider their security implications and implement appropriate measures to mitigate any potential vulnerabilities.

    Mitigated Code

    private static final long serialVersionUID = 1L;
    
    // A secret key for signing the serialized data
    private static final byte[] SECRET_KEY = "change_this_to_a_secure_key".getBytes();
    
    public static void main(String[] args) throws Exception {
        String serializedData = "rO0ABXNyACxqYXZhLnNtaW4uUHJvZ3Jlc3NlZEFjY291bnRJbXBsLkltcGxlbWVudGF0aW9uVG9TdHJpbmcAAAAAAAAAAQIAAHhyAAtvcmcuaW5wdXRTZXJpYWxpemFibGVMaXN0AAAAAAAAAAECAAJMAApjb2RlZHJhdGV0ADhMVGFOemM4VjRtT05JZnlMUkZKeWpHSWw0TTJsczR1RlB5Wm9ua2hvb1g0eTVNPQ==";
        String signature = "change_this_to_a_valid_signature";
    
        // Verify the signature of the serialized data
        if (verifySignature(serializedData, signature)) {
            // Decode and deserialize the data
            ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(serializedData))) {
                @Override
                protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
                    String[] allowedClasses = {"MySerializableClass"};
                    for (String allowedClass : allowedClasses) {
                        if (desc.getName().equals(allowedClass)) {
                            return Class.forName(desc.getName());
                        }
                    }
                    throw new InvalidClassException("Unauthorized deserialization attempt", desc.getName());
                }
            };
    
            Object obj = in.readObject();
            if (obj instanceof MySerializableClass) {
                MySerializableClass myObj = (MySerializableClass) obj;
                System.out.println("Deserialized object: " + myObj.toString());
            } else {
                throw new InvalidObjectException("Object is not of type MySerializableClass");
            }
        } else {
            System.out.println("Deserialization rejected: invalid signature");
        }
    }
    
    // A method to sign the serialized data using HMAC-SHA256
    private static String signData(String data) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec keySpec = new SecretKeySpec(SECRET_KEY, "HmacSHA256");
        mac.init(keySpec);
        byte[] signature = mac.doFinal(data.getBytes());
        return Base64.getEncoder().encodeToString(signature);
    }
    
    // A method to verify the signature of the serialized data
    private static boolean verifySignature(String data, String signature) throws Exception {
        String expectedSignature = signData(data);
        return MessageDigest.isEqual(signature.getBytes(), expectedSignature.getBytes());
    }
  • The Mitigated code does the following:

    • Verify the signature: The code verifies the signature of the serialized data before deserializing it. This ensures that the data has not been tampered with since it was signed, and that it was signed by a trusted source.

    • Restrict the allowed classes: The code only allows deserialization of objects of the specific class MySerializableClass, and throws an exception if any other class is encountered during deserialization. This prevents an attacker from creating and deserializing malicious objects of different classes.

    • Custom resolveClass method: The code uses a custom resolveClass method in the ObjectInputStream to ensure that only the allowed classes are deserialized, and to prevent deserialization of classes that are not allowed. The resolveClass method checks the class name against a list of allowed classes, and throws an exception if the class name is not found in the list.

    • Secret key for signing: The code uses a secret key to sign the serialized data, which ensures that only authorized parties can create valid signatures.

Ruby

Vulnerable Code

require 'yaml'
 
class MyClass
  def initialize
    @data = "secret data"
  end
 
  def to_yaml_properties
    [:@data]
  end
 
  def self.from_yaml_properties(props)
    obj = self.new
    props.each do |key, value|
      obj.instance_variable_set(key, value)
    end
    obj
  end
end
 
serialized_data = "--- !ruby/object:MyClass\n@data: hello\n"
 
obj = YAML.load(serialized_data)
puts obj.inspect

The vulnerability occurs when the YAML.load method is called with the serialized_data input. This method deserializes the YAML data and constructs a new instance of MyClass using the from_yaml_properties method. Since the from_yaml_properties method simply sets the instance variable @data to the value specified in the YAML data, an attacker can craft malicious YAML data to set @data to an arbitrary value, potentially allowing them to execute arbitrary code or obtain sensitive information.

  • Some of the ways the Vulnerable code can be mitigated is:

    • Validate input data: Before deserializing any data, ensure that it comes from a trusted source and is in the expected format. Do not deserialize data from untrusted sources or unverified user input.

    • Sanitize input data: Remove any potentially malicious or unexpected data from the input before deserializing it. U

    • Use safe deserialization methods: Instead of using the default YAML.load method, use a safer deserialization method such as YAML.safe_load or JSON.parse. These methods provide additional security features such as restricting the types of objects that can be deserialized and preventing code execution.

    • Whitelist allowed classes: Use a whitelist of allowed classes and only deserialize objects of those classes. This can help prevent the deserialization of malicious objects.

Mitigated Code

require 'yaml'

class MyClass
  ALLOWED_PROPERTIES = [:data].freeze

  def initialize
    @data = "secret data"
  end

  def to_yaml_properties
    ALLOWED_PROPERTIES.map { |prop| instance_variable_get("@#{prop}") }
  end

  def self.from_yaml_properties(props)
    obj = self.new
    props.each do |key, value|
      next unless ALLOWED_PROPERTIES.include?(key)

      obj.instance_variable_set("@#{key}", value)
    end
    obj
  end
end

serialized_data = "--- !ruby/object:MyClass\n@data: hello\n"

begin
  obj = YAML.safe_load(serialized_data, [MyClass])
  puts obj.inspect
rescue StandardError => e
  puts "Error: #{e.message}"
end
  • The Mitigated Code does the following:

    • Added a ALLOWED_PROPERTIES constant that lists the allowed instance variables for the MyClass class. This helps us to ensure that only the allowed properties are deserialized.

    • Updated the to_yaml_properties method to return only the allowed instance variables. This ensures that only the allowed properties are serialized.

    • Updated the from_yaml_properties method to only set the allowed instance variables. This helps prevent unexpected behavior and mitigates the risk of vulnerabilities.

    • Changed the YAML.load method to YAML.safe_load, which provides additional security features such as restricting the types of objects that can be deserialized and preventing code execution.

    • Added an array of allowed classes as the second argument to YAML.safe_load. This ensures that only instances of MyClass are deserialized, further reducing the risk of deserialization vulnerabilities.

    • Added exception handling to catch any errors that may occur during deserialization.

PHP

Vulnerable Code

<?php

class Example {
  public $name;
  public $value;
  public function __construct($name, $value) {
    $this->name = $name;
    $this->value = $value;
  }
}

$data = $_COOKIE['user_data'];
$user = unserialize($data);

echo "Hello, " . $user->name;

?>

The above script reads a serialized object from a cookie and deserializes it using the unserialize() function. The serialized object represents an instance of the Example class, which has two public properties (name and value) and a constructor that initializes these properties.

However, this code does not validate or sanitize the input data to ensure that it is safe to deserialize. An attacker could potentially tamper with the serialized data to inject malicious code into the application.

For example, an attacker could send a malicious cookie containing the following serialized data:


O:7:"Example":2:{s:4:"name";s:6:"Alice";s:5:"value";s:16:"system('ls -la');";}

When the vulnerable script deserializes this data, it creates a new instance of the Example class with the following properties:


$user->name = "Alice";
$user->value = "system('ls -la');";

When the script tries to print the user's name using echo "Hello, " . $user->name;, it actually executes the ls -la command on the server!

  • Some of the ways the Vulnerable code can be mitigated is:

    • Validate and sanitize input data: Validate and sanitize all input data before passing it to the unserialize() function. For example, you can use input validation functions like filter_input() or regular expressions to ensure that the input data contains only valid characters.

    • Use a whitelist of allowed classes: To further restrict the types of objects that can be deserialized, you can use a whitelist of allowed classes. This means that only objects of a certain type (e.g., User, Product, etc.) are allowed to be deserialized, and all other objects are rejected.

    • Use a signed serialization format: To ensure that the serialized data has not been tampered with, you can use a signed serialization format. This involves adding a signature or checksum to the serialized data that can be verified before deserialization.

    • Use a different data format: To avoid deserialization vulnerabilities altogether, you can use a different data format or serialization method that is less prone to deserialization vulnerabilities. For example, you can use JSON, XML, or a binary format like MessagePack.

    • Limit the scope of deserialization: If deserialization is required, you can limit the scope of deserialization by using a separate process or sandbox environment to deserialize the data. This can help prevent the execution of malicious code on the server.

Mitigated Code

<?php

class Example {
  public $name;
  public $value;
  public function __construct($name, $value) {
    $this->name = $name;
    $this->value = $value;
  }
}

$allowed_classes = ['Example'];
$signature = 'mysecretkey';

// Sanitize and validate the cookie values
if (isset($_COOKIE['user_data']) && isset($_COOKIE['user_hash'])) {
  // Use filter_input instead of filter_var
  $data = filter_input(INPUT_COOKIE, 'user_data', FILTER_SANITIZE_STRING);
  $hash = filter_input(INPUT_COOKIE, 'user_hash', FILTER_SANITIZE_STRING);
  // Use ctype_xdigit instead of preg_match
  if ($data && $hash && ctype_xdigit($hash) && strlen($hash) === 64) {
    // Verify the signature
    $expected_hash = hash_hmac('sha256', $data, $signature);
    if (hash_equals($hash, $expected_hash)) {
      // Deserialize the data
      $user = unserialize($data, ['allowed_classes' => $allowed_classes]);
      if ($user instanceof Example) {
        // Use htmlentities instead of htmlspecialchars
        echo "Hello, " . htmlentities($user->name);
      } else {
        echo "Invalid user data";
      }
    } else {
      echo "Invalid hash";
    }
  } else {
    echo "Invalid cookie format";
  }
} else {
  echo "Missing cookie values";
}
  • The Mitigated code does the following:

    • Sanitize and validate input data using filter_input: The code uses filter_input function instead of filter_var to sanitize and validate the cookie values before using them. The filter_input function with INPUT_COOKIE type filters the input data directly from the $_COOKIE superglobal array.

    • Use a whitelist of allowed classes:To further restrict the types of objects that can be deserialized, an array of allowed classes ($allowed_classes) is passed as an option to the unserialize() function. This means that only objects of the Example class are allowed to be deserialized.

    • Use output encoding: Before printing the user's name, htmlspecialchars() function is used o encode the output and prevent cross-site scripting (XSS) attacks.

    • Use ctype_xdigit instead of preg_match: The code uses ctype_xdigit instead of preg_match to check if the hash contains only hexadecimal characters. This helps prevent possible SQL injection attacks and other malicious inputs.

References

Insecure Deserialisation

Insecure Deserialization | Kondukto

Insecure Deserialization explained with examples - thehackerish

Deserialization - OWASP Cheat Sheet Series

Last updated

Was this helpful?