home // CVE-2014-9138

> ABSTRACT

Canape v1.32 and previous is vulnerable to an arbitrary file deletion bug, along with certain other impacts, via a deserialisation bug. The vulnerability can be triggered by opening a specially crafted project file.

> TIMELINE

Note: Context no longer provide new binary releases, and do not appear to be maintaining the project. Their github account does not accept new pull requests. James Forshaw expressed similar experiences. Due to this, along with the lack of direct response, I decided to release this advisory as the project is to be considered unsupported.

Update: James Forshaw has patched this issue in his personal fork.

> DETAILS

CVE-2014-9138 is a bug in Canape from Context IS. Canape is a network testing tool that allows you to mess with binary protocols over the wire, similar to how Burp works for web applications. The application is written in C#, primarily for Windows. I have found the tool very useful for abusing binary protocols when performing application assessments.

The bug lies in the use of serialised .NET objects as a way to save project files. Due to the way in which serialisation works, it is possible to "inject" arbitrary objects into a serialised stream, such that the application deserialises them later. Usually, this would just result in a casting exception due to the mismatched types, but there are exceptions to this behaviour.

To provide a bit of background to the issue, consider the following code snippet:

  List<string> DeserialiseList(Stream input)
  {
    var bf = new BinaryFormatter();
    var result = (List<string>)bf.Deserialize(input);
    return result;
  }
			

The above code takes a Stream object as input, deserialises it back to a .NET object, then returns the result after casting it to a string list. If the cast fails due to an unexpected type being deserialised, an InvalidCastException is thrown after the Deserialize method returns.

To better understand the bug, it is probably better to show the code like this:

  List<string> DeserialiseList(Stream input)
  {
    var bf = new BinaryFormatter();
    object tmp = bf.Deserialize(input);
    List<string> result = (List<string>)tmp;
    return result;
  }
			

Now, consider that the Deserialize operation will succeed as long as the data is valid, regardless of whether or not the type is List<string> or not. The exception isn't thrown until the line after, where the temporary variable is cast over to the expected type. This means that the heap still contains an instance of whatever object was deserialised, as long as the object remains in scope (i.e. within the scope of the method) and, after that, until the garbage collector decides to wipe it out.

To be clear: serialisation allows you to create representations of objects of any type marked with the Serializable attribute, but it does not allow you to create new types. The serialised format contains only the state of the object, not the type itself. The application must have access to the target type in order to deserialise it. This means that, under normal circumstances, you can't just send over your own type filled with malicious code - you can only utilise existing types.

This might seem innocuous enough, but there are types which have logic attached to them that may be executed even when the object isn't explicitly used in code. One specific area of interest is in finalisers, which are called by the GC when destroying the object. Care should be taken not to confuse these with Dispose methods, which are not explicitly called by the GC, and are used to implement elective disposal patterns to allow your code to tidy up after itself.

If a serialisable type contains finaliser code, then causing one to be deserialised will cause its finaliser code to be executed when the GC destroys it. One useful type in the .NET framework which fits the bill is the TempFileCollection type in the System.CodeDom.Compiler namespace. The purpose of the class is to maintain a list of temporary files, and delete them once an operation has been completed. In order to do this, its Dispose method iterates a list of file names and deletes them all. The Dispose method is, in turn, called by the finaliser. As such, when the GC deletes the object from the heap, the files within the list are deleted. Since we control the state of the object, we can specify that list of files. This leads to an arbitrary file deletion bug, in this case triggerable by simply opening a crafted Canape project file.

The following PoC code will generate a project file that deletes a number of files. Note that due to the fact that we have to create the object on our own system, it will attempt to delete these files on your system when you run it. You should use a VM!

var tfc = new System.CodeDom.Compiler.TempFileCollection();
tfc.AddFile(@"C:\important1.txt", false);
tfc.AddFile(@"C:\important2.txt", false);
tfc.AddFile(@"C:\important3.txt", false);
var bf = new BinaryFormatter();
using (var ms = new MemoryStream())
{
  var bw = new BinaryWriter(ms);
  bw.Write(Encoding.ASCII.GetBytes("CAPE"));
  bw.Write((UInt32)1);
  bw.Write((UInt32)3);
  bf.Serialize(ms, tfc);
  File.WriteAllBytes(@"evil.canape", ms.ToArray());
}
			

Upon opening this in Canape, you'll receive an error relating to casting a TempFileCollection. Depending on the GC's behaviour, the file will either be deleted when you click OK, a short time after that, or when Canape exits.