PHP Object Serialization

Introduction

This post will give a basic explanation of PHP (de)serialization and how such mechanism will be exploited in PHP Object Injection (POI).

Background

The purpose of serialization is to make it easier to transfer objects.
serialize() will transform a PHP object to a string
unserialize() will try to transform a string back to a PHP object

PHP Serialized Format

In PHP serialization, different types of objects are represented with different types of symbols as below:

boolean b:<value>
integer i:<value>
double  d:<value>
reference R:<id>
NULL    N
string  s:<length>:"<value>"
array   a:<length>:{key, value pairs}
object  O:<class_name_length>:"<class_name>":<number_of_properties>:{<properties>}

For a concrete example like:

<?php
class Foobar{
	private $state = 'Inactive';
	public function set_state($state){
		$this->state = $state;
	}

	public function get_state(){
		return $this->state;
	}
}
?>

With some simple PHP script to write the serialized object to a local file, we can get the following output:

Serialized Object Format
O:6:"Foobar":1:{s:13:"Foobarstate";s:6:"Active";}

hexdump -C serialized.txt 
00000000  4f 3a 36 3a 22 46 6f 6f  62 61 72 22 3a 31 3a 7b  |O:6:"Foobar":1:{|
00000010  73 3a 31 33 3a 22 00 46  6f 6f 62 61 72 00 73 74  |s:13:".Foobar.st|
00000020  61 74 65 22 3b 73 3a 36  3a 22 41 63 74 69 76 65  |ate";s:6:"Active|
00000030  22 3b 7d                                          |";}|
00000033

Magic Method

Magic methods are automatically executed upon specific events [1].

__wakeup(): unserialize() checks for the presence of a function with the magic name __wakeup(). If present, this function can reconstruct any resources that the object may have.
__destruct(): the destructor method will be called as soon as there are no other references to a particular object, or in any order during the shutdown sequence.

PHP Object Injection and Property Oriented Programming

The vulnerability exists when using unsafe deserialization methods on untrusted input: unserialize()[2]
Usually there will be some useful POP chain methods in PHP code:
(1)Command Execution: exec(), passthru(), popen(), system()
(2)File Access: file_get_contents(), file_put_contents(), unlink()

We use the following code to demonstrate the usage of a simple POP gadget.

//DemoPopChain.php
<?php
class DemoPopChain{

	private $data = "bar\n";
	private $filename = '/tmp/foo';

	public function __wakeup(){
		$this->save($this->filename);
	}

	public function save($filename){
		file_put_contents($filename, $this->data);
	}
}
?>

//poc.php
<?php
require('./DemoPopChain.php');
$foo = new DemoPopChain();
file_put_contents('./serialized.log', serialize($foo));
?>

//unserialize.php
<?php
require('./DemoPopChain.php');
unserialize(file_get_contents('./serialized.log'));
?php>

For the user that use the expected serialized object as below:

00000000  4f 3a 31 32 3a 22 44 65  6d 6f 50 6f 70 43 68 61  |O:12:"DemoPopCha|
00000010  69 6e 22 3a 32 3a 7b 73  3a 31 38 3a 22 00 44 65  |in":2:{s:18:".De|
00000020  6d 6f 50 6f 70 43 68 61  69 6e 00 64 61 74 61 22  |moPopChain.data"|
00000030  3b 73 3a 34 3a 22 62 61  72 0a 22 3b 73 3a 32 32  |;s:4:"bar.";s:22|
00000040  3a 22 00 44 65 6d 6f 50  6f 70 43 68 61 69 6e 00  |:".DemoPopChain.|
00000050  66 69 6c 65 6e 61 6d 65  22 3b 73 3a 38 3a 22 2f  |filename";s:8:"/|
00000060  74 6d 70 2f 66 6f 6f 22  3b 7d                    |tmp/foo";}|
0000006a

We will get the following result as expected:

$php poc.php
$php unserilize.php

$cat /tmp/foo
bar

If we use the maliciously crafted serialized object as below:

00000000  4f 3a 31 32 3a 22 44 65  6d 6f 50 6f 70 43 68 61  |O:12:"DemoPopCha|
00000010  69 6e 22 3a 32 3a 7b 73  3a 31 38 3a 22 00 44 65  |in":2:{s:18:".De|
00000020  6d 6f 50 6f 70 43 68 61  69 6e 00 64 61 74 61 22  |moPopChain.data"|
00000030  3b 73 3a 36 3a 22 64 61  6e 67 6f 0a 22 3b 73 3a  |;s:6:"dango.";s:|
00000040  32 32 3a 22 00 44 65 6d  6f 50 6f 70 43 68 61 69  |22:".DemoPopChai|
00000050  6e 00 66 69 6c 65 6e 61  6d 65 22 3b 73 3a 31 30  |n.filename";s:10|
00000060  3a 22 2f 74 6d 70 2f 64  61 6e 67 6f 22 3b 7d 0a  |:"/tmp/dango";}.|
00000070

In this crafted data, we change the data to “dango” and filename is changed to “/tmp/dango”. After that, the final result is given below:

$cat /tmp/dango 
dango

Reference

[1] http://php.net/manual/en/language.oop5.magic.php
[2] https://www.insomniasec.com/downloads/publications/Practical%20PHP%20Object%20Injection.pdf

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.