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[1].

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[2], 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.

  1. Cookies, queued jobs, etc…

  2. Or encrypted and protected like Laravel’s Cookie values

👉 Looking to dive deeper into Laravel security? Check out Practical Laravel Security, my hands-on security course that uses interactive hacking challenges to teach you about how vulnerabilities work, so you can avoid them in your own code! 🕵️

⚠️ Want me to hack into your app and tell you how I did it, so you can fix it before someone else finds it? Book in a Laravel Security Audit and Penetration Test! 🕵️