<?php
namespace App\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* SÉCURITÉ : contrôle central de tous les fichiers envoyés au backoffice.
* Refuse les noms contenant une extension exécutable (php, phtml, phar, cgi, sh, exe...), y compris en
* double extension (image.php.jpg), les noms cachés (.htaccess) et les fichiers qui contiennent du code PHP.
*/
class UploadGuardSubscriber implements EventSubscriberInterface
{
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';
public static function getSubscribedEvents(): array
{
return [KernelEvents::REQUEST => ['onKernelRequest', 5]];
}
public function onKernelRequest(RequestEvent $event): void
{
if (!$event->isMainRequest()) {
return;
}
$request = $event->getRequest();
if ($request->files->count() === 0) {
return;
}
$fichiers = [];
$this->collecter($request->files->all(), $fichiers);
foreach ($fichiers as $f) {
if (!$this->estAcceptable($f)) {
$event->setResponse(new Response(
'Type de fichier non autorisé.',
400,
['Content-Type' => 'text/plain; charset=UTF-8']
));
return;
}
}
}
private function collecter($valeur, array &$sortie): void
{
if ($valeur instanceof UploadedFile) {
$sortie[] = $valeur;
} elseif (\is_array($valeur)) {
foreach ($valeur as $v) {
$this->collecter($v, $sortie);
}
}
}
private function estAcceptable(UploadedFile $f): bool
{
$nom = (string) $f->getClientOriginalName();
if ($nom === '' || str_contains($nom, "\0") || $nom[0] === '.' || preg_match(self::FORBIDDEN, $nom)) {
return false;
}
$chemin = $f->getPathname();
if ($f->isValid() && is_readable($chemin)) {
$debut = (string) @file_get_contents($chemin, false, null, 0, 2 * 1024 * 1024);
if (preg_match('~<\?php|<\?=|<\?\s~i', $debut)) {
return false;
}
}
return true;
}
}