normann / silverstripe-svg-sanitization
Sanitises SVG files in Silverstripe CMS, so an uploaded SVG can't run script or load remote resources: uploads are cleaned before they're stored, and a task cleans files already in the asset store.
Package info
github.com/normann/silverstripe-svg-sanitization
Type:silverstripe-vendormodule
pkg:composer/normann/silverstripe-svg-sanitization
Requires
- php: ^8.3
- ext-dom: *
- ext-libxml: *
- enshrined/svg-sanitize: ^1.1.1
- silverstripe/assets: ^3
- silverstripe/framework: ^6
- silverstripe/versioned: ^3
Requires (Dev)
Suggests
- silverstripe/mimevalidator: Checks that uploaded files' contents match their extension. Use SvgSanitisingMimeUploadValidator to combine it with SVG sanitising
Provides
None
Conflicts
None
Replaces
None
README
Sanitises SVG files in Silverstripe CMS 6, so an uploaded SVG can't run script or load remote resources.
An SVG is an XML document that can contain script. Opened directly from your site, or inlined into a page, a malicious SVG runs that script with your site's permissions: it can act as the logged-in CMS user, or show a fake login form on your domain. Checking the file extension or MIME type doesn't help, as the file really is an SVG.
This module:
- cleans every SVG upload before it's stored, so the original never reaches the asset store: the Files area,
UploadField,FileFieldand userforms file fields - rejects SVGs that can't be parsed, with a message to the person uploading
- reports and cleans SVGs already in the asset store (
sake tasks:sanitise-svgs) - gives you the sanitiser to clean SVG markup from anywhere else, e.g. before inlining it into a page
Sanitising is done by enshrined/svg-sanitize, an allowlist-based
sanitiser modelled on DOMPurify's SVG lists. It removes script, event handlers (onload etc.), javascript:
links, <foreignObject>, animations that change links, DTDs and entities, and references to remote resources.
Installation
composer require normann/silverstripe-svg-sanitization
Then flush (vendor/bin/sake flush, or ?flush=1).
| Module version | Branch | Silverstripe CMS | PHP |
|---|---|---|---|
| 1.x | 1 |
6 | 8.3+ |
Allowing SVG files
Silverstripe doesn't allow SVG files by default, and this module doesn't change that. To allow them:
SilverStripe\Assets\File: allowed_extensions: - svg
Don't allow .svgz: it's a compressed SVG, which can't be sanitised without decompressing it.
Setup
Uploads are sanitised as soon as the module is installed, by replacing the default upload validator
(SilverStripe\Assets\Upload_Validator) with SvgSanitisingUploadValidator through the Injector.
With silverstripe/mimevalidator
If you use silverstripe/mimevalidator, use
SvgSanitisingMimeUploadValidator, which runs the MIME type checks first and then sanitises SVGs:
--- Name: app-upload-validator After: - '#svg-sanitization' --- SilverStripe\Core\Injector\Injector: SilverStripe\Assets\Upload_Validator: class: Normann\SvgSanitization\Upload\SvgSanitisingMimeUploadValidator # The MIME types SVG files can be detected as SilverStripe\MimeValidator\MimeUploadValidator: MimeTypes: svg: - 'image/svg+xml' - 'image/svg'
If your project already replaces Upload_Validator with MimeUploadValidator, change it to this class, or
uploads won't be sanitised.
With your own upload validator
Use the SanitisesSvgUploads trait in it:
use Normann\SvgSanitization\Upload\SanitisesSvgUploads; use SilverStripe\Assets\Upload_Validator; class MyUploadValidator extends Upload_Validator { use SanitisesSvgUploads; }
Then point the Injector at your class, as above.
Sanitising existing files
Uploads are sanitised from when the module is installed, so check the SVGs already in your asset store:
vendor/bin/sake tasks:sanitise-svgs # Report only vendor/bin/sake tasks:sanitise-svgs --apply # Replace unsafe files with their sanitised content
UNSAFE icons/alert.svg (ID 12): Suspicious attribute 'onload' (line 2); Suspicious tag 'script' (line 4)
Sanitised and republished
INVALID logos/old.svg (ID 31): not a valid SVG, check and replace or delete it (...)
3 SVG files checked: 1 clean, 1 unsafe (1 sanitised), 1 need manual review.
- Clean files are left alone, so only files with unsafe content get a new version. Use
-vto list them too. - Files that were published are republished. Files with unpublished changes are skipped, as republishing them would also publish those changes: publish or revert them, then run the task again.
- Invalid and missing files are reported for you to check, and never changed.
- It changes files, so it only runs from the command line.
Sanitising SVG markup yourself
Use SvgSanitiser for SVG markup that doesn't come from an upload, e.g. before inlining a file into a page:
use Normann\SvgSanitization\SvgSanitiser; $sanitiser = SvgSanitiser::create(); $clean = $sanitiser->sanitise($file->getString()); if ($clean === null) { // Not a valid SVG document: don't output it } $sanitiser->wasChanged(); // Whether anything was removed (formatting differences don't count) $sanitiser->getIssues(); // What was removed, e.g. "Suspicious tag 'script' (line 3)"
The output has no XML declaration, so it can be inlined into HTML as is.
Options
| Option | Set on | Default | Description |
|---|---|---|---|
remove_remote_references |
Normann\SvgSanitization\SvgSanitiser |
true |
Remove references to remote resources, e.g. <image href="https://..."> and url() in <style>, so an SVG can't load content from, or send requests to, another site. A <use> that points at another document is always removed. |
Serving SVG files safely
Sanitising is the main protection. As a second layer, serve SVG files with a Content-Security-Policy that stops
any script in them running, in case one ever gets past the sanitiser. It doesn't affect SVGs shown with <img>
or in CSS, or inlined into a page.
A module can't change your web server's configuration, so add it yourself. For Apache, in public/.htaccess:
<IfModule mod_headers.c> <FilesMatch "(?i)\.svg$"> Header set Content-Security-Policy "default-src 'none'; img-src data:; style-src 'unsafe-inline'; sandbox" Header set X-Content-Type-Options "nosniff" </FilesMatch> </IfModule>
For nginx, in the server block that serves your assets:
location ~* \.svg$ { add_header Content-Security-Policy "default-src 'none'; img-src data:; style-src 'unsafe-inline'; sandbox" always; add_header X-Content-Type-Options "nosniff" always; }
If you use a CDN, check it passes these headers through.
Good to know
-
Formatting can change: comments, the XML declaration and some whitespace are removed, and empty elements are written out in full (
<path></path>). How the SVG looks doesn't change. -
Some editor-specific data is removed too, e.g. Adobe Illustrator's
i:aipgfelements, as it isn't on the allowlist. -
Public forms with file fields (userforms) accept SVG files if you allow them. They're sanitised, but if your forms don't need SVGs, block them there:
SilverStripe\UserForms\Model\EditableFormField\EditableFileField: allowed_extensions_blacklist: - svg
Development
composer install
composer lint # PHP code style
The PHP tests run in a Silverstripe project with this module installed from source, ie. with
composer require --prefer-source, or a symlinked Composer path repository (copies leave the tests out):
vendor/bin/phpunit vendor/normann/silverstripe-svg-sanitization/tests/php