

Discover more from Securing Laravel
Security Tip: Encoding/Serialising Data
[Tip#36] Encoding/serialising data can be risky if you're not using the correct functions.
Greetings my friends! I’m back from Laracon EU, and slowly recovering from the letjag jetlag. If you weren’t able to attend, my talk (“Th1nk Lik3 a H4cker”) was an interactive hacking challenge the audience could take part in, and I gave away two licenses to Practical Laravel Security. It was a lot of fun and the challenges worked great! I’ll share the recording video when it is released, and I’m hoping to present it at other Laravel and PHP conferences - so hopefully there will be one near you soon. 🙂
Also, I forgot to mention previously, I have a Laracasts series: Laravel Security Through Examples! It was a lot of fun to work on this series, and it’s awesome to see it going up on Laracasts each week. Definitely go check it out! 😁
This week’s tip comes from a discussion I was having at Laracon. I was chatting to a developer who told me how they’d discovered the app they were working on used `serialize()`
and `unserialize()`
to pass data through the browser. Since this opens up a huge security risk, I felt it was good to remind everyone of why.
Looking to learn more?
⏩ Security Tip #19: Cryptographically Secure Randomness
▶️ In Depth #7: Content Security Policy
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 places1.
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 application2, 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.
Cookies, queued jobs, etc…
Or encrypted and protected like Laravel’s Cookie values