unserialize() data from untrusted source

php.internals

Harry Fuecks

21 years ago
Hi All, Have a situation where I want to unserialize a string received from an untrusted source over HTTP (a Javascript client in this case). For basic types this is no concern but when it comes to objects, would be nice to be able to restrict the class of object to a member of a known list, to prevent "unplanned objects" being created from classes which happened to be defined but were not intended for unserialization (such as the growing number pre-loaded classes in PHP5), and the possible security issues that might introduce. Checking the type of class once the object has been created might be too late, depending on what the constructor does. That leaves manually parsing the serialized string in PHP, before called unserialize, as the only really safe option. Could be this is outside of the scope of intended use of unserialize() but PHP's serialized representation of data makes a pretty nice encoding for exchange with other systems and I notice others doing the same e.g.; http://www.aagh.net/projects/ruby-php-serialize http://hcs.harvard.edu/~pli/code/serialPHP.pm http://www.cpan.org/modules/by-module/PHP/JBROWN/php-serialization/ http://hurring.com/code/perl/serialize/ Serialized data is also often used with sessions which means the usual issues with shared hosts and session files (OK - smarter users avoid this but...) Perhaps unserialize could take a second (optional) argument which is a list of allowed classes to validate against. Many thanks, Harry

Jason Garber

21 years ago
Hello Harry, This is an interesting point you bring up. When we have large registration processes or similar multi-page forms, we write our data array to a hidden field using. base64_encode(serialize($aData)) and read it in with unserialize(base64_decode($_POST['aData'])) passing it from page to page with POST. If I understand you correctly, someone could alter the content of the serialized array so that it would load a class upon unserialization? If you are auto-loading classes, this might be even worse. That being the case, I would be much in favor of an optional second array parameter to unserialize that would be a list of accepted classes, or an empty array to that would (obviously) allow no classes (if you were working with simple data types). mixed unserialize ( string str[, array classes]) I'd be interested to hear other comment on this.
-- Best regards, Jason mailto:jason@ionzoft.com Sunday, September 5, 2004, 11:29:53 AM, you wrote: HF> Hi All, HF> Have a situation where I want to unserialize a string received from an HF> untrusted source over HTTP (a Javascript client in this case). For HF> basic types this is no concern but when it comes to objects, would be HF> nice to be able to restrict the class of object to a member of a known HF> list, to prevent "unplanned objects" being created from classes which HF> happened to be defined but were not intended for unserialization (such HF> as the growing number pre-loaded classes in PHP5), and the possible HF> security issues that might introduce. HF> Checking the type of class once the object has been created might be HF> too late, depending on what the constructor does. That leaves manually HF> parsing the serialized string in PHP, before called unserialize, as HF> the only really safe option. HF> Could be this is outside of the scope of intended use of unserialize() HF> but PHP's serialized representation of data makes a pretty nice HF> encoding for exchange with other systems and I notice others doing the HF> same e.g.; HF> http://www.aagh.net/projects/ruby-php-serialize HF> http://hcs.harvard.edu/~pli/code/serialPHP.pm HF> http://www.cpan.org/modules/by-module/PHP/JBROWN/php-serialization/ HF> http://hurring.com/code/perl/serialize/ HF> Serialized data is also often used with sessions which means the usual HF> issues with shared hosts and session files (OK - smarter users avoid HF> this but...) HF> Perhaps unserialize could take a second (optional) argument which is a HF> list of allowed classes to validate against. HF> Many thanks, HF> Harry

Harry Fuecks

