src/EventSubscriber/UploadGuardSubscriber.php line 25

Open in your IDE?
  1. <?php
  2. namespace App\EventSubscriber;
  3. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  4. use Symfony\Component\HttpFoundation\File\UploadedFile;
  5. use Symfony\Component\HttpFoundation\Response;
  6. use Symfony\Component\HttpKernel\Event\RequestEvent;
  7. use Symfony\Component\HttpKernel\KernelEvents;
  8. /**
  9.  * SÉCURITÉ : contrôle central de tous les fichiers envoyés au backoffice.
  10.  * Refuse les noms contenant une extension exécutable (php, phtml, phar, cgi, sh, exe...), y compris en
  11.  * double extension (image.php.jpg), les noms cachés (.htaccess) et les fichiers qui contiennent du code PHP.
  12.  */
  13. class UploadGuardSubscriber implements EventSubscriberInterface
  14. {
  15.     private const FORBIDDEN = '~\.(php\d*|phtml|pht|phps|phar|inc|pl|py|cgi|sh|bash|shtml|exe|bat|cmd|com|msi|jsp|jspx|asp|aspx|htaccess|htpasswd|ini)(\.|$)~i';
  16.     public static function getSubscribedEvents(): array
  17.     {
  18.         return [KernelEvents::REQUEST => ['onKernelRequest', 5]];
  19.     }
  20.     public function onKernelRequest(RequestEvent $event): void
  21.     {
  22.         if (!$event->isMainRequest()) {
  23.             return;
  24.         }
  25.         $request = $event->getRequest();
  26.         if ($request->files->count() === 0) {
  27.             return;
  28.         }
  29.         $fichiers = [];
  30.         $this->collecter($request->files->all(), $fichiers);
  31.         foreach ($fichiers as $f) {
  32.             if (!$this->estAcceptable($f)) {
  33.                 $event->setResponse(new Response(
  34.                     'Type de fichier non autorisé.',
  35.                     400,
  36.                     ['Content-Type' => 'text/plain; charset=UTF-8']
  37.                 ));
  38.                 return;
  39.             }
  40.         }
  41.     }
  42.     private function collecter($valeur, array &$sortie): void
  43.     {
  44.         if ($valeur instanceof UploadedFile) {
  45.             $sortie[] = $valeur;
  46.         } elseif (\is_array($valeur)) {
  47.             foreach ($valeur as $v) {
  48.                 $this->collecter($v, $sortie);
  49.             }
  50.         }
  51.     }
  52.     private function estAcceptable(UploadedFile $f): bool
  53.     {
  54.         $nom = (string) $f->getClientOriginalName();
  55.         if ($nom === '' || str_contains($nom, "\0") || $nom[0] === '.' || preg_match(self::FORBIDDEN, $nom)) {
  56.             return false;
  57.         }
  58.         $chemin = $f->getPathname();
  59.         if ($f->isValid() && is_readable($chemin)) {
  60.             $debut = (string) @file_get_contents($chemin, false, null, 0, 2 * 1024 * 1024);
  61.             if (preg_match('~<\?php|<\?=|<\?\s~i', $debut)) {
  62.                 return false;
  63.             }
  64.         }
  65.         return true;
  66.     }
  67. }