Security Tip: Encoding/Serialising Data

[Tip#36] Encoding/serialising data can be risky if you're not using the correct functions.

Security Tip: Encoding/Serialising Data

Let’s start with a simple question:

Have you ever used the serialize() and unserialize() functions?

If you’ve been doing PHP for a while, you would’ve used them before. They were the solution to turning complex data structures into strings before JSON became a thing. Even now they are still used quite frequently - Laravel itself uses them in a bunch of places.

However, these functions are incredibly unsafe to use. They translate anything into a string, and then can turn it back into any value. This includes classes/objects, which means that you can boot any class available in the application, passing in any parameters you like. If the payload is contained entirely within the application, this is not a risk as the value cannot be changed, however if the payload is passed through the browser, it can be manipulated by the user.

When unserialise() decodes a value, it builds any classes contained within the value and fires off any __unserialize() or __wakeup() methods. In the right circumstances, these methods can be used to execute code (i.e. RCE, Remote Code Execution) within the app, giving the attacker full control.

The solution here is to never use serialize() and unserialize() where a user might be able to modify the value. If you need to pass complex data structures around, you can encrypt or sign the value to prevent tampering, but in virtually all cases what you’re really looking for is json_encode() and json_decode(). These are safe to use when passing through the browser.

Note, I’m not saying serialize() and unserialize() aren’t useful, they are incredibly useful in many use cases, but when it comes to user-data, avoid them at all costs.

For example…

An application might use serialize() to turn the user settings into a string that can be shared between instances of the app.

To serialise:

> serialize(['setting1' => 'value1', 'setting2' => 'value2']);
= "a:2:{s:8:"setting1";s:6:"value1";s:8:"setting2";s:6:"value2";}"

To unserialise:

> unserialize("a:2:{s:8:\"setting1\";s:6:\"value1\";s:8:\"setting2\";s:6:\"value2\";}");
= [ "setting1" => "value1", "setting2" => "value2" ]

Given it’s passing through the browser, an attacker could modify the payload using a tool like PHPGGC: PHP Generic Gadget Chains to execute some malicious PHP:

$ phpggc -a Laravel/RCE6 "dd(config());"

O:29:"Illuminate\Support\MessageBag":2:{S:11:"\00*\00messages";a:0:{}S:9:"\00*\00format";O:40:"Illuminate\Broadcasting\PendingBroadcast":2:{S:9:"\00*\00events";O:25:"Illuminate\Bus\Dispatcher":1:{S:16:"\00*\00queueResolver";a:2:{i:0;O:25:"Mockery\Loader\EvalLoader":0:{}i:1;S:4:"load";}}S:8:"\00*\00event";O:38:"Illuminate\Broadcasting\BroadcastEvent":1:{S:10:"connection";O:32:"Mockery\Generator\MockDefinition":2:{S:9:"\00*\00config";O:35:"Mockery\Generator\MockConfiguration":1:{S:7:"\00*\00name";S:7:"abcdefg";}S:7:"\00*\00code";S:28:"<?php dd(config()); exit; ?>";}}}}

This code would dump the entire contents of the config array to the screen, exposing any API keys, encryption keys, database connections, passwords, etc… all sorts of sensitive data.


If you found this security tip useful, subscribe to get weekly Security Tips straight to your inbox. Upgrade to a premium subscription for exclusive monthly In Depth articles, or drop a coin in the tip jar to show your support.

When was the last time you had a penetration test? Book a Laravel Security Audit and Penetration Test, or a budget-friendly Security Review!

You can also connect with me on Bluesky, or other socials, and check out Practical Laravel Security, my interactive course designed to boost your Laravel security skills.