diff --git a/composer.json b/composer.json index e750eb0d0aa..9e3de1f2245 100644 --- a/composer.json +++ b/composer.json @@ -35,6 +35,7 @@ "doctrine/migrations": "^3.0", "doctrine/orm": "^2.7", "egulias/email-validator": "^2.1", + "enshrined/svg-sanitize": "^0.15.4", "fakerphp/faker": "^1.9", "friendsofphp/proxy-manager-lts": "^1.0", "friendsofsymfony/oauth-server-bundle": ">2.0.0-alpha.0 ^2.0@dev", diff --git a/src/Sylius/Bundle/ApiBundle/composer.json b/src/Sylius/Bundle/ApiBundle/composer.json index 6a4c4fb5013..fa47c64d68a 100644 --- a/src/Sylius/Bundle/ApiBundle/composer.json +++ b/src/Sylius/Bundle/ApiBundle/composer.json @@ -26,6 +26,7 @@ "php": "^7.3", "api-platform/core": "^2.5", "doctrine/dbal": "^2.7", + "enshrined/svg-sanitize": "^0.15.4", "lexik/jwt-authentication-bundle": "^2.6", "sylius/core-bundle": "^1.7", "symfony/messenger": "^4.4 || ^5.2" diff --git a/src/Sylius/Component/Core/Uploader/ImageUploader.php b/src/Sylius/Component/Core/Uploader/ImageUploader.php index 495caf06916..21d5302ce5d 100644 --- a/src/Sylius/Component/Core/Uploader/ImageUploader.php +++ b/src/Sylius/Component/Core/Uploader/ImageUploader.php @@ -13,6 +13,7 @@ namespace Sylius\Component\Core\Uploader; +use enshrined\svgSanitize\Sanitizer; use Gaufrette\Filesystem; use Sylius\Component\Core\Generator\ImagePathGeneratorInterface; use Sylius\Component\Core\Generator\UploadedImagePathGenerator; @@ -22,12 +23,18 @@ class ImageUploader implements ImageUploaderInterface { + private const MIME_SVG_XML = 'image/svg+xml'; + private const MIME_SVG = 'image/svg'; + /** @var Filesystem */ protected $filesystem; /** @var ImagePathGeneratorInterface */ protected $imagePathGenerator; + /** @var Sanitizer */ + protected $sanitizer; + public function __construct( Filesystem $filesystem, ?ImagePathGeneratorInterface $imagePathGenerator = null @@ -41,6 +48,7 @@ public function __construct( } $this->imagePathGenerator = $imagePathGenerator ?? new UploadedImagePathGenerator(); + $this->sanitizer = new Sanitizer(); } public function upload(ImageInterface $image): void @@ -49,11 +57,13 @@ public function upload(ImageInterface $image): void return; } + /** @var File $file */ $file = $image->getFile(); - /** @var File $file */ Assert::isInstanceOf($file, File::class); + $fileContent = $this->sanitizeContent(file_get_contents($file->getPathname()), $file->getMimeType()); + if (null !== $image->getPath() && $this->has($image->getPath())) { $this->remove($image->getPath()); } @@ -64,10 +74,7 @@ public function upload(ImageInterface $image): void $image->setPath($path); - $this->filesystem->write( - $image->getPath(), - file_get_contents($image->getFile()->getPathname()) - ); + $this->filesystem->write($image->getPath(), $fileContent); } public function remove(string $path): bool @@ -79,6 +86,15 @@ public function remove(string $path): bool return false; } + protected function sanitizeContent(string $fileContent, string $mimeType): string + { + if (self::MIME_SVG_XML === $mimeType || self::MIME_SVG === $mimeType) { + $fileContent = $this->sanitizer->sanitize($fileContent); + } + + return $fileContent; + } + private function has(string $path): bool { return $this->filesystem->has($path); diff --git a/src/Sylius/Component/Core/composer.json b/src/Sylius/Component/Core/composer.json index d656d92d3eb..ac5a6bab7e3 100644 --- a/src/Sylius/Component/Core/composer.json +++ b/src/Sylius/Component/Core/composer.json @@ -27,6 +27,7 @@ ], "require": { "php": "^7.3", + "enshrined/svg-sanitize": "^0.15.4", "knplabs/gaufrette": "^0.8", "payum/payum": "^1.6", "php-http/guzzle6-adapter": "^2.0", diff --git a/symfony.lock b/symfony.lock index cf7e64da40d..9c829d7ae67 100644 --- a/symfony.lock +++ b/symfony.lock @@ -155,6 +155,9 @@ "egulias/email-validator": { "version": "2.1.19" }, + "enshrined/svg-sanitize": { + "version": "0.15.4" + }, "fakerphp/faker": { "version": "v1.12.0" }, diff --git a/tests/Functional/ImageUploaderTest.php b/tests/Functional/ImageUploaderTest.php new file mode 100644 index 00000000000..3bc4cecef14 --- /dev/null +++ b/tests/Functional/ImageUploaderTest.php @@ -0,0 +1,59 @@ +getContainer(); + + $imageUploader = self::$container->get('sylius.image_uploader'); + $fileSystem = self::$container->get('gaufrette.sylius_image_filesystem'); + + $file = new UploadedFile(__DIR__ . '/../Resources/xss.svg', 'xss.svg'); + Assert::assertStringContainsString('getContent($file)); + + $image = new ProductImage(); + $image->setFile($file); + + $imageUploader->upload($image); + + $sanitizedFile = $fileSystem->get($image->getPath()); + Assert::assertStringNotContainsString('getContent()); + } + + private function getContent(UploadedFile $file): string + { + $content = file_get_contents($file->getPathname()); + + if (false === $content) { + throw new FileException(sprintf('Could not get the content of the file "%s".', $file->getPathname())); + } + + return $content; + } +} diff --git a/tests/Resources/xss.svg b/tests/Resources/xss.svg new file mode 100644 index 00000000000..08ecd246d46 --- /dev/null +++ b/tests/Resources/xss.svg @@ -0,0 +1,9 @@ + + + + + + +