Privilege Escalation Via PHP Insecure Deserialization

How to escalate privileges of a user via Insecure Deserialization

In this blog we'll take a look at how to escalate privilege of a normal user to administrator by exploiting PHP Deserialization and modifying object attributes with an example from PortSwigger Web Academy

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 object attributes

When tampering with the data, as long as the attacker preserves a valid serialized object, the deserialization process will create a server-side object with the modified attribute values.

As a simple example, consider a website that uses a serialized User object to store data about a user's session in a cookie. If an attacker spotted this serialized object in an HTTP request, they might decode it to find the following byte stream:

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

The isAdmin attribute is an obvious point of interest. An attacker could simply change the boolean value of the attribute to 1 (true), re-encode the object, and overwrite their current cookie with this modified value. In isolation, this has no effect. However, let's say the website uses this cookie to check whether the current user has access to certain administrative functionality:

$user = unserialize($_COOKIE);
if ($user->isAdmin === true) {
// allow access to admin interface
}

This vulnerable code would instantiate a User object based on the data from the cookie, including the attacker-modified isAdmin attribute. At no point is the authenticity of the serialized object checked. This data is then passed into the conditional statement and, in this case, would allow for an easy privilege escalation.

This simple scenario is not common in the wild. However, editing an attribute value in this way demonstrates the first step towards accessing the massive amount of attack-surface exposed by insecure deserialization.

Example

Access the lab from the below link.

Objective -> This lab uses a serialization-based session mechanism and is vulnerable to privilege escalation as a result. To solve the lab, edit the serialized object in the session cookie to exploit this vulnerability and gain administrative privileges. Then, delete Carlos's account.

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.

Access the lab

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:5:"admin";b:0O30%3d

We have PHP serialized object. The admin attribute contains b:0, indicating the boolean value false. Send this request to Burp Repeater.

Check the inspector tab on the right side.

Click on the arrow '>' next to the cookie value.

Change the admin value to 1 as highlighted above and click on 'Apply Changes'

We can see that the Cookie session value is slightly changed, we can take a note of that. After sending the request we can see that the user now has access to /admin portal. Right click on it and select 'Show response in browser'

Copy and paste the link.

Turn on the proxy and click on the Admin panel above.

Now change the value of cookie-session to that we've modified in the repeater.

Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjY6IndpZW5lciI7czo1OiJhZG1pbiI7YjoxO30%3d

Forward the request.

Now we can delete the user carlos. As we did above click on the delete button -> intercept the request -> replace the cookie-session value -> Forward the request.

Lab solved 🔥

Update - You can also paste the modified cookie-session value in the devtools storage section and then refresh the page. This saves the time from copy-paste method

Last updated