When an object is created via deserialization, its constructor is often not executed: the object is instead built directly from its serialized data.
This means an attacker can create a malicious object and completely bypass security checks.

Ask Yourself Whether

There is a risk if you answered yes to any of those questions.

Recommended Secure Coding Practices

Sensitive Code Example

In the following examples, Serializable classes either omit validation or apply different validation logic during instantiation than during deserialization.

For classes inheriting ISerializable:

[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));
       // Sensitive - no validation
     }

    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("url", url);
    }
}

For classes inheriting IDeserializationCallback:

[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)
    {
       // Sensitive - no validation
    }
}

For classes inheriting from neither of previous types:

[Serializable]
public class InternalUrl
{
    private string url;

    public InternalUrl(string tmpUrl)
    {
        // Sensitive - no validation
        url = tmpUrl;
    }
}

Compliant Solution

For classes inheriting ISerializable:

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

For classes inheriting IDeserializationCallback:

[Serializable]
public class InternalUrl : IDeserializationCallback
{
    private string url;

    public InternalUrl(string tmpUrl)
    {
        url = tmpUrl;
        validate();
    }

    void IDeserializationCallback.OnDeserialization(object sender)
    {
        validate();
    }
}

For classes inheriting from neither of previous types:

[Serializable]
public class InternalUrl
{
    private string url;

    public InternalUrl(string tmpUrl)
    {
        if(!tmpUrl.StartsWith("http://localhost/"))
        {
            url = "http://localhost/default";
        }
        else
        {
            url = tmpUrl;
        }
    }
}

See