Insecure deserialisation is often seen as a very hard vulnerability type but it doesn’t have to be. It does require decent knowledge of the programming languages in question but it can also occur very trivially if you have some knowledge of the programming languages.
In this module we will be looking at PHP and Ruby deserialisation processes by practical examples on the portswigger labs. This will allow us to better understand the concept.
Java serialization and deserialisation use binary formats which are harder to read and out of the scope of this document.
If we want to learn about deserialisation processes we first need to learn about what serialization is. When we talk about serialization, we are talking about the processing complex structures such as objects (For example a person with an age,sex and name) into a much flatter format so that it can be sent and received in a sequential stream of bytes. This allows us to write complex data structures to memory, files or databases and also to send that data over the network to different API’s.
When we serilalise data, we save it’s attributes and their values, this is really important to remember. Such as a female person of 16 years of age with the name “Sophie Kent” will get turned into something like {female|16|Sophie|kent}
For example, consider a User object with the attributes in PHP:
$user->name = "carlos";
$user->isLoggedIn = true;
When serialized, this object may look something like this:
O:4:"User":2:{s:4:"name":s:6:"carlos"; s:10:"isLoggedIn":b:1;}
This can be interpreted as follows:
O:4:"User" - An object with the 4-character class name "User"
2 - the object has 2 attributes
s:4:"name" - The key of the first attribute is the 4-character string "name"
s:6:"carlos" - The value of the first attribute is the 6-character string "carlos"
s:10:"isLoggedIn" - The key of the second attribute is the 10-character string "isLoggedIn"
b:1 - The value of the second attribute is the boolean value true
Python refers to serialization as pickling
Ruby refers to serialization as marshalling
When we Deserialize we the opposite, we use the bytestream that was created and turn it back into an object. The avid hacker will have already spotted where this can go wrong.
How exactly the seriliasation happens depends heavily on the programming language, some might turn the objects into binary formats where others might use different string formats. Some are easy to read, some are very hard to read.
Wherever user input is involved we have a potential for issues either now or later down the line. In case of deserialisation that time is right now. It is very hard to securely serialize or deserialize user input properly because the developer will have to create their own filtering and blacklists. If you think about it, modern websites have so many dependencies and libraries that it’s impossible to predict which methods an attacker can invoke. If the attacker is really determined they will even chain different methods while they pass data via sinks.
What’s alarming about Insecure deserialization is that the attack occurs even before the deserialization is complete. There is a general lack of understanding the whole deserialization process and how dangerous it can be to Deserialise user input.
Even adding extra checks on the Deserialised data is going to be pretty ineffective as the defect occurs before the deserialization process.
Ideally you should NEVER deserialise user input!
Because of the way this process works we can replace serialize objects with any object the web application has access to. This is also the reason why Insecure deserialization is sometimes refered to as object injection.
Identifying Insecure deserialization can usually be fairly straight forward to identify. Different languages all use a specific format and if you know what it looks like it can be easy to identify.
O:4:"User":2:{s:4:"name":s:6:"carlos"; s:10:"isLoggedIn":b:1;}
We’ve already looked at this example before, PHP will serialize data in a mostly human readable format.
O:4:"User" - An object with the 4-character class name "User"
2 - the object has 2 attributes
s:4:"name" - The key of the first attribute is the 4-character string "name"
s:6:"carlos" - The value of the first attribute is the 6-character string "carlos"
s:10:"isLoggedIn" - The key of the second attribute is the 10-character string "isLoggedIn"
b:1 - The value of the second attribute is the boolean value true
marshalled = Marshal.dump([1, 2, 'string', Object.new])
This dump method will create a seriliazed object. Our input contains numbers, a string and even an object. We can easily seriliaze those into one and then later deserialize it when we need it.
Result => "\\x04\\b[\\ti\\x06i\\aI\\"\\vstring\\x06:\\x06ETo:\\vObject\\x00"
As you can see Ruby seems to indicate it’s “marshalled” data (remember that marshalling is ruby’s term for serilization) with hexadecimal notations (\x04).
What matters most if we encounter this is that we can create a very simple ruby script to deserialize this object for us.
Marshal.load("\\x04\\b[\\ti\\x06i\\aI\\"\\vstring\\x06:\\x06ETo:\\vObject\\x00")
# => [1, 2, "string", #<Object:0x00000002643000>]
We can then make any changes we need to make and re-encode it with the marshal dump method we saw earlier.
marshalled = Marshal.dump([2, 2, 'string', Object.new])
We can exploit this defect in two ways, I am mostly going to cover the functional way in this document because that’s how i hunt and object injection usually requires access to the source code. I will also cover the object injection method to a certain degree so you can become accustomed to it and i hope that you will look into it further if you are interested in it.
When we are testing purely functionality we are relying on the fact that we might find one of these serialized objects somewhere that we can manipulate the value. Portswigger labs uses the cookies as an example but it can also occur in other locations such as a hidden field in a form that might send a seriliazed object. Another location we can find serliliazed objects is in the javascript code as it might need to pass on one of those values to the back-end.
If we have a look at the portswigger labs we can see some easy PHP examples. All of these rely on a cookie being set as a seriliazed PHP object. If you check the cookies you should find something similar to our example. You can simply edit this cookie to do things you should not be able to do. A basic example of this would be making yourself admin by changing a boolean which you can find in the following lab:
Programming languages like PHP have something that’s known as loose comperison operators. In PHP this is an example.
if("a"=="b")
This == is called an operator and in this case two = signs means that PHP will check the values of the provided variables but not the types. This means the following condition would be true if the real password does not start with a number and we are able to manipulate the password to be 0.
$login = unserialize($_COOKIE)
if ($login['password'] == $password)
This last lab is actually very clever as it tries to get you to chain several vulnerabilities together. You have to delete a file on the server which can be very destructive but deleting that file is not so easy. After all, how are you going to delete a file on a webserver you do not even own?
This lab also has seriliazed data in it’s cookies and this time it allows you to set a user avatar by providing a link. This does not seem very impactful as simply setting an avatar does not cause much damage so let’s move on.
A smart hacker however is thinking about what their possibilities are and they find out that whenever they delete their account, the avatar picture also gets deleted by creating a new account and trying to browse to his old avatar where they are greeted by a 404 — not found message. This behaviour gives the hacker an idea, he will set the avatar’s location to the file he wants to delete and deletes the account. Suddenly they can’t surf the website anymore and they smile knowing they just deleted all the config files
All of these labs talk about PHP seriliazation and deseriliazation but ofcourse all of these attacks are also possible on any other language since they all revolve about simply deserializing these objects ourselves while we make some changes and serilialize the payload again (By looking up what commands to use in the language manual and writing a simple script).
In this lab you will learn that you view the source code a file by entering the tilde sign after the filename (~). This works because some editors and IDEs create backup files which they mark with a tilde. Sometimes developers forget that they should never upload these files.
When going through the source code we can find a __destruct() method that will invoke the unlink() method on the lock_file_path attribute, which will delete the file on this path according to PHPs manual.
unlink ( string $filename , resource $context = ? ) : bool
==> Deletes filename. Similar to the Unix C unlink() function. An E_WARNING level error will be generated on failure.
The website is making constant references to some CustomTemplate PHP site so let’s apply our tilde trick here.
https://[[YOUR-LAB-ID]].web-security-academy.net/libs/CustomTemplate.php~
this gives us a cool source code file with all of those methods we talked about. The destruct method is a so called magic method which will be envoked when a serialized object is destroyed which happens when we delete a serialized object.
<?phpclass CustomTemplate {
private $template_file_path;
private $lock_file_path; public function __construct($template_file_path) {
$this->template_file_path = $template_file_path;
$this->lock_file_path = $template_file_path . ".lock";
} private function isTemplateLocked() {
return file_exists($this->lock_file_path);
} public function getTemplate() {
return file_get_contents($this->template_file_path);
} public function saveTemplate($template) {
if (!isTemplateLocked()) {
if (file_put_contents($this->lock_file_path, "") === false) {
throw new Exception("Could not write to " . $this->lock_file_path);
}
if (file_put_contents($this->template_file_path, $template) === false) {
throw new Exception("Could not write to " . $this->template_file_path);
}
}
} **function __destruct() {**
// Carlos thought this would be a good idea
if (file_exists($this->lock_file_path)) {
unlink($this->lock_file_path);
}
}
}?>
With all of this information we are able to solve the labs. Since there is a serialized object in our cookies we can use that to construct a serialized object that contains the lock_file_path pointing towards a file we want to delete. If we now replace our session cookie with the modified session cookie, the server will invoke the __destruct method automatically and destroy the file we were targeting.
NEVER DELETE IMPORTANT OR RANDOM FILES IN BUG BOUNTIES OR PENTESTING!
Ofcourse object injection and functional testing are not the only two things we can do here. You can also learn more about:
https://stackoverflow.com/questions/410186/how-does-ruby-serialization-marshaling-work
https://portswigger.net/web-security/deserialization/exploiting