Php rfc: new custom object serialization mechanism

Deserializing

When you are ready to operate on the object again, you can deserialize the string with unserialize().

<?phpclass User{public $username;public $status;}$user = new User;$user->username = 'vickie';$user->status = 'not admin';$serialized_string = serialize($user);$unserialized_data = unserialize($serialized_string);var_dump($unserialized_data);var_dump($unserialized_data);?>

Unserialize() under the hood

So how does unserialize() work under the hood? And why does it lead to vulnerabilities?

What are PHP magic methods?

PHP magic methods are function names in PHP that have “magical” properties. Learn more about them here.

The magic methods that are relevant for us now are __wakeup() and __destruct(). If the class of the serialized object implements any method named __wakeup() and __destruct(), these methods will be executed automatically when unserialize() is called on an object.

Step 1: Object instantiation

Instantiation is when the program creates an instance of a class in memory. That is what unserialize() does. It takes the serialized string, which specifies the class and the properties of that object. With that data, unserialize() creates a copy of the originally serialized object.

It will then search for a function named __wakeup(), and execute code in that function. __wakeup() reconstructs any resources that the object may have. It is used to reestablish any database connections that have been lost during serialization and perform other reinitialization tasks.

Step 2: Program uses the object

The program operates on the object and uses it to perform other actions.

Step 3: Object destruction

Finally, when no reference to the deserialized object instance exists, __destruct() is called to clean up the object.

Exploiting PHP deserialization

When you control a serialized object that is passed into unserialize(), you control the properties of the created object. You might also be able to hijack the flow of the application by controlling the values passed into automatically executed methods like __wakeup() or __destruct().

This is called a PHP object injection. PHP object injection can lead to variable manipulation, code execution, SQL injection, path traversal, or DoS.

Proposal

The proposal is to amend unserialize() function, allowing to either completely prohibit restoring objects or restrict the objects being restored to a whitelist of objects.

For this purpose, optional second parameter is added to unserialize(), which can take the following values:

  • true — default value, allows all objects just as before
  • false — no objects allowed
  • array of class names, which list the allowed classes for unserialized objects (empty array here means the same as false)

If the class for the object is not allowed, the object is unserialized as an object of “incomplete class”, just as it is done in a case where object’s class does not exist. This means that the serialized data are roundtrip-safe with any settings, but with added security settings the unintended objects will not be accessible and their destructors and other methods will not be called.

Examples

// this will unserialize everything as before
$data = unserialize($foo); 
// this will convert all objects into __PHP_Incomplete_Class object
$data = unserialize($foo, false); 
// this will convert all objects except ones of MyClass and MyClass2 into __PHP_Incomplete_Class object
$data = unserialize($foo, array("MyClass", "MyClass2")); 

See API Update below.

Usage

How to serialize data

For serializing a Java object into a PHP serialization format string you
just use the static method Pherialize.serialize(). Just pass the object you
want to serialize to this method and you get a string in return which you
can then unserialize in PHP.

Example:

The result is printed to stdout and looks like this:

Now you can use this string in PHP to unserialize it back into a PHP array:

Result is a PHP array with exactly the data and types you have added to the
array with Java:

How to unserialize data

Let’s assume you have serialized the PHP array from the previous example and
you have stored this serialized string in the variable data. For unserializing and
printing the value you just have to use the Pherialize.unserialize() method.

Example:

The correct result printed to stdout is this:

License

Copyright (c) 2006 Klaus Reimer k@ailis.de

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the «Software»),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED «AS IS», WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

Introduction

What is serialization?

In the context of data storage, serialization is the process of translating data structures or object state into a format that can be stored (for example, in a file or memory buffer, or transmitted across a network connection link) and reconstructed later in the same or another computer environment.

Why not and ?

These native functions rely on having the serialized classes loaded and available at runtime and tie your unserialization process to a platform.

If the serialized string contains a reference to a class that cannot be instantiated (e.g. class was renamed, moved namespace, removed or changed to abstract) PHP will immediately die with a fatal error.

Is this a problem? Yes it is. Serialized data is now unusable.

Usage

Suppose that you have all XSD files in , first of all we need a configuration file
(as example ) that will keep all the namespace and directory mappings information.

# config.yml
# Linux Users: PHP Namespaces use back slash \ rather than a forward slash /
# So for destinations_php, the namespace would be TestNs\MyApp

