<summary><strong>Apprenez le piratage AWS de zéro à héros avec</strong><ahref="https://training.hacktricks.xyz/courses/arte"><strong>htARTE (HackTricks AWS Red Team Expert)</strong></a><strong>!</strong></summary>
* Si vous souhaitez voir votre **entreprise annoncée dans HackTricks** ou **télécharger HackTricks en PDF**, consultez les [**PLANS D'ABONNEMENT**](https://github.com/sponsors/carlospolop)!
* Obtenez le [**merchandising officiel PEASS & HackTricks**](https://peass.creator-spring.com)
* Découvrez [**La Famille PEASS**](https://opensea.io/collection/the-peass-family), notre collection d'[**NFTs**](https://opensea.io/collection/the-peass-family) exclusifs
* **Rejoignez le** 💬 [**groupe Discord**](https://discord.gg/hRep4RUj7f) ou le [**groupe telegram**](https://t.me/peass) ou **suivez** moi sur **Twitter** 🐦 [**@carlospolopm**](https://twitter.com/carlospolopm)**.**
* **Partagez vos astuces de piratage en soumettant des PR aux dépôts github** [**HackTricks**](https://github.com/carlospolop/hacktricks) et [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud).
Depuis sa création en 2008, l'utilisation du framework [Symfony](https://symfony.com) n'a cessé de croître dans les applications basées sur PHP. Il est maintenant un composant central de nombreux CMS bien connus, tels que [Drupal](https://www.drupal.org), [Joomla!](https://www.joomla.org), [eZPlatform](https://ezplatform.com) (anciennement eZPublish), ou [Bolt](https://bolt.cm), et est souvent utilisé pour construire des sites web personnalisés.
L'une des fonctionnalités intégrées de Symfony, conçue pour gérer les [ESI (Edge-Side Includes)](https://en.wikipedia.org/wiki/Edge\_Side\_Includes), est la classe [`FragmentListener`](https://github.com/symfony/symfony/blob/5.1/src/Symfony/Component/HttpKernel/EventListener/FragmentListener.php). Essentiellement, lorsqu'une requête est envoyée à `/_fragment`, ce listener définit les attributs de la requête à partir des paramètres GET fournis. Puisque cela permet d'**exécuter du code PHP arbitraire** (_plus de détails plus tard_), la requête doit être signée à l'aide d'une valeur HMAC. La clé cryptographique secrète de ce HMAC est stockée sous une valeur de configuration Symfony nommée `secret`.
Cette valeur de configuration, `secret`, est également utilisée, par exemple, pour construire des jetons CSRF et des jetons de fonction "se souvenir de moi". Étant donné son importance, cette valeur doit évidemment être très aléatoire.
Malheureusement, nous avons découvert que souvent, le secret soit a une **valeur par défaut**, ou qu'il existe **des moyens d'obtenir la valeur, de la forcer hors ligne, ou de contourner purement et simplement le contrôle de sécurité auquel elle est associée**. Cela affecte notamment Bolt, eZPlatform et eZPublish.
Bien que cela puisse sembler être un problème de configuration bénin, nous avons constaté que des valeurs par défaut, forçables ou devinables sont **très, très souvent présentes** dans les CMS mentionnés ainsi que dans les applications personnalisées. Cela est principalement dû au fait que l'importance de cette valeur n'est pas suffisamment soulignée dans la documentation ou les guides d'installation.
De plus, un attaquant peut escalader des vulnérabilités moins impactantes pour soit lire le `secret` (via une divulgation de fichier), contourner le processus de signature de `/_fragment` (en utilisant un SSRF), et même le divulguer via `phpinfo()` !
Dans ce billet de blog, nous décrirons comment le secret peut être obtenu dans divers CMS et sur le framework de base, et comment obtenir l'exécution de code en utilisant ledit secret.
Étant un framework moderne, Symfony a dû gérer la génération de sous-parties d'une requête depuis sa création jusqu'à nos jours. Avant `/_fragment`, il y avait `/_internal` et `/_proxy`, qui faisaient essentiellement la même chose. Cela a produit de nombreuses vulnérabilités au fil des ans : [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), et [CVE-2015-4050](https://symfony.com/blog/cve-2015-4050-esi-unauthorized-access), par exemple.
Depuis Symfony 4, le secret est généré lors de l'installation, et la page `/_fragment` est désactivée par défaut. On pourrait donc penser que la conjonction d'un `secret` faible et d'un `/_fragment` activé serait rare. Ce n'est pas le cas : de nombreux frameworks s'appuient sur d'anciennes versions de Symfony (même la version 2.x est encore très présente), et implémentent soit une valeur `secret` statique, soit la génèrent de manière médiocre. De plus, beaucoup s'appuient sur ESI et activent donc la page `/_fragment`. Aussi, comme nous le verrons, d'autres vulnérabilités de moindre impact peuvent permettre de déverser le secret, même s'il a été généré de manière sécurisée.
Nous allons d'abord démontrer comment un attaquant, ayant connaissance de la valeur de configuration `secret`, peut obtenir l'exécution de code. Ceci est fait pour la dernière version de `symfony/http-kernel`, mais est similaire pour d'autres versions.
`FragmentListener:onKernelRequest` sera exécuté à chaque requête : si le chemin de la requête est `/_fragment` \[1], la méthode vérifiera d'abord que la requête est valide (_c'est-à-dire_ correctement signée), et lèvera une exception dans le cas contraire \[2]. Si les contrôles de sécurité réussissent, elle analysera le paramètre `_path` encodé dans l'URL, et définira les attributs de `$request` en conséquence.
Les attributs de requête ne doivent pas être confondus avec les paramètres de requête HTTP : ce sont des valeurs internes, maintenues par Symfony, que l'utilisateur ne peut généralement pas spécifier. L'un de ces attributs de requête est `_controller`, qui spécifie quel contrôleur Symfony (un tuple _(classe, méthode)_ ou simplement une _fonction_) doit être appelé. Les attributs dont le nom ne commence pas par `_` sont des arguments qui vont être transmis au contrôleur. Par exemple, si nous souhaitions appeler cette méthode :
Essentiellement, cela permet d'appeler n'importe quelle fonction, ou n'importe quelle méthode de n'importe quelle classe, avec n'importe quel paramètre. Étant donné la pléthore de classes que Symfony possède, **obtenir l'exécution de code est trivial**. Nous pouvons, par exemple, appeler `system()` :
Pour vérifier la signature d'une URL, un HMAC est calculé contre l'_URL complète_. Le hash obtenu est ensuite comparé à celui spécifié par l'utilisateur.
En résumé, Symfony extrait le paramètre GET `_hash`, puis reconstruit l'URL complète, par exemple `https://symfony-site.com/_fragment?_path=controller%3d...%26argument1=test%26...`, calcule un HMAC de cette URL en utilisant le `secret` comme clé \[1], et le compare à la valeur de hash donnée \[2]. S'ils ne correspondent pas, une exception `AccessDeniedHttpException` est levée \[3], résultant en une erreur `403`.
En consultant `http://localhost:8000/_fragment?_hash=lNweS5nNP8QCtMqyqrW8HIl4j9JXIfscGeRm%2FcmFOh8%3D`, nous obtenons maintenant un code de statut `404`. La signature était correcte, mais nous n'avons spécifié aucun attribut de requête, donc Symfony ne trouve pas notre contrôleur.
Puisque nous pouvons appeler n'importe quelle méthode, avec n'importe quel argument, nous pouvons par exemple choisir `system($command, $return_value)`, et fournir un payload comme suit :
Nous pouvons maintenant visiter l'URL de l'exploit : `http://localhost:8000/_fragment?_path=_controller%3Dsystem%26command%3Did%26return_value%3Dnull&_hash=GFhQ4Hr1LIA8mO1M%2FqSfwQaSM8xQj35vPhyrF3hvQyI%3D`.
Encore une fois : tout cela ne serait pas important si les secrets n'étaient pas accessibles. Souvent, ils le sont. Nous décrirons plusieurs façons d'obtenir l'exécution de code sans aucune connaissance préalable.
Sur les versions récentes de Symfony (3.x), `secret` est stocké dans `.env` sous le nom de `APP_SECRET`. Puisqu'il est ensuite importé comme une variable d'environnement, elles peuvent être vues à travers une page `phpinfo()`.
Le code derrière `FragmentListener` a évolué au fil des ans : jusqu'à la version _2.5.3_, lorsque la requête provenait d'un proxy de confiance (lire : `localhost`), elle était considérée comme sûre, et en tant que telle, le hash n'était pas vérifié. Un SSRF, par exemple, peut permettre d'exécuter immédiatement du code, que l'on ait `secret` ou non. Cela affecte notamment eZPublish jusqu'à 2014.7.
Lors de la configuration d'un site Symfony, la première étape consiste à installer le squelette [symfony-standard](https://github.com/symfony/symfony-standard). Une fois installé, une invite demande certaines valeurs de configuration. Par défaut, la clé est `ThisTokenIsNotSoSecretChangeIt`.
[ezPlatform](https://ezplatform.com), le successeur de [ezPublish](https://en.wikipedia.org/wiki/EZ\_Publish), utilise toujours Symfony. Le 10 juin 2019, un [commit](https://github.com/ezsystems/ezplatform/commit/974f2a70d9d0507ba7ca17226693b1a4967f23cf#diff-f579cccc964135c7d644c7b2d3b0d3ecR59) a défini la clé par défaut à `ff6dc61a329dc96652bb092ec58981f7`. Les versions vulnérables vont de 3.0-alpha1 à 3.1.1 (actuelle).
Bien que la [documentation](https://doc.ezplatform.com/en/latest/getting\_started/install\_ez\_platform/#change-installation-parameters) indique que le secret doit être changé, cela n'est pas imposé.
Comme pour le squelette de Symfony, on vous demandera de saisir un secret lors de l'installation. La valeur par défaut est `ThisEzPlatformTokenIsNotSoSecret_PleaseChangeIt`.
[Bolt CMS](https://bolt.cm) utilise [Silex](https://github.com/silexphp/Silex), un micro-framework obsolète basé sur Symfony. Il configure la clé secrète en utilisant ce calcul :
Comme le secret est souvent défini manuellement (au lieu d'être généré aléatoirement), les gens utilisent souvent une phrase secrète au lieu d'une valeur aléatoire sécurisée, ce qui le rend vulnérable au bruteforce si nous avons un hash contre lequel effectuer le bruteforce. Évidemment, une URL valide `/_fragment`, comme celle générée par Symfony, nous fournirait un tuple message-hash valide pour bruteforcer le secret.
Au début de cet article de blog, nous avons dit que le secret de Symfony avait plusieurs utilisations. L'une d'elles est qu'il est également utilisé pour générer des tokens CSRF. Une autre utilisation de `secret` est de signer les cookies remember-me. Dans certains cas, un attaquant peut utiliser son propre token CSRF ou cookie remember-me pour bruteforcer la valeur de `secret`.
Comme exemple de la façon dont les secrets peuvent être bruteforcés pour obtenir une exécution de code, nous verrons comment nous pouvons découvrir le secret d'eZPublish 2014.07.
Pour construire ce jeton, eZP utilise deux valeurs que nous connaissons, et le secret : `getIntention()` est l'action que l'utilisateur tente de réaliser (par exemple `authenticate`), `session_id()` est l'ID de session PHP, et `getSecret()`, eh bien, est le `secret` de Symfony.
Malheureusement, ezPublish a intégré un bundle de sensiolabs, [sensio/distribution-bundle](https://packagist.org/packages/sensio/distribution-bundle). Ce package s'assure que la clé secrète est aléatoire. Il la génère de cette manière, lors de l'installation :
Cela semble très difficile à forcer par bruteforce : `mt_rand()` peut produire 2^31 valeurs différentes, et `uniqid()` est construit à partir de l'horodatage actuel (avec microsecondes).
Heureusement, nous savons que ce secret est généré lors de la dernière étape de l'installation, juste après la configuration du site web. Cela signifie que nous pouvons probablement divulguer le timestamp utilisé pour générer ce hash.
Une manière de faire est d'utiliser les logs (_par exemple_ `/var/log/storage.log`); on peut divulguer la première fois qu'une entrée de cache a été créée. L'entrée de cache est créée juste après l'appel à `generateRandomSecret()`.
Si les logs ne sont pas disponibles, on peut utiliser le moteur de recherche très puissant d'eZPublish pour trouver le moment de création du tout premier élément du site web. En effet, lors de la création du site, de nombreux timestamps sont insérés dans la base de données. Cela signifie que le timestamp des données initiales du site eZPublish est le même que celui utilisé pour calculer `uniqid()`. Nous pouvons rechercher l'objet _ContentObject_`landing_page` et découvrir son timestamp.
Cela nous laisse avec un total de 231 \* 106 possibilités. Cela semble faisable avec [hashcat](https://hashcat.net) et un bon ensemble de GPU, mais hashcat ne fournit pas de noyau `sha1(sha1($pass).$salt)`. Heureusement, nous l'avons implémenté ! Vous pouvez trouver [la pull-request ici](https://github.com/hashcat/hashcat/pull/2536).
Symfony est maintenant un composant central de nombreuses applications PHP. En tant que tel, tout risque de sécurité qui affecte le framework affecte de nombreux sites web. Comme démontré dans cet article, soit un secret faible soit une vulnérabilité de moindre impact permet aux attaquants d'obtenir une **exécution de code à distance**.
En tant que membre d'une équipe bleue, vous devriez examiner chacun de vos sites web dépendant de Symfony. Un logiciel à jour ne peut être exclu pour les vulnérabilités, car la clé secrète est générée lors de la première installation du produit. Par conséquent, si vous avez créé un site web basé sur Symfony-3.x il y a quelques années, et que vous l'avez maintenu à jour en cours de route, il y a des chances que la clé secrète soit toujours celle par défaut.
* Le HMAC est calculé en utilisant l'**URL complète**. Si le site web est derrière un proxy inverse, nous devons utiliser l'URL interne du service au lieu de celle à laquelle nous envoyons notre charge utile. Par exemple, l'URL interne pourrait être en HTTP au lieu de HTTPS.
* L'algorithme du HMAC a changé au fil des ans : c'était **SHA-1** avant, et c'est maintenant **SHA-256**.
* Comme Symfony supprime le paramètre `_hash` de la requête, puis génère à nouveau l'URL, nous devons calculer le hash sur la même URL que lui.
* Beaucoup de secrets peuvent être utilisés, donc nous devons tous les vérifier.
* Sur certaines versions de PHP, nous ne pouvons pas appeler des fonctions qui ont des paramètres "par référence", comme `system($command, &$return_value)`.
* Sur certaines versions de Symfony, `_controller` ne peut pas être une fonction, cela doit être une méthode. Nous devons trouver une méthode Symfony qui nous permet d'exécuter du code.
* URL interne : cela pourrait être `https://target.com/_fragment`, ou peut-être `http://target.com/_fragment`, ou quelque chose de complètement différent (_par exemple_ `http://target.website.internal`), ce que nous ne pouvons pas deviner
* Clé secrète : nous avons une liste de clés secrètes habituelles, telles que `ThisTokenIsNotSoSecretChangeIt`, `ThisEzPlatformTokenIsNotSoSecret_PleaseChangeIt`, etc.
Nous n'avons pas besoin de nous préoccuper de la charge utile effective (le contenu de `_path`) pour l'instant, car une URL correctement signée ne résultera pas en un `AccessDeniedHttpException` lancé, et donc ne produira pas un `403`. L'exploit essaiera donc chaque combinaison `(algorithme, URL, secret)`, générera une URL et vérifiera si elle ne produit pas un code de statut `403`.
Ensuite, nous devons déterminer si nous pouvons appeler une fonction directement, ou si nous devons utiliser une méthode de classe. Nous pouvons d'abord essayer la première manière, la plus directe, en utilisant une fonction telle que `phpinfo ([ int $what = INFO_ALL ] )` ([documentation](https://www.php.net/manual/en/function.phpinfo.php)). Le paramètre GET `_path` ressemblerait à ceci :
Sinon, cela signifie que nous devrons utiliser une méthode de classe à la place. Un bon candidat pour cela est `Symfony\Component\Yaml\Inline::parse`, qui est une classe intégrée de Symfony, et en tant que telle est présente sur les sites web Symfony.
De toute évidence, cette méthode analyse une chaîne d'entrée YAML. Le parseur [YAML](https://yaml.org) de Symfony prend en charge la balise `php/object`, qui convertira une chaîne d'entrée sérialisée en un objet en utilisant `unserialize()`. Cela nous permet d'utiliser notre outil PHP favori, [PHPGGC](https://github.com/ambionics/phpggc) !
Au lieu de construire `_path` pour chacun de ces éléments, nous pouvons tirer parti du fait que si nous donnons un argument dont le nom ne correspond pas au prototype de la méthode, il sera ignoré. Nous pouvons donc ajouter tous les arguments possibles à la méthode, sans nous soucier du prototype réel.
L'exploit va donc tester toutes les combinaisons de variables possibles, puis essayer les deux méthodes d'exploitation. Le code est disponible sur [notre GitHub](https://github.com/ambionics/symfony-exploits).
Comme vous pouvez le voir dans la capture d'écran ci-dessus, il y a un logo `sf` en bas à droite de la page. Ce logo apparaît lorsque Symfony est en mode débogage. Il y a des cas où ce logo n'apparaît pas, donc essayez d'accéder à `/_profiler` et vous verrez la page comme ci-dessous
Cette fonctionnalité s'appelle Symfony Profiler, et il n'y a pas beaucoup d'informations à ce sujet sur internet. L'intention de cette fonctionnalité est très claire ; elle vous aide à déboguer en cas d'erreur ou de bogue. Bien sûr, cette fonctionnalité ne peut être utilisée que lorsque le mode débogage est activé.
Le framework Symfony en lui-même est très sécurisé, mais l'activation du mode débogage rendra ce framework extrêmement vulnérable. Par exemple, Profiler a une fonctionnalité appelée Recherche de Profil, comme le montre la capture d'écran suivante.
Comme vous pouvez le voir dans la capture d'écran ci-dessus, vous pouvez accéder à toutes les requêtes envoyées au serveur. En cliquant sur les hachages dans le jeton, vous verrez que tous les paramètres POST peuvent être lus, comme on le voit dans la capture d'écran suivante. Avec cette fonctionnalité, nous pouvons détourner les identifiants des comptes administrateurs et utilisateurs.
<summary><strong>Apprenez le hacking AWS de zéro à héros avec</strong><ahref="https://training.hacktricks.xyz/courses/arte"><strong>htARTE (HackTricks AWS Red Team Expert)</strong></a><strong>!</strong></summary>
* Si vous souhaitez voir votre **entreprise annoncée dans HackTricks** ou **télécharger HackTricks en PDF**, consultez les [**PLANS D'ABONNEMENT**](https://github.com/sponsors/carlospolop)!
* Obtenez le [**merchandising officiel PEASS & HackTricks**](https://peass.creator-spring.com)
* Découvrez [**La Famille PEASS**](https://opensea.io/collection/the-peass-family), notre collection d'[**NFTs**](https://opensea.io/collection/the-peass-family) exclusifs
* **Rejoignez le** 💬 [**groupe Discord**](https://discord.gg/hRep4RUj7f) ou le [**groupe Telegram**](https://t.me/peass) ou **suivez** moi sur **Twitter** 🐦 [**@carlospolopm**](https://twitter.com/carlospolopm)**.**
* **Partagez vos astuces de hacking en soumettant des PR aux dépôts GitHub** [**HackTricks**](https://github.com/carlospolop/hacktricks) et [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud).