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