xsd2php:
  namespaces:
    'http://www.example.org/test/': 'TestNs\MyApp'
  destinations_php: 
    'TestNs\MyApp': soap/src
#    'TestNs\MyApp': soap\src  #  on Windows

  destinations_jms:
    'TestNs\MyApp': soap/metadata
#    'TestNs\MyApp': soap\metadata  #  on Windows
 
#  Uncomment this section if you want to have also symfony/validator metadata to be generated from XSD    
#  destinations_validation:
#    'TestNs\MyApp': soap/validation
#    'TestNs\MyApp': soap\validation  #  on Windows
    
  aliases: # optional
    'http://www.example.org/test/':
      MyCustomXSDType:  'MyCustomMappedPHPType'
  naming_strategy: short # optional and default
  path_generator: psr4 # optional and default
#  known_locations: # optional
#    "http://www.example.org/test/somefile.xsd": somefile.xsd
#  known_namespace_locations: # optional
#    "urn:veloconnect:catalog-1.1": xsd/catalog-1.1.xsd

Here is an explanation on the meaning of each parameter:

  • (required) defines the mapping between XML namespaces and PHP namespaces.
    (in the example we have the XML namespace mapped to )

  • (required) specifies the directory where to save the PHP classes that belongs to
    PHP namespace. (in this example classes will be saved into directory.

  • (required) specifies the directory where to save JMS Serializer metadata files
    that belongs to PHP namespace.
    (in this example metadata will be saved into directory.

  • (optional) specifies some mappings that are handled by custom JMS serializer handlers.
    Allows to specify to do not generate metadata for some XML types, and assign them directly a PHP class.
    For that PHP class is necessary to create a custom JMS serialize/deserialize handler.

  • (optional) specifies the naming strategy to use when converting XML names PHP classes.

  • (optional) specifies the strategy to use for path generation and file saving

  • (optional) override remote location with a local file.

  • (optional) Specify schema location by namespace.
    This can be used to read schemas which import namespaces but do not specify schemaLocation attributes.

Data Transformation

Transformer classes greatly differ from a class because these cannot as all class references are lost in the process of transformation.

To obtain transformations instead of the class usage of is required.

The Serializer library comes with a set of defined Transformers that implement the .
Usage is as simple as before, pass a Transformer as a .

For instance:

//...same as before ...

$serializer = new DeepCopySerializer(new JsonTransformer());
echo $serializer->serialize($post);

Following, there are some examples and its output, given the object as data to be Transformed.

Array Transformer

NilPortugues\Serializer\Transformer\ArrayTransformer

array(
  'postId' => 9,
  'title' => 'Hello World',
  'content' => 'Your first post',
  'author' => array(
       'userId' => 1,
       'name' => 'Post Author',
   ),
  'comments' => array(
           => array(
           'commentId' => 1000,
           'dates' => array(
              'created_at' => '2015-07-18T12:13:00+02:00',
              'accepted_at' => '2015-07-19T00:00:00+02:00',
            ),
           'comment' => 'Have no fear, sers, your king is safe.',
           'user' => array(
             'userId' => 2,
             'name' => 'Barristan Selmy',
            ),
          ),
      ),
);

Flat Array Transformer

NilPortugues\Serializer\Transformer\FlatArrayTransformer

array(
  'postId' => 9,
  'title' => 'Hello World',
  'content' => 'Your first post',
  'author.userId' => 1,
  'author.name' => 'Post Author',
  'comments.0.commentId' => 1000,
  'comments.0.dates.created_at' => '2015-07-18T12:13:00+02:00',
  'comments.0.dates.accepted_at' => '2015-07-19T00:00:00+02:00',
  'comments.0.comment' => 'Have no fear, sers, your king is safe.',
  'comments.0.user.userId' => 2,
  'comments.0.user.name' => 'Barristan Selmy',
);

XML Transformer

NilPortugues\Serializer\Transformer\XmlTransformer

<?xml version="1.0" encoding="UTF-8"?>
<data>
  <postId type="integer">9</postId>
  <title type="string">Hello World</title>
  <content type="string">Your first post</content>
  <author>
    <userId type="integer">1</userId>
    <name type="string">Post Author</name>
  </author>
  <comments>
    <sequential-item>
      <commentId type="integer">1000</commentId>
      <dates>
        <created_at type="string">2015-07-18T12:13:00+02:00</created_at>
        <accepted_at type="string">2015-07-19T00:00:00+02:00</accepted_at>
      </dates>
      <comment type="string">Have no fear, sers, your king is safe.</comment>
      <user>
        <userId type="integer">2</userId>
        <name type="string">Barristan Selmy</name>
      </user>
    </sequential-item>
  </comments>
</data>

YAML Transformer

NilPortugues\Serializer\Transformer\YamlTransformer

title: 'Hello World'
content: 'Your first post'
author:
    userId: 1
    name: 'Post Author'
comments:
    - { commentId: 1000, dates: { created_at: '2015-07-18T12:13:00+02:00', accepted_at: '2015-07-19T00:00:00+02:00' }, comment: 'Have no fear, sers, your king is safe.', user: { userId: 2, name: 'Barristan Selmy' } }

Json Transformer

JsonTransformer comes in 2 flavours. For object to JSON transformation the following transformer should be used:

NilPortugues\Serializer\Transformer\JsonTransformer

Output

{
    "postId": 9,
    "title": "Hello World",
    "content": "Your first post",
    "author": {
        "userId": 1,
        "name": "Post Author"
    },
    "comments": 
}

If your desired output is for API consumption, you may like to check out the JsonTransformer library, or require it using:

$ composer require nilportugues/json

JSend Transformer

JSend Transformer has been built to transform data into valid JSend specification resources.

Please check out the JSend Transformer or download it using:

$ composer require nilportugues/jsend

JSON API Transformer

JSON API Transformer has been built to transform data into valid JSON API specification resources.

Please check out the JSON API Transformer or download it using:

$ composer require nilportugues/json-api

HAL+JSON Transformer

HAL+JSON Transformer has been built for HAL+JSON API creation. Given an object and a series of mappings a valid HAL+JSON resource representation is given as output.

Please check out the HAL+JSON API Transformer or download it using:

$ composer require nilportugues/haljson

Types

Because the data types in PHP are different to the types in Java conversion
is not always possible without switching to a different data type. If for
example you serialize a Java Byte then you will get a PHP int because PHP
does only knows the number types int and double. So when you unserialize this
int back into Java then you will end up with an Integer and not with a Byte.
The same problem comes up with Long objects. Pherialize serializes a Long
into a int if it fits an Integer. Otherwise it’s serialized into a double

To make life easier while unserializing PHP data into Java all PHP data
types are wrapped by an object of type Mixed.
From this object you can
convert the data easily in whatever you need (as long as the conversion makes
sense).

Here is a complete list of the conversions performed when serializing Java
data types and unserializing them in PHP:

Java class PHP type
null null
Boolean boolean
Character string
Byte int
Short int
Integer int
Long int or double (Depending on how large the value is)
Float double
Double double
String string
Collection array
Map array
Serializable object
Enum Value used as a string
Mixed Depends on the raw type in the Mixed wrapper

And here is a list of performed conversion when you unserialize PHP
data in Java. But note that you will never get in touch with these
conversions because it’s completely hidden by the .

PHP type Java class
null null
boolean Boolean
int Integer
double Double
string String
array Map

Serialize / Unserialize

vendor/bin/xsd2php  convert:jms-yaml \
`/home/my/ota/OTA_HotelAvail*.xsd \

--ns-map='http://www.opentravel.org/OTA/2003/05;Mercurio/OTA/2007B/'  \
--ns-dest='Mercurio/OTA/2007B/;src/Metadata/JMS;' \

--alias-map='http://www.opentravel.org/OTA/2003/05;CustomOTADateTimeFormat;Vendor/Project/CustomDateClass'

What about namespaces?

http://www.opentravel.org/OTA/2003/05 will be converted into Mercurio/OTA/2007B PHP namespace

Where place the files?

http://www.opentravel.org/OTA/2003/05 will be placed into src/Metadata/JMS directory

What about custom types?

  • Add xsd2php dependency to satisfy BaseTypesHandler and XmlSchemaDateHandler.

"require" : {
    "goetas-webservices/xsd2php-runtime":"^0.2.2",
}
use JMS\Serializer\SerializerBuilder;
use JMS\Serializer\Handler\HandlerRegistryInterface;

use GoetasWebservices\Xsd\XsdToPhpRuntime\Jms\Handler\BaseTypesHandler;
use GoetasWebservices\Xsd\XsdToPhpRuntime\Jms\Handler\XmlSchemaDateHandler;

$serializerBuilder = SerializerBuilder::create();
$serializerBuilder->addMetadataDir('metadata dir', 'DemoNs');
$serializerBuilder->configureHandlers(function (HandlerRegistryInterface $handler) use ($serializerBuilder) {
    $serializerBuilder->addDefaultHandlers();
    $handler->registerSubscribingHandler(new BaseTypesHandler()); // XMLSchema List handling
    $handler->registerSubscribingHandler(new XmlSchemaDateHandler()); // XMLSchema date handling

    // $handler->registerSubscribingHandler(new YourhandlerHere());
});

$serializer = $serializerBuilder->build();

// deserialize the XML into Demo\MyObject object
$object = $serializer->deserialize('<some xml/>', 'DemoNs\MyObject', 'xml');

// some code ....

// serialize the Demo\MyObject back into XML
$newXml = $serializer->serialize($object, 'xml');

Serializing

When you need to store a PHP object or transfer it over the network, you use serialize() to pack it up.

serialize(): PHP object -> plain old string that represents the obj

When you need to use that data, use unserialize() to unpack and get the underlying object.

unserialize(): string containing object data -> original object

For example, this code snippet will serialize the object “user”.

<?phpclass User{public $username;public $status;}$user = new User;$user->username = 'vickie';$user->status = 'not admin';echo serialize($user);?>

Run the code snippet, and you will get the serialized string that represents the “user” object.

O:4:"User":2:{s:8:"username";s:6:"vickie";s:6:"status";s:9:"not admin";}

How it works

unserialize() in a (very big) nutshell.

Step 0: What are PHP magic methods?

PHP magic methods are function names in PHP that have “magical” properties. Learn more about them here.

The magic methods that are relevant for us now are __wakeup() and __destruct(). If the class of the serialized object implements any method named __wakeup() and __destruct(), these methods will be executed automatically when unserialize() is called on an object.

Step 0.1: Unserialize prerequisites

When you serialize an object in PHP, serialize() will save all properties in the object. But it will not store the methods of the class of the object, just the name of the class.

Thus, in order to unserialize() an object, the class of the object will have to be defined in advance (or be autoloaded). That is, the definition of the class needs to be present in the file that you unserialize() the object in.

If the class is not already defined in the file, the object will be instantiated as __PHP_Incomplete_Class, which has no methods and the object will essentially be useless.

Step 1: Object instantiation

Instantiation is when the program creates an instance of a class in memory. And that is what unserialize() does. It takes the serialized string, which specifies the class of the object to be created and the properties of that object. With that data, unserialize() creates a copy of the originally serialized object.

It will then search for a function named __wakeup(), and execute code in that function if it is defined for the class. __wakeup() is called to reconstruct any resources that the object may have. It is often used to reestablish any database connections that may have been lost during serialization and perform other reinitialization tasks.

Step 2: Program uses the object

The program can then operate on the object and use it to perform other actions.

Step 3: Object destruction

Finally, when no reference to the deserialized object instance exists, __destruct() is called.

How vulnerabilities happen in unserialize()

When an attacker controls a serialized object that is passed into unserialize(), she can control the properties of the created object. This will then allow her the opportunity to hijack the flow of the application, by controlling the values passed into automatically executed methods like __wakeup().

This is called a PHP object injection. PHP object injection can lead to code execution, SQL injection, path traversal or DoS, depending on the context in which it happened.

For example, consider this vulnerable code snippet: (taken from https://www.owasp.org/index.php/PHP_Object_Injection)

class Example2{   private $hook;   function __construct()   {      // some PHP code...   }   function __wakeup()   {      if (isset($this->hook)) eval($this->hook);   }}// some PHP code...$user_data = unserialize($_COOKIE);// some PHP code...

An attacker can achieve RCE using this deserialization flaw because a user-provided object is passed into unserialize, and the class Example2 has a magic function that runs eval() on user-provided input.

To exploit this RCE, the attacker simply has to set her data cookie to a serialized Example2 object with the hook property set to whatever PHP code that she wants executed. She can generate the serialized object using the following code snippet:

class Example2{   private $hook = "phpinfo();";}print urlencode(serialize(new Example2));

In this case, passing the above-generated string into the data cookie will cause phpinfo() to be executed. Once the attacker passes the serialized object into the program, the following is what will happen in detail:

  1. The attacker passes a serialized Example2 object into the program as the data cookie.
  2. The program calls unserialize() on the data cookie.
  3. Because the data cookie is a serialized Example2 object, unserialize() instantiates a new Example2 object.
  4. unserialize() sees that the Example2 class has __wakeup() implemented, so __wakeup() is called.
  5. __wakeup() looks for the $hook property of the object, and if it is not NULL, it runs eval($hook).
  6. $hook is not NULL, and is set to “phpinfo();”, so eval(“phpinfo();”) is run.
  7. RCE is achieved.

Proposal

The proposed serialization mechanism tries to combine the generality of with the implementation approach of /.

Two new magic methods are added:

// Returns array containing all the necessary state of the object.
public function __serialize() array;
 
// Restores the object state from the given data array.
public function __unserialize(array $data) void;

The usage is very similar to the interface. From a practical perspective the main difference is that instead of calling inside , you directly return the data that should be serialized as an array.

The following example illustrates how / are used, and how they compose under inheritance:

class A {
    private $prop_a;
    public function __serialize() array {
        return "prop_a" => $this->prop_a;
    }
    public function __unserialize(array $data) {
        $this->prop_a = $data"prop_a";
    }
}
class B extends A {
    private $prop_b;
    public function __serialize() array {
        return 
            "prop_b" => $this->prop_b,
            "parent_data" => parent::__serialize(),
        ;
    }
    public function __unserialize(array $data) {
        parent::__unserialize($data"parent_data");
        $this->prop_b = $data"prop_b";
    }
}

This resolves the issues with by leaving the actual serialization and unserialization to the implementation of the serializer. This means that we don’t have to share the serialization state anymore, and thus avoid issues related to backreference ordering. It also allows us to delay calls to the end of unserialization.

Encoding and interoperability

The and methods reuse the serialization format used by ordinary object serialization, as well as /. This means that the data array returned by will be stored as-if it represented object properties.

In principle, this makes existing strings serialized in format fully interoperable with the new serialization mechanism, the data is just provided in a different way (for in properties, for as an explicit array). If a class has both and , then the latter will be preferred. If a class has both and then the latter will be preferred.

If a class both implements and /, then serialization will prefer the new mechanism, while unserialization can make use of either, depending on whether the (Serializable) or (__unserialize) format is used. As such, old serialized strings encoded in format can still be decoded, while new strings will be produced in format.

Magic methods vs interface

This RFC proposes the addition of new magic methods, but using an interface instead would also be possible, though it will require some naming gymnastics to avoid .

This proposal uses magic methods for two reasons. First, they interoperate well. and can be added to a class without compatibility concerns: They will be used on PHP 7.4 or newer and ignored on PHP 7.3 or older. Using an interface instead requires either raising the version requirement to PHP 7.4, or dealing with the definition of a stub interface in a compatible manner.

Second, they are semantically more correct. In PHP all objects are serializable by default. The interface is a misnomer in that sense, because an object that does not implement can be (and usually is) still serializable. On the contrary, might be implemented specifically for the purpose of forbidding serialization, by throwing an exception. The magic methods and are just hooks to customize the serialization functionality, they do not determine whether an object can be serialized, and code should generally have no reason to check for their presence or absence.

Creating objects in __unserialize()

Some people have expressed a desire to make a static method which creates and returns the unserialized object (rather than first constructing the object and then calling to initialize it).

This would allow an even greater degree of control over the serialization mechanism, for example it would allow to return an already existing object from .

However, allowing this would once again require immediately calling functions (interleaved with unserialization) to make the object available for backreferences, which would reintroduce some of the problems that suffers from. As such, this will not be supported.

API change

After some thought and discussion, I have decided to slightly change the API:

// this will unserialize everything as before
$data = unserialize($foo); 
// this will convert all objects into __PHP_Incomplete_Class object
$data = unserialize($foo, "allowed_classes" => false); 
// this will convert all objects except ones of MyClass and MyClass2 into __PHP_Incomplete_Class object
$data = unserialize($foo, "allowed_classes" => "MyClass", "MyClass2"); 
//accept all classes as in default
$data = unserialize($foo, "allowed_classes" => true); 

This will allow to extend the options array in the future if we ever want to add more parameters. No objections were voiced on the list regarding this API change.

Advanced: Enhancements

Fast destruct

PHPGGC implements a () flag, that will make sure your serialized object will be destroyed right after the call, and not at the end of the script. I’d recommend using it for every vector, as it improves reliability. For instance, if PHP script raises an exception after the call, the method of your object might not be called. As it is processed at the same time as encoders, it needs to be set first.

ASCII Strings

Uses the serialization format instead of the standard . This replaces every non-ASCII value to an hexadecimal representation:
->
This can be useful when for some reason non-ascii characters are not allowed (NULL BYTE for instance). Since payloads generally contain them, this makes sure that the payload consists only of ASCII values.
Note: this is experimental and it might not work in some cases.

Plus Numbers

Sometimes, PHP scripts verify that the given serialized payload does not contain objects by using a regex such as . This is easily bypassed using instead of . One can use , or , to automatically add these signs in front of symbols.
For instance, to obfuscate objects and strings, one can use: . Please note that since PHP 7.2, only i and d (float) types can have a +.

API

Instead of using PHPGGC as a command line tool, you can program PHP scripts:

<?php

# Include PHPGGC
include("phpggc/lib/PHPGGC.php");

# Include guzzle/rce1
$gc = new \GadgetChain\Guzzle\RCE1();

# Always process parameters unless you're doing something out of the ordinary
$parameters = $gc->process_parameters();

# Generate the payload
$object = $gc->generate($parameters);

# Most (if not all) GC's do not use process_object and process_serialized, so
# for quick & dirty code you can omit those two 
$object = $gc->process_object($object);

# Serialize the payload
$serialized = serialize($object);
$serialized = $gc->process_serialized($serialized);

# Display it
print($serialized . "\n");

# Create a PHAR file from this payload
$phar = new \PHPGGC\Phar\Tar($serialized);
file_put_contents('output.phar.tar', $phar->generate());

This allows you to tweak the parameters or write exploits more easily.
Note: This is pretty experimental at the moment, so please, report bugs.

Contributing

Pull requests are more than welcome. Please follow these simple guidelines:

  • is always the best vector
  • Specify at least the version of the library you’ve built the payload on
  • Refrain from using references unless it is necessary or drastically reduces the size of the payload. If the payload is modified by hand afterwards, this might cause problems.
  • Do not include unused parameters in the gadget definition if they keep their default values. It just makes the payload bigger.

Codewise, the directory structure is fairly straightforward: gadgets in gadgets.php, description + logic in chain.php.
You can define pre- and post- processing methods, if parameters need to be modified.
Hopefully, the already implemented gadgets should be enough for you to build yours.
Otherwise, I’d be glad to answer your questions.

The command-line option can be used to create the directory and file structure for a new gadget chain.
For instance, use would create a new Drupal RCE gadgetchain.

The details

According to PHP docs, unserialize() “creates a PHP value from a stored representation”, and ”takes a single serialized variable and converts it back into a PHP value”.

It takes two parameters: str and options. str is the parameter containing the serialized string waiting to be deserialized. options is the array containing the options that control certain function behaviors. In unserialize() particularly, the only valid user-defined option is allowed_classes. allowed_classes specify the class names that should be accepted.

We’ll dive into allowed_classes further, but essentially, when unserialize() encounters an object of a class that isn’t to be accepted, then the object will be instantiated as __PHP_Incomplete_Class instead.

Prerequisites for the exploit

There are a few conditions that have to be met for this to be exploitable. Let’s look at this chart again, as it points to important exploit prerequisites:

In order for an attacker to exploit an insecure unserialize() function, two conditions must be met:

  • The class of the deserialized object needs to be defined (or autoloaded) and needs to be one of the allowed classes.
  • The class of the object must implement some magic method that allows an attacker to inject code into.

And there you have it! That’s how unserialize() leads to dangerous vulnerabilities.

How to protect against unserialize() vulnerabilities

In order to prevent PHP object injections from happening, it is recommended to never pass untrusted user input into unserialize(). Consider using JSON to pass serializes data to and from the user. And if you do need to pass untrusted, serialized data into unserialize(), be sure to implement rigorous data validation in order to minimize the risk of a critical vulnerability.

Mitigation advice is taken from php.net. Read more about the unserialize() function here:

Intro to PHP object injection vulnerabilities

Vickie LiFollow

May 17 · 5 min read

PHP: Hypertext Preprocessor | Logo by Colin Viebrock on php.net

Serialization is when an object in a programming language (say, a Java or PHP object) is converted into a format that can be stored or transferred. Whereas deserialization refers to the opposite: it’s when the serialized object is read from a file or the network and converted back into an object.

Insecure deserialization vulnerabilities happen when applications deserialize objects without proper sanitization. An attacker can then manipulate serialized objects to change the program’s flow.

Today, let’s talk about PHP object injections. They are insecure deserialization vulnerabilities that happen when developers deserialize PHP objects recklessly.

Dealing with xsd:anyType or xsd:anySimpleType

If your XSD contains or types you have to specify a handler for this.

When you generate the JMS metadata you have to specify a custom handler:

bin/xsd2php.php convert:jms-yaml \

 ... various params ... \

--alias-map='http://www.w3.org/2001/XMLSchema;anyType;MyCustomAnyTypeHandler' \
--alias-map='http://www.w3.org/2001/XMLSchema;anyType;MyCustomAnySimpleTypeHandler' \

Now you have to create a custom serialization handler:

use JMS\Serializer\XmlSerializationVisitor;
use JMS\Serializer\XmlDeserializationVisitor;

use JMS\Serializer\Handler\SubscribingHandlerInterface;
use JMS\Serializer\GraphNavigator;
use JMS\Serializer\VisitorInterface;
use JMS\Serializer\Context;

class MyHandler implements SubscribingHandlerInterface
{
    public static function getSubscribingMethods()
    {
        return array(
            array(
                'direction' => GraphNavigator::DIRECTION_DESERIALIZATION,
                'format' => 'xml',
                'type' => 'MyCustomAnyTypeHandler',
                'method' => 'deserializeAnyType'
            ),
            array(
                'direction' => GraphNavigator::DIRECTION_SERIALIZATION,
                'format' => 'xml',
                'type' => 'MyCustomAnyTypeHandler',
                'method' => 'serializeAnyType'
            )
        );
    }

    public function serializeAnyType(XmlSerializationVisitor $visitor, $data, array $type, Context $context)
    {
        // serialize your object here
    }

    public function deserializeAnyType(XmlDeserializationVisitor $visitor, $data, array $type)
    {
        // deserialize your object here
    }
}

Serialization

For the serializer to work, all you need to do is pass in a PHP Object to the serializer and a Strategy to implement its string representation.

Serializers (JSON, XML, YAML)

  • NilPortugues\Serializer\JsonSerializer
  • NilPortugues\Serializer\XmlSerializer
  • NilPortugues\Serializer\YamlSerializer

Example

In the following example a object is serialized into JSON.

Code

use NilPortugues\Serializer\Serializer;
use NilPortugues\Serializer\Strategy\JsonStrategy;

//Example object
$post = new Post(
  new PostId(9),
  'Hello World',
  'Your first post',
  new User(
      new UserId(1),
      'Post Author'
  ),
  [
      new Comment(
          new CommentId(1000),
          'Have no fear, sers, your king is safe.',
          new User(new UserId(2), 'Barristan Selmy'),
          [
              'created_at' => (new DateTime('2015/07/18 12:13:00'))->format('c'),
              'accepted_at' => (new DateTime('2015/07/19 00:00:00'))->format('c'),
          ]
      ),
  ]
);

//Serialization 
$serializer = new JsonSerializer();

$serializedObject = $serializer->serialize($post);

//Returns: true
var_dump($post == $serializer->unserialize($serializedObject));

echo $serializedObject;

The object, before it’s transformed into an output format, is an array with all the necessary data to be rebuild using unserialize method.

Output

{
    "@type": "Acme\\\\Domain\\\\Dummy\\\\Post",
    "postId": {
        "@type": "Acme\\\\Domain\\\\Dummy\\\\ValueObject\\\\PostId",
        "postId": {
            "@scalar": "integer",
            "@value": 14
        }
    },
    "title": {
        "@scalar": "string",
        "@value": "Hello World"
    },
    "content": {
        "@scalar": "string",
        "@value": "Your first post"
    },
    "author": {
        "@type": "Acme\\\\Domain\\\\Dummy\\\\User",
        "userId": {
            "@type": "Acme\\\\Domain\\\\Dummy\\\\ValueObject\\\\UserId",
            "userId": {
                "@scalar": "integer",
                "@value": 1
            }
        },
        "name": {
            "@scalar": "string",
            "@value": "Post Author"
        }
    },
    "comments": {
        "@map": "array",
        "@value": 
    }
}'

Custom Serializers

If a custom serialization strategy is preferred, the class should be used instead. A must implement the .

Usage is as follows:

use NilPortugues\Serializer\Serializer;
use NilPortugues\Serializer\Strategy\CustomStrategy;

$serializer = new Serializer(new CustomStrategy());

echo $serializer->serialize($post);
Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector