Skip to content

Commit e97e373

Browse files
committed
✅ [open-formulieren/security-issues#35] Testing svg sanitizer
1 parent d421984 commit e97e373

File tree

3 files changed

+190
-1
lines changed

3 files changed

+190
-1
lines changed

src/openforms/config/tests/test_global_configuration.py

+51-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import clamd
1313
from django_webtest import WebTest
1414
from maykin_2fa.test import disable_admin_mfa
15-
from webtest import Form as WebTestForm
15+
from webtest import Form as WebTestForm, Upload
1616

1717
from openforms.accounts.tests.factories import SuperUserFactory
1818
from openforms.tests.utils import NOOP_CACHES
@@ -165,6 +165,56 @@ def test_virus_scan_enabled_cant_connect(self):
165165
"Cannot connect to ClamAV: Cannot connect!",
166166
)
167167

168+
@override_settings(LANGUAGE_CODE="en")
169+
def test_svg_sanitation_favicon(self):
170+
bad_svg_content = """
171+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50">
172+
<circle cx="25" cy="25" r="25" fill="green" onclick="alert(\'forget about me?\')" />
173+
<script>//<![CDATA[
174+
alert("I am malicious >:)")
175+
//]]></script>
176+
<g>
177+
<rect class="btn" x="0" y="0" width="10" height="10" fill="red" onload="alert(\'click!\')" />
178+
</g>
179+
</svg>
180+
"""
181+
sanitized_svg_content = b"""
182+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50">
183+
<circle cx="25" cy="25" r="25" fill="green"></circle>
184+
//
185+
alert("I am malicious &gt;:)")
186+
//
187+
<g>
188+
<rect x="0" y="0" width="10" height="10" fill="red"></rect>
189+
</g>
190+
</svg>
191+
"""
192+
193+
config = GlobalConfiguration.get_solo()
194+
195+
with self.subTest(part="admin config"):
196+
url = reverse("admin:config_globalconfiguration_change", args=(1,))
197+
change_page = self.app.get(url)
198+
199+
upload = Upload(
200+
"bad_svg.svg", bad_svg_content.encode("utf-8"), "image/svg+xml"
201+
)
202+
203+
form = change_page.forms["globalconfiguration_form"]
204+
form["favicon"] = upload
205+
_ensure_arrayfields(form, config=config)
206+
response = form.submit()
207+
208+
self.assertEqual(response.status_code, 302)
209+
config.refresh_from_db()
210+
self.assertEqual(config.favicon, "logo/bad_svg.svg")
211+
212+
with self.subTest(part="assert favicon sanitized"):
213+
with config.favicon.file.open("r") as favicon_file:
214+
# Assert that the logo is completely sanitized
215+
decoded_logo = favicon_file.read()
216+
self.assertEqual(decoded_logo, sanitized_svg_content)
217+
168218

169219
class GlobalConfirmationEmailTests(TestCase):
170220
def setUp(self):

src/openforms/config/tests/test_theme.py

+48
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,54 @@ def test_upload_svg(self):
7777
style_tag.text(),
7878
)
7979

