Language: C# | Type: VULNERABILITY | Severity: Major
Tags: cwe, former-hotspot
Serializable objects that lack data validation during deserialization can be exploited by attackers to bypass security checks.
When an object is created via deserialization, its constructor is often not executed: the object is instead built directly from its serialized data. If the constructor performs security checks — such as validating URLs, restricting values, or enforcing access controls — those checks are silently skipped during deserialization.
This rule raises an issue when a Serializable class lacks validation in its deserialization logic or applies different validation than
its constructor.
An attacker who can control serialized input can craft a malicious payload that completely bypasses the constructor’s validation logic. This can lead to objects being initialized with dangerous or unexpected values, potentially resulting in unauthorized access, data corruption, or remote code execution depending on the application context.
The following code is vulnerable because the deserialization path does not apply the same validation as the regular constructor, allowing an attacker to bypass security checks by crafting a malicious serialized payload.
[Serializable]
public class InternalUrl : ISerializable
{
private string url;
public InternalUrl(string tmpUrl)
{
if(!tmpUrl.StartsWith("http://localhost/"))
{
url = "http://localhost/default";
}
else
{
url = tmpUrl;
}
}
// Special Deserialization constructor
protected InternalUrl(SerializationInfo info, StreamingContext context)
{
url = (string) info.GetValue("url", typeof(string));
// Noncompliant: no validation
}
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("url", url);
}
}
[Serializable]
public class InternalUrl : ISerializable
{
private string url;
public InternalUrl(string tmpUrl)
{
url = tmpUrl;
validate();
}
// Special Deserialization constructor
protected InternalUrl(SerializationInfo info, StreamingContext context)
{
url = (string) info.GetValue("url", typeof(string));
validate();
}
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("url", url);
}
void validate()
{
if(!url.StartsWith("http://localhost/"))
{
url = "http://localhost/default";
}
}
}
[Serializable]
public class InternalUrl : IDeserializationCallback
{
private string url;
public InternalUrl(string tmpUrl)
{
if(!tmpUrl.StartsWith("http://localhost/"))
{
url = "http://localhost/default";
}
else
{
url = tmpUrl;
}
}
void IDeserializationCallback.OnDeserialization(object sender)
{
// Noncompliant: no validation
}
}
[Serializable]
public class InternalUrl : IDeserializationCallback
{
private string url;
public InternalUrl(string tmpUrl)
{
url = tmpUrl;
validate();
}
void IDeserializationCallback.OnDeserialization(object sender)
{
validate();
}
void validate()
{
if(!url.StartsWith("http://localhost/"))
{
url = "http://localhost/default";
}
}
}