Security Tip: Can You Safely Unserialise Classes?
[Tip #95] While you really shouldn't unserialise anything you get from a user, occasionally you have no choice... so how do you do it safely?
Don't use unserialize()
ever!
Avoid unserialize()
at all costs!
You really shouldn't use unserialize()
...
unserialize()
is one of those PHP functions you don't actually want to use...
There are few legitimate uses for unserialize()
.
Hrm... how can I put this? 🤔
unserialize()
is a double-edged sword.
It can be used very effectively alongside serialize()
to pass complex data structures around, and make your life as a developer easier. Laravel uses it under the hood in many places for this specific reason. But it can also be a nightmare if used incorrectly - for example, if you have to unserialize()
any data you receive from a user. We've covered this before.
unserialize()
at all costs. Use JSON, even if it's ugly and complex, use JSON! Please!However, if you really do need to pass untrusted user data into unserialize()
, your only protection is the allowed_classes
option. It allows you to specify a list of classes which are allowed to be unserialised. Any other classes will be unserialised as __PHP_Incomplete_Class
, which will prevent them from triggering __wakeup()
or __destruct()
methods, that can allow Remote Code Execution (RCE).
As a simple example, consider this serialised array that contains two objects:
a:2:{
s:11:"unsafeUuser";O:10:"UnsafeUser":1:{s:4:"name";s:8:"evil.php";}
s:8:"safeUser";O:8:"SafeUser":1:{s:4:"name";s:8:"evil.php";}
}
There are two classes in there: UnsafeUser
and SafeUser
. We only want to allow SafeUser
to be unserialised.
return unserialize($serialised, [
'allowed_classes' => ['SafeUser']
]);
Would produce the following:
array(2) {
["unsafeUser"]=>
object(__PHP_Incomplete_Class)#8 (2) {
["__PHP_Incomplete_Class_Name"]=>
string(10) "UnsafeUser"
["name"]=>
string(8) "evil.php"
}
["safeUser"]=>
object(SafeUser)#6 (1) {
["name"]=>
string(8) "evil.php"
}
}
You'll note that SafeUser
is set back up as an object (object(SafeUser)#6
), but UnsafeUser
is loaded as object(__PHP_Incomplete_Class)#8
instead.
So if you really need to go down the path of unserialising untrusted data, check out allowed_classes
and get it configured to keep your app secure.
Check out the PHP docs to learn more about configuring unserialize()
.
Found this security tip helpful? Don't forget to subscribe to receive new Security Tips each week, and upgrade to a premium subscription to receive monthly In Depth articles, or toss a coin in the tip jar.
Reach out if you're looking for a Laravel Security Audit and Penetration Test or a budget-friendly Security Review, and find me on the various socials through Pinkary. Finally, don't forget to check out Practical Laravel Security, my interactive security course.