Insecure Deserialization With PHP Type Juggling

Privilege escalation with insecure deserialization with PHP type Juggling

In this blog, we will take a look at how insecure deserialization occurs along with how to exploit it by PHP type juggling in the end for escalation the privileges of a low end user.

Insecure Deserialization

Insecure deserialization is when user-controllable data is deserialized by a website. This potentially enables an attacker to manipulate serialized objects in order to pass harmful data into the application code.

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.

An object of an unexpected class might cause an exception. By this time, however, the damage may already be done. Many deserialization-based attacks are completed before deserialization is finished. This means that the deserialization process itself can initiate an attack, even if the website's own functionality does not directly interact with the malicious object. For this reason, websites whose logic is based on strongly typed languages can also be vulnerable to these techniques.

Reasons

Insecure deserialization typically arises because there is a general lack of understanding of how dangerous deserializing user-controllable data can be. Ideally, user input should never be deserialized at all.

However, sometimes website owners think they are safe because they implement some form of additional check on the deserialized data. This approach is often ineffective because it is virtually impossible to implement validation or sanitization to account for every eventuality. These checks are also fundamentally flawed as they rely on checking the data after it has been deserialized, which in many cases will be too late to prevent the attack.

PHP Serialization Format

Although there are serialization formats for every languages but in this blog we'll take a look at how PHP serialization works. The challenge is related to that

PHP uses a mostly human-readable string format, with letters representing the data type and numbers representing the length of each entry. For example, consider a User object with the attributes:

$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

The native methods for PHP serialization are serialize() and unserialize(). If you have source code access, you should start by looking for unserialize() anywhere in the code and investigating further.

Modifying serialized data types with Type Juggling.

To better understand how PHP Type Juggling works behind the scenes, refer to my other blog

pagePHP Type Juggling

Now onsider a case where this loose comparison operator is used in conjunction with user-controllable data from a deserialized object. This could potentially result in dangerous logic flaws

$login = unserialize($_COOKIE)
if ($login['password'] == $password) {
// log in successfully
}

Let's say an attacker modified the password attribute so that it contained the integer 0 instead of the expected string. As long as the stored password does not start with a number, the condition would always return true, enabling an authentication bypass. Note that this is only possible because deserialization preserves the data type. If the code fetched the password from the request directly, the 0 would be converted to a string and the condition would evaluate to false.

Be aware that when modifying data types in any serialized object format, it is important to remember to update any type labels and length indicators in the serialized data too. Otherwise, the serialized object will be corrupted and will not be deserialized.

Example

Access the lab from the below link.

Objective -> This lab uses a serialization-based session mechanism and is vulnerable to authentication bypass as a result. To solve the lab, edit the serialized object in the session cookie to access the administrator account. Then, delete Carlos.

You can log in to your own account using the following credentials: wiener:peter

Before we begin, configure burp suite to listen and capture requests in the background.

Login with the credentials provided

wiener - peter

Check the Burp history. Take a look at the GET /my-account.

The request contains a session cookie that appears to be URL and Base64-encoded. Copy the string and paste it in Burp decoder.

O:4:"User":2:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"rxmfjaplj6e0l31nhkveh1fgs7xjjftm";fQ%3d%3d

It is a PHP serialized object. Send the request to repeater.

Go to the Inspector on the right. Then click on cookie-session

Now do the following:

  • Update the length of the username attribute to 13.

  • Change the username to administrator.

  • Change the access token to the integer 0. As this is no longer a string, you also need to remove the double-quotes surrounding the value.

  • Update the data type label for the access token by replacing s with i.

O:4:"User":2:{s:8:"username";s:13:"administrator";s:12:"access_token";i:0;}

Apply the changes. We can the the value of cookie-session changes, take a note of it. Then send the request.

From the response it seems that we have access to admin panel. Right click -> Show response in browser -> Copy&Paste the URL

For ease of consistency, open devtools then paste the modified cookie-session. Refresh the page. Navigate to admin panel.

Delete the user.

Lab solved 🔥

Last updated