Cacher le nom de fichier du contrôleur d'entrée Symfony

Publié le 23/08/2019 • Actualisé le 10/11/2024

Dans cet article nous allons voir comment cacher le nom de fichier du contrôleur d'entrée principal d'une application Symfony ("main front controller" en anglais) pour qu'il soit impossible d'accéder au site en tapant "index.php" dans l'URL. Le moins d'informations les utilisateurs (ou hackers bien sûr) auront sur l'implémentation technique de votre site, le mieux ce sera. C'est parti ! 😎


English language detected! 🇬🇧

  We noticed that your browser is using English. Do you want to read this post in this language?

Read the english version 🇬🇧 Close

C'est l'été, cet article sera donc assez concis. En fait c'est un test que je voulais faire sur ce blog. Je voulais avoir un contrôleur spécifique pour l'environnement de production (comme avec Symfony3) et pouvoir cacher le contrôleur d'entrée principal pour que l'accès aux pages par "index.php" aboutisse à une erreur 404 et ce, sans avoir à utiliser de fichier .htaccess ni de redirections (quand on utilise Apache comme serveur web). Ce n'est pas considéré comme une bonne pratique, utilisez à vos risques et périls !

» Publié dans "Une semaine Symfonique 660" (du 19 au 25 Août 2019).

utilisez à vos risques et périls

Configuration

  • PHP 8.3
  • Symfony 6.4
  • Apache 2.4

Introduction

Le contrôleur d'entrée principal est le fichier responsable de traiter toutes les requêtes d'une application Symfony. Quand on utilise Symfony4, c'est le fichier index.php situé dans le répertoire "public". Par exemple quand on accède au site officiel Symfony, on remarque que les URLs suivantes sont accessibles : (ça a été corrigé depuis l'écriture de cet article qui date de 2019)

Le but ici va être d'empêcher les utilisateurs, bots ou moteurs de recherche d'accéder aux URLs via le fichier index.php.

Création du point d'entrée spécifique de production

Ajoutez un fichier dans le répertoire "public" de votre application et nommez le avec une chaîne aléatoire. (ex: ajJoNGJix4KRMM3xwhgLXG4GUgJHhyFk.php) Ajoutez le code suivant :

<?php

// public/ajJoNGJix4KRMM3xwhgLXG4GUgJHhyFk.php
// this is a fake name, not the one used by this website! 😁

declare(strict_types=1);

use App\Kernel;

require_once dirname(__DIR__).'/vendor/autoload_runtime.php';

return static fn (array $context) => new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);

Nous avons supprimé toutes les parties relatives à l'environnement de développement et aux fonctionnalités de débug pour garder le strict minimum. (il n'y a pas de différence de performance substantielle avec le contrôleur original) Nous avons toujours le fichier index.php, dans ce projet j'ai préféré le renommer en index_dev.php afin qu'il n'y ait pas de confusion. Mais on peut garder bien sûr le nom original.

Attention, si vous utilisez les variables d'environnement TRUSTED_PROXIES or TRUSTED_HOSTS vous devez garder les lignes suivantes :

if ($trustedProxies = $_SERVER['TRUSTED_PROXIES'] ?? false) {
    Request::setTrustedProxies(explode(',', $trustedProxies), Request::HEADER_X_FORWARDED_ALL ^ Request::HEADER_X_FORWARDED_HOST);
}

if ($trustedHosts = $_SERVER['TRUSTED_HOSTS'] ?? false) {
    Request::setTrustedHosts(explode(',', $trustedHosts));
}

Cacher le contrôleur d'entrée

Maintenant, cachons le point d'entrée. La première chose à faire est d'exclure le fichier index.php de votre procédure de déploiement. Pour déployer le code relatif à ce blog j'utilise EasyDeploy, vous trouverez le fichier de déploiement ici). Je supprime le fichier index_dev.php avec la fonction controllersToRemove(). Maintenant, nous devons indiquer au serveur web d'utiliser le nouveau fichier. Par exemple si on utilise Apache :

