Deserialization 101

César Silva <@apl3b/>

Can we kill it?

What is it?

What is it?

A8 - Insecure Deserialization

Serialization is the process of turning some object into a data format that can be restored later

Deserialization is the process of restore the serialized data

or

Reverse the serialization process

Formats

  • Binary Data

  • Text

    • XML

    • JSON

  • Custom Binary Formats

Reflection

Ability to examine, introspect and modify its own structure at runtime

class Test{
    private string Name { get; set; }
    public string formatName(){
        return "My name is :" + this.Name;
    }
}

// Reflection
class Main{
    public void Main(string[] args){
        Object test = Activator.CreateInstance("my.name.space.Test");
        var my_function = test.GetType().GetMethod("formatName");
        my_function.Invoke(test, null);
    }
}

Vectors

  • Default constructor and setters
  • Special Constructor or "deserialization callbacks"
  • Default constructor and reflection

Default Constructor and Setters

The serializer creates a new object using the default parameter-less constructor and uses setters to populate all the fields

More limited because we can only access public properties

Default Constructor and Setters

<src = "https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-Json-Attacks.pdf">

Special Constructor and "Deserialization Callbacks"

The serializer creates a new object using special constructors to trigger special logic required to initialize an object

Json.Net's [OnError]
.NET's [OnDeserialized]

.NET's [OnDeserializing]

etc

Special Constructor and "Deserialization Callbacks"

public final class SerializableRenderedImage implements RenderedImage, Serializable {
    protected void finalize() throws Throwable {
        dispose();
        // Triggers the payload when called by the garbage collection
        super.finalize();
    }
    ...
}

Default Constructor and Reflection

The serializer creates a new object using the default parameter-less constructor and uses reflection to populate all the fields

Vulnerable on destructors, some reconstructions invocations (i.e.: HashTables) and common calls (toString())

Default Constructor and Reflection

//Newtonsoft.Json.Converters.EntityKeyMemberConverter
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
        JsonSerializer serializer)
{
    EntityKeyMemberConverter.EnsureReflectionObject(objectType);
    object obj = EntityKeyMemberConverter._reflectionObject.Creator(new object[0]);
    EntityKeyMemberConverter.ReadAndAssertProperty(reader, "Key");

    reader.ReadAndAssert();
    EntityKeyMemberConverter._reflectionObject.SetValue(obj, "Key",
        reader.Value.ToString());
    EntityKeyMemberConverter.ReadAndAssertProperty(reader, "Type");
    reader.ReadAndAssert();

    Type type = Type.GetType(reader.Value.ToString());
    EntityKeyMemberConverter.ReadAndAssertProperty(reader, "Value");
    reader.ReadAndAssert();

    EntityKeyMemberConverter._reflectionObject.SetValue(obj, "Value",
        serializer.Deserialize(reader, type));
    reader.ReadAndAssert();

    return obj;
}

C#

using Newtonsoft.Json;

namespace deserialization_training
{
    public static class SerializerHelper
    {
        public static dynamic Deserialize(string json)
        {
            // Use dynamic to use properties independently from the type
            return JsonConvert.DeserializeObject<dynamic>(json,
                // Include type information to allow for a more smooth casting and check
                new JsonSerializerSettings {
                    TypeNameHandling = TypeNameHandling.All
            });
        }

        public static string Serialize(dynamic obj) {
            return JsonConvert.SerializeObject(obj);
        }
    }
}

Payload Example

{
    '$type':'System.Windows.Data.ObjectDataProvider,\
             PresentationFramework,\
             Version=4.0.0.0,\
             Culture=neutral,\
             PublicKeyToken=31bf3856ad364e35',
    'MethodName':'Start',
    'MethodParameters':{
        '$type':'System.Collections.ArrayList,\
                 mscorlib,\
                 Version=4.0.0.0,\
                 Culture=neutral,\
                 PublicKeyToken=b77a5c561934e089',
        '$values':['cmd','/c calc']
    },
    'ObjectInstance':{'$type':'System.Diagnostics.Process,\
                               System,\
                               Version=4.0.0.0,\
                               Culture=neutral,\
                               PublicKeyToken=b77a5c561934e089'}
}

Java

import java.io.ObjectInputStream;
import java.io.FileInputStream;
import java.io.ObjectOutputStream;
import java.io.FileOutputStream;
import java.io.Serializable;
import java.io.IOException;

public class SerializeExample {
    public static void main(String args[]) throws Exception {
        VulnerableObject testObj = new VulnerableObject();
        testObj.name = "test";
        ObjectOutputStream oStream = new ObjectOutputStream(new FileOutputStream("test.bin"));
        oStream.writeObject(testObj);
        oStream.close();
        ObjectInputStream iStream = new ObjectInputStream(new FileInputStream("test.bin"));
        VulnerableObject readObj = (VulnerableObject)iStream.readObject();
        System.out.println(readObj.name);
        iStream.close()
    }
}

class VulnerableObject implements Serializable {
    public String name;
    private void readObject(ObjectInputStream in)
    throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        this.name = "Class_" + this.name;
    }
}

0xAC 0xED

rO0

Python

import pickle

class User(object):
    def __init__(self, name):
        self.name = name

serialized = pickle.dumps(User('Some Username')))
user = pickle.loads(serialized)

Cool Resources

Tools

Materials

Can we kill it?

C#

using Newtonsoft.Json;

namespace deserialization_training
{
    public static class SerializerHelper<T>
    {
        public static T Deserialize(string json)
        {
            // Set a type to limit the object's tree
            return JsonConvert.DeserializeObject<T>(json,
                // Do not use TypeNameHandling. If needed, define a CustomParser
                new JsonSerializerSettings {
                    TypeNameHandling = TypeNameHandling.None
            });
        }

        public static string Serialize(T obj) {
            return JsonConvert.SerializeObject(obj);
        }
    }
}

Java

public class SecureObjectInputStream extends ObjectInputStream {

    public SecureObjectInputStream(InputStream inputStream) throws IOException {
        super(inputStream);
    }

    /**
     * Only deserialize instances of our expected class
     */
    @Override
    protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException,
            ClassNotFoundException {
        if (!desc.getName().equals(VulnerableObject.class.getName())) {
            throw new InvalidClassException(
                    "Unauthorized deserialization attempt",
                    desc.getName());
        }
        return super.resolveClass(desc);
    }
}

Python

Use a secure module for deserialization

 hmac (verify the object's integrity)

write your own parser

Recap

  • Validate all inputs
  • Do not trust external data
  • Do not allow for dynamic typing
  • In last case, write custom parsers to enforce typing

?

Thank you

Deserialization 101

By apl3b

Deserialization 101

101 on Deserialization Vulnerabilities

  • 423