21 years ago
On Sun, 5 Sep 2004 15:33:28 -0400, Jason Garber <jason@ionzoft.com> wrote:
> This is an interesting point you bring up. When we have large > registration processes or similar multi-page forms, we write our > data array to a hidden field using. > > base64_encode(serialize($aData)) > > and read it in with > > unserialize(base64_decode($_POST['aData'])) > > passing it from page to page with POST.
That's another scenario, and perhaps quite a common one, I hadn't thought of. I guess in this case, as the serialized string was originally generated by your code, you could attach a checksum with it to spot alteration but validating classes against a list would be easier. Checksums only really works though when your PHP script is doing all the serializing; not an option when a remote, untrusted, system will be doing the serializing.
> If I understand you correctly, someone could alter the content of > the serialized array so that it would load a class upon > unserialization?
Exactly.
> If you are auto-loading classes, this might be > even worse.
Good point - hadn't considered that.
> > That being the case, I would be much in favor of an optional second > array parameter to unserialize that would be a list of accepted > classes, or an empty array to that would (obviously) allow no > classes (if you were working with simple data types). > > mixed unserialize ( string str[, array classes])
There may be an issue with returned value, come to think of it. Right now unserialize returns FALSE (plus triggers and error) if it was unable to parse the serialized string. Might be a requirement to be able to identify invalid syntax from attempts to load an invalid class (another callback or an exception?).

Markus Fischer

21 years ago
Jason Garber wrote:
> This is an interesting point you bring up. When we have large > registration processes or similar multi-page forms, we write our > data array to a hidden field using. > > base64_encode(serialize($aData)) > > and read it in with > > unserialize(base64_decode($_POST['aData'])) > > passing it from page to page with POST.
I fail to understand, in your scenario, why you don't simply save the data in a session? You're effectively generated some data server and send it to the client only to get it back on the next request; typical session scenario, if you ask me. cheers, - Markus

Jason Garber

21 years ago
Hello Markus, I've done both many times. There are many ways to do this type of thing, but the way I described it is very clean because the data is always in sync with the page (due to the fact that the data is on the page). If you save the data to a session, and then click back a couple times, it can really mess with things, creating a much more complex scenario to deal with. As I said, over the years I've done both, and this is what I've settled on as the most practical.
-- Best regards, Jason mailto:jason@ionzoft.com Monday, September 6, 2004, 1:28:22 AM, you wrote: MF> Jason Garber wrote: >> This is an interesting point you bring up. When we have large >> registration processes or similar multi-page forms, we write our >> data array to a hidden field using. >> >> base64_encode(serialize($aData)) >> >> and read it in with >> >> unserialize(base64_decode($_POST['aData'])) >> >> passing it from page to page with POST. MF> I fail to understand, in your scenario, why you don't simply save the MF> data in a session? MF> You're effectively generated some data server and send it to the client MF> only to get it back on the next request; typical session scenario, if MF> you ask me. MF> cheers, MF> - Markus

Sascha Schumann

21 years ago
> As I said, over the years I've done both, and this is what I've > settled on as the most practical.
Then you should have learned by now that verifying your data's integrity is a mandatory task when designing any client-side session system. Otherwise, attackers can inject any kind of data into your system where the falsified data will be viewed as 'trusted'. Once you add the integrity check, you have also eliminated the possibility that arbitrary classes could be instantiated. - Sascha

Sara Golemon

21 years ago
> Have a situation where I want to unserialize a string received from an > untrusted source over HTTP (a Javascript client in this case). For > basic types this is no concern but when it comes to objects, would be > nice to be able to restrict the class of object to a member of a known > list, to prevent "unplanned objects" being created from classes which > happened to be defined but were not intended for unserialization (such > as the growing number pre-loaded classes in PHP5), and the possible > security issues that might introduce. >
if (preg_match_all('(^|:|\{)O:\d+:(.*?):', $serializedString, $matches, PREG_PATTERN_ORDER)) { /* Serialized data contains objects */ foreach($matches[1] as $match) { $class = trim($match, "'\""); if (in_array($class, $bad_classes)) die("Bad hacker, no cookie for you!"); } } Something along the lines of the above should do the trick, it's got some shortcomings, but they're the type to give false positives rather than act as security holes..... Though considering the fact that it'd be more trustable/reliable to implement in unserialize itself and not a complicated check to include anyway, I'd say "sure, why not".