15

AppHarbor has a blog post containing sample C# code which reads data from an unsigned cookie and passes it through .Net binary serialization.

Is that safe?

Obviously, the data is completely tamperable.

However, are there any risks in passing attacker-controlled input to the deserializer?
Could that result in code execution?

SLaks
  • 250
  • 1
  • 2
  • 8
  • I added a .NET-specific answer to this question on SO. I don't want to copy-paste it so I just place a link here: https://stackoverflow.com/a/67107584/5114784 – György Kőszeg Apr 15 '21 at 11:33

5 Answers5

14

I know this is really, really late in the game, but yes, there is a specific vulnerability that pertains directly to deserialisation of untrusted data. It works in the same way that so-called "PHP Object Injection" attacks do - by abusing classes that perform actions in their finalisation and disposal stages.

Consider the following class:

[Serializable]
class TempFile : IDisposable
{
    private readonly string _fileName;

    public TempFile()
    {
        _fileName = Path.GetTempFileName();
    }

    ~TempFile()
    {
        Dispose();
    }

    public FileStream GetStream()
    {
        return new FileStream(_fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite);
    }

    public void Dispose()
    {
        try
        {
            if (File.Exists(_fileName))
                File.Delete(_fileName);
        } catch {}
    }
}

Now, imagine there's a really boring serialisable class called Message that just contains some strings. Nothing really interesting at all. You're using this class to pass messages around over the network, like so:

public void ProcessNetworkMessage(byte[] message)
{
    var bf = new BinaryFormatter();
    Message msg = null;
    try
    {
        using (var ms = new MemoryStream(message))
        {
            msg = (Message) bf.Deserialise(ms);
        }
    }
    catch (Exception ex)
    {
        AppLog.WriteLine("Exception: " + ex.Message);
    }
    if (msg != null)
    {
        ProcessMessage(msg);
    }
}

Seems innocuous, no? But what happens if an attacker passes a binary serialised version of a TempFile object over the network, rather than the expected Message object?

Four things happen:

  1. The data is deserialised into an object instance by the BinaryFormatter.
  2. The returned object is unsuccessfully cast to a Message object, throwing a cast exception.
  3. The method exits without using the returned object, leaving the internal representation with zero references in the GC tree.
  4. The garbage collector disposes of the object.

Why is this exploitable? Well, when the finalisation logic is called by the GC, the destructor calls TempFile.Dispose() and deletes the file specified by the _fileName field. Since that field's value is set within the binary serialised blob, the attacker can control it. Put it all together and you've got an arbitrary file deletion bug.

This seems a bit of a stretch, but often you'll find SQL queries and all sorts of other goodies in Dispose() or destructors of serialisable classes.

Edit: I did a quick search of the reference source, and it turns out that System.CodeDo m.Compiler.TempFileCollection contains exactly the issue I showed above - you can fill it with a list of files and upon destruction it deletes them all.

Polynomial
  • 132,208
  • 43
  • 298
  • 379
  • What is the purpose of the `GetStream()` method? – deed02392 Feb 10 '16 at 07:30
  • @deed02392 It's just part of an example of a class someone might write. – Polynomial Feb 10 '16 at 11:01
  • 1
    Hmm OK, but specifically this is a class that a malicious person might write, I think the most reduced version of a malicious payload here might be best to illustrate the problem. I think that the method doesn't get used at all in this example of an exploit but its presence made me think I'd misunderstood. – deed02392 Feb 10 '16 at 11:07
9

I don't know enough about C#'s serialization to know whether it is a security risk, but I can tell you this:

In Java, it is not safe to unserialize untrusted data. There are a number of subtle security pitfalls that can really screw you over. If you are guru-level, you can probably avoid the pitfalls, but an average developer probably has no clue about the dangers.

Here are some examples of subtle points:

If you fully understand all of that and can keep it in your head, you may be a good candidate to write secure deserialization code that can safely handle untrusted byte streams. On the other hand, if you are a mere mortal (like me) who throws up their hands in disgust at the whole thing, then it might be prudent to ensure that you never deserialize untrusted data.

