Journal — Sécurité
Illustration : auteur inconnu — CC0 1.0 · source
Trois motifs reviennent dans presque tous les audits de modules développés à la demande. Aucun n'est exotique, tous sont évitables, et on les repère en lisant le code.
Un module développé sur mesure échappe aux radars. Il n'a pas de communauté qui remonte les problèmes, pas de bulletin de sécurité, pas de mise à jour automatique. Il fait son travail, année après année, et personne ne le relit.
Voici les trois motifs que j'y retrouve le plus souvent, dans l'ordre où je les cherche.
C'est de loin le plus fréquent, et il prend toujours la même forme : un identifiant lu dans l'adresse ou dans un formulaire, inséré tel quel dans une chaîne SQL.
// à ne pas faire
$sql = 'DELETE FROM '.$prefixe.'table WHERE id = "'.$id.'"';
Ce qui rend le motif difficile à repérer, c'est qu'il cohabite avec du code correct. Dans le même fichier, souvent dans la même méthode, l'insertion échappe proprement ses valeurs pendant que la suppression, écrite trois minutes plus tard, ne le fait pas. Ce n'est pas une question de compétence : c'est une question de relecture.
Le correctif est mécanique. Un identifiant se convertit en entier, une chaîne passe par la fonction d'échappement de la plateforme, et les paramètres se lisent via l'objet requête plutôt que dans les variables globales — ce qui règle au passage le cas du paramètre absent.
Le deuxième motif est plus discret. La page est dans le back-office, donc « elle est protégée » : il faut être connecté pour l'atteindre.
Sauf qu'être authentifié n'est pas être autorisé. Un compte créé pour un saisonnier, un prestataire externe ou un stagiaire dispose souvent d'un profil très restreint — et se retrouve pourtant à pouvoir déclencher une action qui touche des données qui ne le concernent pas, parce que le module n'a jamais vérifié autre chose que la présence d'une session.
La question à poser sur chaque action sensible n'est pas « qui peut arriver ici », mais « quel profil a le droit de faire ça », et la réponse doit être écrite dans le code.
Import de catalogue, téléversement d'un visuel, génération d'un document : dès qu'un nom de fichier vient de l'extérieur, il faut le traiter comme hostile. Un nom peut contenir de quoi remonter l'arborescence, et une extension peut mentir sur le contenu.
La règle qui évite l'essentiel : ne jamais réutiliser le nom fourni. On génère un nom, on impose le répertoire, on valide le type réel du contenu, et on stocke hors des dossiers servis directement par le serveur web.
Sans être développeur, vous pouvez déjà poser trois questions à qui maintient vos modules :
Trois réponses claires valent mieux qu'un rapport de cent pages. Et si les réponses sont floues, c'est précisément le moment de faire relire le code — avant que quelqu'un d'autre s'en charge.
Migration Symfony passe de 4.4 à 6.4 et FrameworkBundleAdminController disparaît. Inventaire de ce qui casse, module par module, et de ce qui ne bouge pas.
Reprise Pas d'accès, pas de dépôt, pas de documentation. La méthode d'audit que j'applique avant de dire si on répare ou si on refait.
Retour d'expérience Trois versions de Symfony, deux de PHP, un seul code. J'ai essayé sur mes propres modules — voilà où passe la frontière entre une branche et deux.