Commentaires
Comment ne pas (trop) exposer Symfony !
Au fil des années, Symfony s'est largement imposé en France comme le cadre de
travail préféré de la plupart des développeurs PHP professionnels. L'image
négative du langage véhiculée par certains opposants à PHP s'efface de jour en
jour au profit d'un regard plus contemporain et professionnel du développement
Web avec PHP.
Le couple PHP / Symfony séduit aussi bien les grandes entreprises du CAC 40,
les PME, les startups comme les actuelles licornes et celles en devenir.
L'engouement est parfois si fort que certaines d'entre elles n'hésitent plus à
communiquer officiellement leur préférence pour Symfony à l'occasion, entre
autres, des conférences comme le Symfony Live ou le Forum PHP de l'AFUP
pour partager leur travail et leur savoir-faire.
Malgré cela, le degré de criticité de certaines applications nécessite parfois de
divulguer le moins d'informations possibles à propos de la pile technique afin
d'empêcher les utilisateurs mal intentionnés de la détourner. Comme le dit le
célèbre proverbe : vivons heureux, vivons cachés.
Par défaut, le framework Symfony ne dévoile que peu ou prou d'informations
sensibles concernant son usage. Pour autant, il existe quelques indices bien
pratiques pour soupçonner ou attester avec certitude de l'usage de Symfony. Cet
article dresse la liste la plus exhaustive possible de ces indices qui révèlent
l'usage de Symfony dans un site Web. Les conseils et contre-mesures présentés
dans les sections qui suivent vous aideront à mieux cacher l'implémentation de
Symfony.
Le déploiement
Le déploiement et la configuration du serveur Web constituent la première étape
essentielle pour masquer l'usage de Symfony. Les développeurs expérimentés
savent que seuls les fichiers accessibles depuis un navigateur doivent être
exclusivement déployés sous le dossier configuré comme étant la racine Web du
nom de domaine. Ainsi, dans un projet Symfony traditionnel, c'est le dossier
web/
(ou public/
pour un projet Symfony 3.4 / 4.x avec Flex) qui joue ce
rôle.
Par conséquent, c'est lui et seulement lui qui doit être configuré pour être le
répertoire de la racine Web du domaine. Certaines plateformes d'hébergement
empêchent la modification de la configuration du répertoire Web racine et
forcent parfois un nom de dossier prédéfini (htdocs
, public
, www
, etc.).
Dans ce cas, il suffit de renommer le dossier web/
avec le nom correspondant
à celui sur la plateforme d'hébergement.
La Distribution Standard de Symfony embarque par
défaut un fichier .htaccess
à la racine du dossier web/
. Si le serveur Web
de production est basé sur Apache, il est alors conseillé de copier cette
configuration au niveau de l'hôte virtuel Apache. À l'inverse, si la
technologie est différente (nginx par exemple), alors le fichier .htaccess
devient inutile et doit être retiré.
Les fichiers publics
Pour valider la présence de Symfony sur le Web, la technique la plus simple
consiste à vérifier l'existence des contrôleurs frontaux app.php
et
app_dev.php
en essayant de les exécuter par les URLs. Dans l'idéal,
l'application doit répondre avec une réponse de type 404
lorsque ces fichiers
sont demandés explicitement au serveur. Pour y parvenir, il convient simplement
de renommer le fichier web/app.php
en web/index.php
.
Les développeurs Symfony expérimentés savent qu'il ne faut en aucun cas
déployer le fichier web/app_dev.php
sur un serveur de production.
Bien que ce fichier contienne une pseudo protection empêchant son exécution
via une adresse IP non locale ou clairement identifiée, il n'en demeure pas
moins que sa simple présence sur le serveur de production confirme la thèse
de l'existence de Symfony. Cette thèse est d'autant plus vérifiée si l'exécution
de ce contrôleur frontal donne à l'utilisateur un accès au mode « débogage » de
Symfony ou bien, à défaut, lui retourne une réponse HTTP dont le corps
contient la chaîne suivante :
You are not allowed to access this file.
Une simple recherche Google avec un opérateur inurl:
ou intext:
donne des résultats malheureusement positifs. De nombreux sites n'ayant pas été déployés
correctement offrent des accès complets aux espaces de débogage de Symfony.
Au même titre que le fichier web/app_dev.php
, il convient de s'assurer que le
fichier web/config.php
n'a pas été lui aussi déployé sur le serveur. Ce fichier
audite la configuration de PHP et du serveur Web, et met en garde les
développeurs si celle-ci ne convient pas. Ce fichier doit donc n'être déployé
qu'une seule fois afin de tester la configuration du serveur puis être retiré
aussitôt lorsqu'elle-ci est validée.
Ce n'est pas tout, il subsiste encore deux fichiers capables de trahir l'usage
de Symfony : favicon.ico
et apple-touch-icon.png
.
Il est conseillé de les supprimer s'ils ne sont pas remplacés par ceux de
l'application. Dans ce cas, il ne faut pas oublier de retirer la balise
de définition du favicon dans le gabarit base.html.twig
.
Les URLs par défaut
Les URLs par défaut
constituent un autre point délicat à vérifier au moment de déployer une
application Symfony. L'environnement de développement d'une Distribution
Standard de Symfony enregistre un certain nombre d'URLs par défaut en
important les fichiers de configuration des routes des bundles TwigBundle
et WebProfilerBundle
.
$ ./bin/console debug:router
-------------------------- -------- -------- ------ -----------------------------------
Name Method Scheme Host Path
-------------------------- -------- -------- ------ -----------------------------------
_wdt ANY ANY ANY /_wdt/{token}
_profiler_home ANY ANY ANY /_profiler/
_profiler_search ANY ANY ANY /_profiler/search
_profiler_search_bar ANY ANY ANY /_profiler/search_bar
_profiler_phpinfo ANY ANY ANY /_profiler/phpinfo
_profiler_search_results ANY ANY ANY /_profiler/{token}/search/results
_profiler_open_file ANY ANY ANY /_profiler/open
_profiler ANY ANY ANY /_profiler/{token}
_profiler_router ANY ANY ANY /_profiler/{token}/router
_profiler_exception ANY ANY ANY /_profiler/{token}/exception
_profiler_exception_css ANY ANY ANY /_profiler/{token}/exception.css
_twig_error_test ANY ANY ANY /_error/{code}.{_format}
homepage ANY ANY ANY /
-------------------------- -------- -------- ------ -----------------------------------
Comme ces routes sont importées par le fichier app/config/routing_dev.yml
,
elles ne sont heureusement pas disponibles en production. Sauf si les
développeurs ont fait l'erreur de déployer le contrôleur frontal web/app_dev.php
ou bien d'importer tout simplement ces routes quel que soit l'environnement.
Deux URLs par défaut supplémentaires peuvent trahir l'utilisation de Symfony. Il
s'agit des chemins /bundles
et /_fragment
. Le premier correspond au dossier
web/bundles/
dans lequel sont déployés par défaut les médias publics (images,
feuilles de styles, JavaScript, etc.) des bundles installés grâce à la commande
assets:install
. Il convient de déployer ces médias dans un répertoire différent
au moyen de liens symboliques par exemple ou par copie brute des fichiers des
bundles.
L'autre chemin par défaut, /_fragment
, correspond au mécanisme de fragments
de Symfony. C'est lui qui gère la génération de fragments de page Web lorsque
l'application réalise des sous-requêtes, ou expose des ESI
et H-Includes. Pour ces derniers,
les URLs sont publiques. Il convient de modifier le chemin par défaut par
un autre personnalisé dans le fichier app/config/config.yml
.
# app/config/config.yml
framework:
fragments:
enabled: true
path: /blocks
La section suivante s'intéresse aux pages d'erreur par défaut.
Les pages d'erreur
Les pages d'erreur par défaut sont un autre point critique auquel une attention
toute particulière doit être portée. En effet, Symfony fournit des thèmes de
pages d'erreur très basiques lorsqu'il s'agit de rendre une page 403, 404, 500,
etc.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>An Error Occurred: Not Found</title>
</head>
<body>
<h1>Oops! An Error Occurred</h1>
<h2>The server returned a "404 Not Found".</h2>
<div>
Something is broken. Please let us know what you were doing when this error occurred.
We will fix it as soon as possible. Sorry for any inconvenience caused.
</div>
</body>
</html>
Une manière simple et efficace pour détecter si un site est construit avec
Symfony consiste à demander une page qui n'existe pas. Si la réponse est
similaire à celle ci-dessus, alors bingo !
Il est donc particulièrement important d'ajouter à sa liste de contrôle de mise
en production que les gabarits de pages d'erreur ont bien été tous redéfinis et
personnalisés. Pour ce faire, il s'agit de créer les gabarits suivants au niveau
du dossier de l'application :
app/Resources/TwigBundle/views/Exception/error.html.twig
app/Resources/TwigBundle/views/Exception/error403.html.twig
app/Resources/TwigBundle/views/Exception/error404.html.twig
app/Resources/TwigBundle/views/Exception/error500.html.twig
- etc.
Attention aussi aux variations de ces pages d'erreurs suivant le format de
sortie négocié dans la requête. Si l'application a été configurée pour exposer
des ressources dans d'autres formats (JSON,
Atom, XML,
RDF, etc.) au moyen de la
variable spéciale _format
, il ne faudra pas oublier de surcharger les vues
correspondantes d'erreur. À minima :
app/Resources/TwigBundle/views/Exception/error.json.twig
app/Resources/TwigBundle/views/Exception/error.xml.twig
app/Resources/TwigBundle/views/Exception/error.atom.twig
- etc.
La documentation officielle de Symfony propose
un tutoriel complet
qui explique comment redéfinir et personnaliser chaque page d'erreur par défaut.
Les formulaires offrent eux aussi des indices utiles à la détection de l'usage
de Symfony. Pour commencer, le mode de rendu des formulaires Symfony peut
s'avérer très utile pour révéler certains indices.
En effet, les formulaires Symfony dont le rendu est basé sur l'un des thèmes
prédéfinis sont assez facilement reconnaissables. Il suffit pour cela de repérer
certaines répétitions de patrons de balisage HTML (nœuds, ordre des attributs,
formatage, choix des noms de classes CSS, etc.). Des patrons qui se répètent
indiquent que la génération du code HTML a sans doute été automatisée. Dans une
application Symfony standard, les thèmes par défaut de rendu de formulaires sont
situés dans le dossier
vendor/symfony/symfony/src/Symfony/Bridge/Twig/Resources/views/Form/
.
Certains blocs de balisage HTML sont plus facilement identifiables que d'autres.
C'est par exemple le cas avec un élément de formulaire conçu à partir d'une
définition RepeatedType
. Avec une configuration par défaut, les deux champs
générés auront un nommage similaire à celui-ci dessous :
<input type="email" name="foo[bar][first]"/>
<input type="email" name="foo[bar][second]"/>
Les deux clés first
et second
sont les noms par
défaut attribués à chacun des champs générés. C'est un bon indice pour
détecter si le site utilise le composant de formulaire de Symfony.
Bien entendu, le balisage HTML ne permet pas toujours d'affirmer avec certitude
que l'application est développée avec Symfony mais cela reste un outil pratique.
Il existe en réalité deux outils supplémentaires à disposition pour augmenter le
degré de certitude : le jeton de protection CSRF
et les messages d'erreur de validation.
Le jeton CSRF est
par défaut automatiquement ajouté à chaque formulaire Symfony
généré. Sa valeur étant calculée à partir de l'identifiant de session du client,
il garantit que le formulaire est bien soumis par l'utilisateur qui en a fait la
demande. Dans un formulaire Symfony, il prend la forme d'un champ caché dont le
nom est par défaut _token
. Ce nommage est propre à Symfony et révèle donc avec
une quasi certitude que l'application est développée sur la base de ce framework.
Pour le changer, c'est très simple. La première approche consiste à le faire
unitairement dans chaque formulaire grâce au système d'options :
// ...
class ContactType extends AbstractType
{
// ...
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefault('csrf_field_name', 'signature');
}
}
Sur une application avec quelques formulaires, c'est gérable mais lorsqu'il y
en a beaucoup, il convient de surcharger cette valeur globalement dans la
configuration générale du FrameworkBundle
.
# app/config/config.yml
framework:
form:
csrf_protection:
field_name: signature
Tous les formulaires générés par le composant Form bénéficieront
à présent d'un champ caché nommé signature
au lieu de
_token
pour transporter le jeton unique de protection
CSRF.
Enfin, pour accroître encore davantage le degré de certitude de détection de
Symfony, il convient de tester les messages d'erreur de validation du formulaire.
En effet, les contraintes de validation du composant Validator
de Symfony possèdent par défaut leurs propres messages d'erreur traduits
dans différentes langues. Lorsque les développeurs associent ces
contraintes aux propriétés de leurs objets PHP manipulés par les
formulaires, ils en oublient souvent de les personnaliser :
use Symfony\Component\Validator\Constraints as Assert;
class ContactRequest
{
/**
* @Assert\NotBlank
* @Assert\Length(min=5, max=60)
*/
public $subject;
}
En soumettant un formulaire dont les valeurs ont été volontairement laissées
vides ou invalides génèrera au niveau du formulaire des messages d'erreur par
défaut issus des fichiers de traduction de Symfony. En les comparant, il devient
très facile de détecter avec certitude que l'application utilise Symfony. Ces
messages d'erreur peuvent être redéfinis localement au niveau de l'usage des
contraintes comme l'illustre le listing ci-après :
use Symfony\Component\Validator\Constraints as Assert;
class ContactRequest
{
/**
* @Assert\NotBlank(message="Veuillez spécifier un sujet à votre demande.")
* @Assert\Length(
* min=5,
* max=60,
* minMessage="Le sujet doit comporter au moins 5 caractères.",
* maxMessage="Le sujet est limité à 60 caractères maximum."
* )
*/
public $subject;
}
Cette approche purement pragmatique est recommandée lorsque l'application
supporte une seule langue. Dans le cas des applications localisées, la solution
consiste à surcharger les fichiers de traduction de Symfony au niveau du dossier
de l'application.
<!-- app/Resources/translations/validators.fr.xlf -->
<?xml version="1.0"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file source-language="en" datatype="plaintext" original="file.ext">
<body>
<trans-unit id="22">
<source>This value should not be blank.</source>
<target>Ce champ est obligatoire.</target>
</trans-unit>
</body>
</file>
</xliff>
Le moment est venu de s'intéresser à la configuration de la sécurité.
La sécurité
Le composant de sécurité qui offre les mécanismes d'authentification et
d'autorisation des utilisateurs expose lui aussi des indices qui révèlent
l'usage de Symfony. C'est en particulier le cas quand un site Web met à
disposition un formulaire de connexion.
Le premier indice consiste à identifier le chemin vers lequel les données du
formulaire seront transmises. Les développeurs qui apprécient faire du copier /
coller de la documentation laisseront sans doute l'URL d'exemple /login_check
.
Il convient aussi d'analyser les noms des champs du formulaire qui transportent
l'identifiant et le mot de passe de connexion de l'utilisateur. Par défaut, le
pare-feu configuré dans le composant de sécurité s'attend à recevoir dans la
requête deux paramètres POST : _username
et _password
. À ces deux là
s'ajoutent aussi les paramètres _remember_me
, _target_path
et _failure_path
sous la forme de champs cachés optionnels. Par conséquent, cela dévoile encore
plus l'usage de Symfony par l'application. Il est donc recommandé de surcharger
ces valeurs par défaut au niveau de la configuration du bundle SecurityBundle
dans le fichier app/config/security.yml
.
# app/config/security.yml
security:
firewalls:
main:
form_login:
username_parameter: yolo_login
password_parameter: yolo_pwd
target_path_parameter: yolo_destination
failure_path_parameter: yolo_over
remember_me:
remember_me_parameter: yolo_me
Il existe sans doute d'autes paramètres ou indices exploitables pour
déterminer si un site Web utilise Symfony mais ceux-ci sont les plus
courants et les plus faciles à exploiter.
Conclusion
Cet article a présenté la plupart des points sensibles qui exposent à tous
l'usage de Symfony et qu'il convient de surcharger. Cela permet ainsi de limiter
le nombre d'indices qui révèlent l'usage de Symfony dans une application et donc
de réduire le risque que des personnes mal intentionnées contournent
l'application pour l'exploiter à des fins différentes. Les points d'attention
identifiés dans cet article ne concernent que ceux de Symfony mais il se peut
qu'il faille aussi porter une attention particulière à certains bundles tiers
sujets à exposer des indices sensibles.
Bien que cette liste se veuille être la plus exhaustive possible, il existe
certainement d'autres indices révélateurs de l'usage de Symfony qui n'y figurent
pas. Je vous invite à les adresser dans les commentaires afin que je puisse les
ajouter à cette liste par la suite.