← back to index

S5766 — Serializable objects should validate data during deserialization

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.

Why is this an issue?

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.

What is the potential impact?

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.

How to fix it

Code examples

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.

Noncompliant code example

[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);
    }
}

Compliant solution

[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";
        }
    }
}

Noncompliant code example

[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
    }
}

Compliant solution

[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";
        }
    }
}

Resources

Documentation

Standards