80+
def test_upload_malicious_svg(self):
81+
bad_svg_content = """
82+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50">
83+
<circle cx="25" cy="25" r="25" fill="green" onclick="alert('forget about me?')" />
84+
<script>//<![CDATA[
85+
alert("I am malicious >:)")
86+
//]]></script>
87+
<g>
88+
<rect class="btn" x="0" y="0" width="10" height="10" fill="red" onload="alert('click!')" />
89+
</g>
90+
</svg>
91+
"""
92+
sanitized_svg_content = b"""
93+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50">
94+
<circle cx="25" cy="25" r="25" fill="green"></circle>
95+
//
96+
alert("I am malicious &gt;:)")
97+
//
98+
<g>
99+
<rect x="0" y="0" width="10" height="10" fill="red"></rect>
100+
</g>
101+
</svg>
102+
"""
103+
theme = ThemeFactory.create()
104+
105+
with self.subTest(part="admin config"):
106+
url = reverse("admin:config_theme_change", args=(theme.pk,))
107+
108+
change_page = self.app.get(url)
109+
110+
upload = Upload(
111+
"bad_svg.svg", bad_svg_content.encode("utf-8"), "image/svg+xml"
112+
)
113+
114+
form = change_page.forms["theme_form"]
115+
form["logo"] = upload
116+
response = form.submit()
117+
118+
self.assertEqual(response.status_code, 302)
119+
theme.refresh_from_db()
120+
self.assertEqual(theme.logo, "logo/bad_svg.svg")
121+
122+
with self.subTest(part="assert logo sanitized"):
123+
with theme.logo.file.open("r") as logo_file:
124+
# Assert that the logo is completely sanitized
125+
decoded_logo = logo_file.read()
126+
self.assertEqual(decoded_logo, sanitized_svg_content)
127+
80128
def test_upload_png(self):
81129
logo = Path(settings.DJANGO_PROJECT_DIR) / "static" / "img" / "digid.png"
82130
theme = ThemeFactory.create()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import io
2+
3+
from django.test import SimpleTestCase
4+
5+
from ..sanitizer import sanitize_svg_content, sanitize_svg_file
6+
7+
8+
class SanitizeSvgFileTests(SimpleTestCase):
9+
def test_sanitize_svg_content_removes_script_tags(self):
10+
bad_svg_content = """
11+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50">
12+
<circle cx="25" cy="25" r="25" fill="green" />
13+
<script>//<![CDATA[
14+
alert("I am malicious >:)")
15+
//]]></script>
16+
<g>
17+
<rect class="btn" x="0" y="0" width="10" height="10" fill="red" />
18+
</g>
19+
</svg>
20+
"""
21+
sanitized_svg_content = b"""
22+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50">
23+
<circle cx="25" cy="25" r="25" fill="green"></circle>
24+
//
25+
alert("I am malicious &gt;:)")
26+
//
27+
<g>
28+
<rect x="0" y="0" width="10" height="10" fill="red"></rect>
29+
</g>
30+
</svg>
31+
"""
32+
33+
temp_file = io.BytesIO(bad_svg_content.encode("utf-8"))
34+
35+
# Assert that sanitize_svg_content removed the script tag
36+
sanitized_svg_file = sanitize_svg_file(temp_file)
37+
self.assertEqual(
38+
sanitized_svg_file.read(), sanitized_svg_content
39+
)
40+
41+
42+
class SanitizeSvgContentTests(SimpleTestCase):
43+
def test_sanitize_svg_content_removes_script_tags(self):
44+
bad_svg_content = """
45+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50">
46+
<circle cx="25" cy="25" r="25" fill="green" />
47+
<script>//<![CDATA[
48+
alert("I am malicious >:)")
49+
//]]></script>
50+
<g>
51+
<rect class="btn" x="0" y="0" width="10" height="10" fill="red" />
52+
</g>
53+
</svg>
54+
"""
55+
sanitized_svg_content = """
56+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50">
57+
<circle cx="25" cy="25" r="25" fill="green"></circle>
58+
//
59+
alert("I am malicious &gt;:)")
60+
//
61+
<g>
62+
<rect x="0" y="0" width="10" height="10" fill="red"></rect>
63+
</g>
64+
</svg>
65+
"""
66+
67+
# Assert that sanitize_svg_content removed the script tag
68+
sanitized_bad_svg_content = sanitize_svg_content(bad_svg_content)
69+
self.assertEqual(sanitized_svg_content, sanitized_bad_svg_content)
70+
71+
def test_sanitize_svg_content_removes_event_handlers(self):
72+
bad_svg_content = """
73+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50">
74+
<circle cx="25" cy="25" r="25" fill="green" onclick="alert('forget about me?')" />
75+
<g>
76+
<rect class="btn" x="0" y="0" width="10" height="10" fill="red" onload="alert('click!')" />
77+
</g>
78+
</svg>
79+
"""
80+
sanitized_svg_content = """
81+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50">
82+
<circle cx="25" cy="25" r="25" fill="green"></circle>
83+
<g>
84+
<rect x="0" y="0" width="10" height="10" fill="red"></rect>
85+
</g>
86+
</svg>
87+
"""
88+
89+
# Assert that sanitize_svg_content removed the event handlers
90+
sanitized_bad_svg_content = sanitize_svg_content(bad_svg_content)
91+
self.assertEqual(sanitized_svg_content, sanitized_bad_svg_content)

0 commit comments

Comments
 (0)