Skip to content

Commit

Permalink
Sanitize SVG uploads
Browse files Browse the repository at this point in the history
  • Loading branch information
duncanmcclean committed Feb 11, 2024
1 parent 43bb34f commit 01f0a13
Show file tree
Hide file tree
Showing 2 changed files with 179 additions and 0 deletions.
12 changes: 12 additions & 0 deletions src/Http/Controllers/GuestEntryController.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Rhukster\DomSanitizer\DOMSanitizer;
use Statamic\Facades\Asset;
use Statamic\Facades\AssetContainer;
use Statamic\Facades\Collection;
Expand Down Expand Up @@ -258,6 +260,16 @@ protected function uploadFile(string $key, Field $field, Request $request)

/* @var \Illuminate\Http\Testing\File $file */
foreach ($uploadedFiles as $uploadedFile) {
if (Str::endsWith($uploadedFile->getClientOriginalExtension(), 'svg')) {
$sanitizer = new DOMSanitizer(DOMSanitizer::SVG);

$contents = $sanitizer->sanitize($svg = File::get($uploadedFile->getPathname()), [
'remove-xml-tags' => ! Str::startsWith($svg, '<?xml'),
]);

File::put($uploadedFile->getPathname(), $contents);
}

$path = '/'.$uploadedFile->storeAs(
isset($field->config()['folder'])
? $field->config()['folder']
Expand Down
167 changes: 167 additions & 0 deletions tests/Http/Controllers/GuestEntryControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,78 @@
$this->assertIsString($entry->get('attachment'));
});

it('can store entry and ensure uploaded SVG file is sanitized', function () {
AssetContainer::make('assets')->disk('local')->save();

Blueprint::make('comments')
->setNamespace('collections.comments')
->setContents([
'title' => 'Comments',
'sections' => [
'main' => [
'display' => 'main',
'fields' => [
[
'handle' => 'title',
'field' => [
'type' => 'text',
],
],
[
'handle' => 'slug',
'field' => [
'type' => 'slug',
],
],
[
'handle' => 'attachment',
'field' => [
'mode' => 'list',
'container' => 'assets',
'restrict' => false,
'allow_uploads' => true,
'show_filename' => true,
'display' => 'Attachment',
'type' => 'assets',
'icon' => 'assets',
'listable' => 'hidden',
'max_items' => 1,
],
],
],
],
],
])
->save();

Collection::make('comments')->save();

$this
->post(route('statamic.guest-entries.store'), [
'_collection' => encrypt('comments'),
'title' => 'This is great',
'slug' => 'this-is-great',
'attachment' => UploadedFile::fake()->createWithContent('foobar.svg', '<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" width="500" height="500"><script type="text/javascript">alert(`Bad stuff could go in here.`);</script></svg>'),
])
->assertRedirect();

$entry = Entry::all()->last();

$this->assertNotNull($entry);
$this->assertSame($entry->collectionHandle(), 'comments');
$this->assertSame($entry->get('title'), 'This is great');
$this->assertSame($entry->slug(), 'this-is-great');

$this->assertNotNull($entry->get('attachment'));
$this->assertIsString($entry->get('attachment'));

$file = Storage::disk('local')->get($entry->get('attachment'));

$this->assertStringNotContainsString('<script', $file);
$this->assertStringNotContainsString('Bad stuff could go in here.', $file);
$this->assertStringNotContainsString('</script>', $file);
});

it('cant store an entry when uploading a PHP file', function () {
AssetContainer::make('assets')->disk('local')->save();

Expand Down Expand Up @@ -1528,6 +1600,101 @@
$this->assertIsString($entry->get('attachment'));
});

it('can update entry and ensure uploaded SVG file is sanitized', function () {
AssetContainer::make('assets')->disk('local')->save();

Blueprint::make('albums')
->setNamespace('collections.albums')
->setContents([
'title' => 'Albums',
'sections' => [
'main' => [
'display' => 'main',
'fields' => [
[
'handle' => 'title',
'field' => [
'type' => 'text',
],
],
[
'handle' => 'artist',
'field' => [
'type' => 'text',
],
],
[
'handle' => 'slug',
'field' => [
'type' => 'slug',
],
],
[
'handle' => 'record_label',
'field' => [
'type' => 'text',
],
],
[
'handle' => 'attachment',
'field' => [
'mode' => 'list',
'container' => 'assets',
'restrict' => false,
'allow_uploads' => true,
'show_filename' => true,
'display' => 'Attachment',
'type' => 'assets',
'icon' => 'assets',
'listable' => 'hidden',
'max_items' => 1,
],
],
],
],
],
])
->save();

Collection::make('albums')->save();

Entry::make()
->id('allo-mate-idee')
->collection('albums')
->slug('allo-mate')
->data([
'title' => 'Allo Mate!',
'artist' => 'Guvna B',
])
->save();

$this
->post(route('statamic.guest-entries.update'), [
'_collection' => encrypt('albums'),
'_id' => encrypt('allo-mate-idee'),
'record_label' => 'Unknown',
'attachment' => UploadedFile::fake()->createWithContent('foobar.svg', '<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" width="500" height="500"><script type="text/javascript">alert(`Bad stuff could go in here.`);</script></svg>'),
])
->assertRedirect();

$entry = Entry::find('allo-mate-idee');

$this->assertNotNull($entry);
$this->assertSame($entry->collectionHandle(), 'albums');
$this->assertSame($entry->get('title'), 'Allo Mate!');
$this->assertSame($entry->get('record_label'), 'Unknown');
$this->assertSame($entry->slug(), 'allo-mate');

$this->assertNotNull($entry->get('attachment'));
$this->assertIsString($entry->get('attachment'));

$file = Storage::disk('local')->get($entry->get('attachment'));

$this->assertStringNotContainsString('<script', $file);
$this->assertStringNotContainsString('Bad stuff could go in here.', $file);
$this->assertStringNotContainsString('</script>', $file);
});

it('cant update entry when uploading a PHP file', function () {
AssetContainer::make('assets')->disk('local')->save();

Expand Down

0 comments on commit 01f0a13

Please sign in to comment.