# https://stackoverflow.com/questions/59921221/apache-how-to-handle-unknown-php-file-like-standard-urls
# configuration/vhosts/prod/www.strangebuzz.com-le-ssl.conf
<IfModule mod_ssl.c>
    <VirtualHost *:443>
        ServerName www.strangebuzz.com
        ServerAlias strangebuzz.com
        DocumentRoot /var/www-protected/strangebuzz.com/public
        DirectoryIndex /ajJoNGJix4KRMM3xwhgLXG4GUgJHhyFk.php

        <Directory /var/www-protected/strangebuzz.com/public>
            AllowOverride All
            Require all granted
            # this is a fake one, again! 😁
            FallbackResource /ajJoNGJix4KRMM3xwhgLXG4GUgJHhyFk.php
            #FallbackResource /index.php

            # 1st test to move .htaccess here
            RewriteEngine On
            RewriteBase /

            RewriteCond %{THE_REQUEST} ^.*/index\.php
            RewriteRule ^(.*)index.php$ /$1 [R=301,L]

            RewriteCond %{REQUEST_FILENAME} !-f
            RewriteCond %{REQUEST_FILENAME} !-d
            RewriteRule . /ajJoNGJix4KRMM3xwhgLXG4GUgJHhyFk.php [L]
        </Directory>

        ErrorDocument 404 https://www.strangebuzz.com/404

        ErrorLog /var/log/apache2/strangebuzz.com_error.log
        CustomLog /var/log/apache2/strangebuzz.com_access.log combined

        SSLCertificateFile /etc/letsencrypt/live/strangebuzz.com/fullchain.pem
        SSLCertificateKeyFile /etc/letsencrypt/live/strangebuzz.com/privkey.pem
        Include /etc/letsencrypt/options-ssl-apache.conf

        # . to www
        RewriteEngine on
        RewriteCond %{SERVER_NAME} =strangebuzz.com
        RewriteRule ^ https://www.strangebuzz.com [END,NE,R=permanent]

    </VirtualHost>
</IfModule>
# Used as full snippet in templates/blog/posts/_39.html.twig

Comme vous pouvez le voir, nous avons changé la solution de repli (fallback) pour utiliser le nouveau fichier au lieu de l'ancien fichier index.php. Et voilà ! Maintenant, maintenant quand on essaie d'accèder au site avec l'ancien nom de fichier /index.php nous aboutissons à une erreur 404, il n'y a donc plus qu'une seule URL accessible pour cette ressource et nous sommes donc sûrs que l'URL canonique sera toujours celle sans nom de fichier php.

Bonus:
1) Il y a un article sur ce blog ou vous pourriez trouver le nom réel du contrôleur d'entrée de ce site, pouvez-vous le trouver ?
2) Pourquoi est-ce toujours indiqué index.php?

Cliquez ici pour voir les réponses.

1) Il se trouve ici : L'antisèche intéractive de la classe Symfony Request. Le nom de fichier est retourné par la fonction getScriptName() de l'objet / valeur (value object) Request.
2) Je triche un peu, j'ai simplement mis la valeur en dur au lieu d'utiliser le résultat de la fonction ! 😁

[
    'name' => 'getScriptName',
    'parameters' => '',
    'type' => 'string',
    //'result' => $r->getScriptName(),
    'result' => 'index.php', // hide real controller name
    'show' => !$refresh,
    'url' => 812
],

Et voilà ! J'espère que vous avez aimé. Découvrez d'autres informations en rapport à cet article avec les liens ci-dessous. Comme toujours, retours, likes et retweets sont les bienvenus. (voir la boîte ci-dessous) À bientôt ! COil. 😊

 La documentation  Plus sur Stackoverflow  Plus sur le web

  Travaillez avec moi !


A vous de jouer !

Ces articles vous ont été utiles ? Vous pouvez m'aider à votre tour de plusieurs manières : (cf le tweet à droite pour me contacter )

  • Me remonter des erreurs ou typos.
  • Me remonter des choses qui pourraient être améliorées.
  • Aimez et retweetez !
  • Suivez moi sur Twitter
  • Inscrivez-vous au flux RSS.
  • Cliquez sur les boutons Plus sur Stackoverflow pour me faire gagner des badges "annonceur" 🏅.

Merci d'avoir tenu jusque ici et à très bientôt sur Strangebuzz ! 😉

COil