In the example you give of deserializing a cookie value, here is one plausible defense you could use to ensure you never deserialize an untrusted byte stream. When generating the cookie, you could compute a message authentication code (MAC) over the cookie name and value and append it to the cookie value. When receiving the cookie, you could check whether the MAC is valid. Your server would need to generate a random MAC key and store it securely, but it doesn't need to share this secret value with anyone else (other than all servers who are serving requests).

D.W.
  • 98,420
  • 30
  • 267
  • 572
2

The advice from Microsoft is to use DataContractSerializer. This article (PDF, archived) discusses the serialization vulnerabilities and mitigations on the .NET framework. Also, avoid using Remoting as it uses BinaryFormatter.

riQQ
  • 107
  • 6
Chui Tey
  • 121
  • 2
2

Apart from the obvious tampering weakness there's nothing wrong with doing it this way. Just don't trust data from the client.

As far as I know there are no outstanding code execution exploits of the .NET serialization libraries.

On further examination I noticed that the object is deserialized to IDictionary not Dictionary<string,object> it's conceivable that an attacker could create an object that implements IDictionary but contains arbitrary code. I don't know enough about .NET's implementation of binary serializaiton to know if this would be immediately successful, but depending on the implementation it's feasible.

pdubs
  • 1,103
  • 6
  • 12
  • Wrong on both counts. The deserialized object is _casted_ to `IDictionary` after being deserialized. An attacker could give any type of object, and it will successfully deserialize, then throw an InvalidCastException. – SLaks Apr 05 '12 at 19:35
  • And, AFAIK, you can only deserialize an existing class in any assembly that the deserializing process can load. You cannot (AFAIK) put your own class in the serialized blob. (and if you can get the server to load a DLL, you've already won) – SLaks Apr 05 '12 at 19:36
  • Based off of what's stated in [the docs](http://msdn.microsoft.com/en-us/library/72hyey7b(v=vs.71).aspx) it binary serializes public/private fields along with class/assembly names. Since `IDictionary` is an interface, there are many classes in standard libraries that implement it, but aren't specifically dictionaries. If any of those types have certain types of available fields, you could potentially serialize a lambda expression to execute unauthorized code. – pdubs Apr 05 '12 at 20:29
  • @pdubs a lambda expression shouldn't be serializeable because it gets compiled to a method in IL. – Steve Apr 06 '12 at 00:07
  • @SteveS: Are expression trees serializable? – SLaks Apr 06 '12 at 01:14
  • According to [this](http://reverseblade.blogspot.com/2008/11/how-to-serialize-lambda-expressions.html) it's possible. I'm not entirely convinced it's really a vulnerability, but it would be interesting to research. – pdubs Apr 06 '12 at 01:47
  • @pdubs: That's a separate library, which would need to be loadable by the deserializer. – SLaks Apr 06 '12 at 02:55
2

Is serialization a potential attack vector? Yes. Is it in this case? Nnnnnyesssssno. I think it would require a vulnerability in the BinaryFormatter that doesn't currently exist (at least, publically).

The vulnerability would exist in the code that consumes the IDictionary after it's been deserialized.

I think this is badly written code, but not all that harmful as the vulnerability would exist in the calling code. In the event that something other than IDictionary<string, object> is deserialized, the cast won't throw an exception. It will simple return a null value. A cast like

thing2 foo = thing1 as thing2

will return null. A cast like

thing2 foo = (thing2)thing1

will throw a cast exception.

That object has to be serializeable too, so you are limited to whats currently serializeable in the CLR as well as what the assembly/app knows is serializeable (and that also applies to the concrete implementation of the IDictionary<>). Then that object within the dictionary has to be cast to something useful or reflection would need to be used to get data out of it. Up until that object is actually consumed though, code within either the Dictionary<> or object isn't executed.

Steve
  • 15,155
  • 3
  • 37
  • 66
  • You're right; I misread the cast. I'm primarily asking whether the FCL has any serializable classes that can be used to execute code in the deserializer. – SLaks Apr 06 '12 at 01:15