Skip to content

Commit

Permalink
Svg sanitization (#11386)
Browse files Browse the repository at this point in the history
* setting up the svg saniziter on logo and assets upload

* more detailed exception message

* changed to mime type check instead of file extension

* adding the sanitization to "Upload new version"

* refactor to sanitize on preAdd/preUpdate, rollback AssetController.php

* fix resource|string, null given on mime_content_type

* using symfony mime component + small tweaks

* avoiding save without changes case

* tweak when checking if is image type
  • Loading branch information
kingjia90 committed Feb 11, 2022
1 parent 5aa4d71 commit 7697f70
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 4 deletions.
24 changes: 20 additions & 4 deletions bundles/AdminBundle/Controller/Admin/SettingsController.php
Expand Up @@ -46,8 +46,11 @@
use Symfony\Component\HttpKernel\Event\TerminateEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\Mime\MimeTypes;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Yaml\Yaml;
use enshrined\svgSanitize\Sanitizer;


/**
* @Route("/settings")
Expand Down Expand Up @@ -104,17 +107,30 @@ public function displayCustomLogoAction(Request $request)
*/
public function uploadCustomLogoAction(Request $request)
{
$sourcePath = $_FILES['Filedata']['tmp_name'];
$fileExt = File::getFileExtension($_FILES['Filedata']['name']);
if (!in_array($fileExt, ['svg', 'png', 'jpg'])) {
throw new \Exception('Unsupported file format');
}

if ($fileExt === 'svg' && stripos(file_get_contents($_FILES['Filedata']['tmp_name']), '<script')) {
throw new \Exception('Scripts in SVG files are not supported');
}

$storage = Tool\Storage::get('admin');
$storage->writeStream(self::CUSTOM_LOGO_PATH, fopen($_FILES['Filedata']['tmp_name'], 'rb'));

$fileMimeType = MimeTypes::getDefault()->guessMimeType($sourcePath);

if ($fileMimeType === 'image/svg+xml') {
$fileContent = file_get_contents($sourcePath);

$sanitizer = new Sanitizer();
$sanitizedFileContent = $sanitizer->sanitize($fileContent);
if ($sanitizedFileContent) {
$storage->write(self::CUSTOM_LOGO_PATH, $sanitizedFileContent);
}else{
throw new \Exception('SVG Sanitization failed, probably due badly formatted XML. Filename:'.$sourcePath);
}
}else {
$storage->writeStream(self::CUSTOM_LOGO_PATH, fopen($sourcePath, 'rb'));
}

// set content-type to text/html, otherwise (when application/json is sent) chrome will complain in
// Ext.form.Action.Submit and mark the submission as failed
Expand Down
80 changes: 80 additions & 0 deletions bundles/CoreBundle/EventListener/AssetSanitizationListener.php
@@ -0,0 +1,80 @@
<?php

/**
* Pimcore
*
* This source file is available under two different licenses:
* - GNU General Public License version 3 (GPLv3)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license http://www.pimcore.org/license GPLv3 and PCL
*/

namespace Pimcore\Bundle\CoreBundle\EventListener;


use Pimcore\Event\AssetEvents;
use Pimcore\Event\Model\ElementEventInterface;
use Pimcore\Model\Asset;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use enshrined\svgSanitize\Sanitizer;
use Symfony\Component\Mime\MimeTypes;

/**
* @internal
*/
class AssetSanitizationListener implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
AssetEvents::PRE_ADD => 'sanitizeAsset',
AssetEvents::PRE_UPDATE => 'sanitizeAsset',
];
}

/**
* @param ElementEventInterface $e
*/
public function sanitizeAsset(ElementEventInterface $e)
{
$element = $e->getElement();

if ($element instanceof Asset\Image && $element->getDataChanged()) {
$assetStream = $element->getStream();

if (isset($assetStream)) {
$streamMetaData = stream_get_meta_data($assetStream);
$mime = MimeTypes::getDefault()->guessMimeType($streamMetaData['uri']);

if ($mime === 'image/svg+xml') {
$sanitizedData = $this->sanitizeSVG(stream_get_contents($assetStream));
$element->setData($sanitizedData);
}
}
}
}

/**
* @param string $fileContent
*
* @return string
*
* @throws \Exception
*/

protected function sanitizeSVG(string $fileContent)
{
$sanitizer = new Sanitizer();
$sanitizedFileContent = $sanitizer->sanitize($fileContent);

if (!$sanitizedFileContent) {
throw new \Exception('SVG Sanitization failed, probably due badly formatted XML.');
}

return $sanitizedFileContent;
}
}
2 changes: 2 additions & 0 deletions bundles/CoreBundle/Resources/config/event_listeners.yaml
Expand Up @@ -147,3 +147,5 @@ services:
$debugToolbarListener: '@?web_profiler.debug_toolbar'

Pimcore\Bundle\CoreBundle\EventListener\MessengerClearRuntimeCacheListener: ~

Pimcore\Bundle\CoreBundle\EventListener\AssetSanitizationListener: ~
1 change: 1 addition & 0 deletions composer.json
Expand Up @@ -59,6 +59,7 @@
"symfony/doctrine-messenger": "^5.2.0",
"egulias/email-validator": "^3.0.0",
"endroid/qr-code": "^4",
"enshrined/svg-sanitize": "^0.14.1",
"friendsofsymfony/jsrouting-bundle": "^2.7",
"geoip2/geoip2": "^2.9",
"google/apiclient": "^2.10",
Expand Down

0 comments on commit 7697f70

Please sign in to comment.