<?php
namespace AppBundle\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class XSSSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
KernelEvents::REQUEST => ['onKernelRequest', 1000],
];
}
public function onKernelRequest(RequestEvent $event): void
{
$postValues = $event->getRequest()->request->all();
if (count($postValues) > 0) {
$postValues = $this->getProtectedValues($postValues);
$event->getRequest()->request->replace($postValues);
}
$getValues = $event->getRequest()->query->all();
if (count($getValues) > 0) {
$getValues = $this->getProtectedValues($getValues);
$event->getRequest()->query->replace($getValues);
}
}
private function getProtectedValues(array &$data): array
{
foreach ($data as $key => &$value) {
if (is_array($value)) {
$this->getProtectedValues($value);
} else {
$data[$key] = $this->filterText($value);
}
}
return $data;
}
private function filterText(?string $value): string
{
if(null === $value || '' === trim($value)) {
return '';
}
return $this->escapeJsEvent($this->removeScriptTag(trim($value)));
}
private function escapeJsEvent(string $value): string
{
/** @var string $value */
$value = (string) preg_replace('/(<.+?)(?<=\s)on[a-z]+\s*=\s*(?:([\'"])(?!\2).+?\2|(?:\S+?\(.*?\)(?=[\s>])))(.*?>)/i', "$1 $3", $value);
return $value;
}
private function removeScriptTag(string $text): string
{
$search = [
sprintf("'<%s[^>]*?>.*?</%s>'si", 'script', 'script'),
sprintf("'<%s[^>]*?>.*?</%s>'si", 'iframe', 'iframe'),
];
/** @var string $text */
$text = (string) preg_replace($search, ['', ''], $text);
return (string) preg_replace_callback("'&#(\d+);'", static function ($m) { return chr($m[1]); }, $text);
}
}