Skip to content

Commit d28f0b9

Browse files
committed
Improve escaping of BrandSetting & EditorSetting custom CSS settings
1 parent 29f823b commit d28f0b9

File tree

4 files changed

+132
-2
lines changed

4 files changed

+132
-2
lines changed

modules/backend/models/BrandSetting.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ public static function renderCss()
195195
$customCss = '/* ' . e($ex->getMessage()) . ' */';
196196
}
197197

198-
return $customCss;
198+
return strip_tags($customCss);
199199
}
200200

201201
public static function compileCss()

modules/backend/models/EditorSetting.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ public static function renderCss()
256256
$customCss = '/* ' . e($ex->getMessage()) . ' */';
257257
}
258258

259-
return $customCss;
259+
return strip_tags($customCss);
260260
}
261261

262262
public static function compileCss()
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
namespace Backend\Tests\Models;
4+
5+
use Backend\Models\BrandSetting;
6+
use System\Tests\Bootstrap\PluginTestCase;
7+
8+
class BrandSettingTest extends PluginTestCase
9+
{
10+
public function setUp(): void
11+
{
12+
parent::setUp();
13+
14+
// Reset the cached instance so each test starts fresh
15+
\System\Behaviors\SettingsModel::clearInternalCache();
16+
}
17+
18+
public function tearDown(): void
19+
{
20+
// Clean up the settings record
21+
BrandSetting::instance()->resetDefault();
22+
\System\Behaviors\SettingsModel::clearInternalCache();
23+
24+
parent::tearDown();
25+
}
26+
27+
/**
28+
* Test that renderCss output does not contain script tags even when
29+
* malicious CSS using LESS escape syntax is stored in the database.
30+
*/
31+
public function testRenderCssStripsScriptTags()
32+
{
33+
$maliciousCss = '.x { content: ~"</style><script>alert(1)</script><style>"; }';
34+
35+
BrandSetting::set('custom_css', $maliciousCss);
36+
37+
\System\Behaviors\SettingsModel::clearInternalCache();
38+
\Illuminate\Support\Facades\Cache::forget(BrandSetting::instance()->cacheKey);
39+
40+
$renderedCss = BrandSetting::renderCss();
41+
42+
$this->assertStringNotContainsString('<script>', $renderedCss);
43+
$this->assertStringNotContainsString('</script>', $renderedCss);
44+
$this->assertStringNotContainsString('</style>', $renderedCss);
45+
}
46+
47+
/**
48+
* Test that normal CSS content is preserved through renderCss.
49+
*/
50+
public function testRenderCssPreservesNormalCss()
51+
{
52+
$normalCss = '.my-class { color: red; font-size: 14px; }';
53+
54+
BrandSetting::set('custom_css', $normalCss);
55+
56+
\System\Behaviors\SettingsModel::clearInternalCache();
57+
\Illuminate\Support\Facades\Cache::forget(BrandSetting::instance()->cacheKey);
58+
59+
$renderedCss = BrandSetting::renderCss();
60+
61+
$this->assertStringContainsString('color', $renderedCss);
62+
$this->assertStringContainsString('font-size', $renderedCss);
63+
$this->assertDoesNotMatchRegularExpression('/<[a-z\/!]/', $renderedCss);
64+
}
65+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
namespace Backend\Tests\Models;
4+
5+
use Backend\Models\EditorSetting;
6+
use System\Tests\Bootstrap\PluginTestCase;
7+
8+
class EditorSettingTest extends PluginTestCase
9+
{
10+
public function setUp(): void
11+
{
12+
parent::setUp();
13+
14+
// Reset the cached instance so each test starts fresh
15+
\System\Behaviors\SettingsModel::clearInternalCache();
16+
}
17+
18+
public function tearDown(): void
19+
{
20+
// Clean up the settings record
21+
EditorSetting::instance()->resetDefault();
22+
\System\Behaviors\SettingsModel::clearInternalCache();
23+
24+
parent::tearDown();
25+
}
26+
27+
/**
28+
* Test that renderCss output does not contain script tags even when
29+
* malicious CSS using LESS escape syntax is stored in the database.
30+
*/
31+
public function testRenderCssStripsScriptTags()
32+
{
33+
$maliciousStyles = '.x { content: ~"</style><script>alert(1)</script><style>"; }';
34+
35+
EditorSetting::set('html_custom_styles', $maliciousStyles);
36+
37+
\System\Behaviors\SettingsModel::clearInternalCache();
38+
\Illuminate\Support\Facades\Cache::forget(EditorSetting::instance()->cacheKey);
39+
40+
$renderedCss = EditorSetting::renderCss();
41+
42+
$this->assertStringNotContainsString('<script>', $renderedCss);
43+
$this->assertStringNotContainsString('</script>', $renderedCss);
44+
$this->assertStringNotContainsString('</style>', $renderedCss);
45+
}
46+
47+
/**
48+
* Test that normal CSS content is preserved through renderCss.
49+
*/
50+
public function testRenderCssPreservesNormalCss()
51+
{
52+
$normalStyles = '.my-class { color: blue; font-weight: bold; }';
53+
54+
EditorSetting::set('html_custom_styles', $normalStyles);
55+
56+
\System\Behaviors\SettingsModel::clearInternalCache();
57+
\Illuminate\Support\Facades\Cache::forget(EditorSetting::instance()->cacheKey);
58+
59+
$renderedCss = EditorSetting::renderCss();
60+
61+
$this->assertStringContainsString('color', $renderedCss);
62+
$this->assertStringContainsString('font-weight', $renderedCss);
63+
$this->assertDoesNotMatchRegularExpression('/<[a-z\/!]/', $renderedCss);
64+
}
65+
}

0 commit comments

Comments
 (0)