CWE 502 - Deserialization of Untrusted Data
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
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 Codeprivate 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 classMySerializableClass
, 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 customresolveClass
method in theObjectInputStream
to ensure that only the allowed classes are deserialized, and to prevent deserialization of classes that are not allowed. TheresolveClass
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
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. UUse safe deserialization methods:
Instead of using the defaultYAML.load
method, use a safer deserialization method such asYAML.safe_load
orJSON.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 theMyClass
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 toYAML.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 ofMyClass
are deserialized, further reducing the risk of deserialization vulnerabilities.Added exception handling to catch any errors that may occur during deserialization.
PHP
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 theunserialize()
function. For example, you can use input validation functions likefilter_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 asigned 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 dataformat or serialization
method that isless 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. Thefilter_input
function withINPUT_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 theunserialize()
function. This means that only objects of theExample
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 Deserialization | Kondukto
Insecure Deserialization explained with examples - thehackerish
Last updated
Was this helpful?