CVE-2022-4526: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: A vulnerability was found in django-photologue up to 3.15.1 and classified as problematic. Affected by this issue is some unknown functionality of the file photologue/templates/photologue/photo_detail.html of the component Default Template Handler. The manipulation of the argument object.caption leads to cross site scripting. The attack may be launched remotely. Upgrading to version 3.16 is able to address this issue. The name of the patch is 960cb060ce5e2964e6d716ff787c72fc18a371e7. It is recommended to apply a patch to fix this issue. VDB-215906 is the identifier assigned to this vulnerability.
CWE description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
CVSS v3 base score: 6.1
Published: 2022-12-15T21:15Z
Repository: https://github.com/richardbarran/django-photologue
Language: Python
Files changed: CHANGELOG.txt, docs/pages/usage.rst, photologue/templates/photologue/photo_detail.html, photologue/tests/test_views_photo.py
Commit date: 2022-08-29 22:23:13 +0200
Commit message: Prevent XSS injection with default template.

Diff: diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index a7b6a1b..a32348d 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -3,10 +3,11 @@ Changelog
 
 3.16 (unreleased)
 -----------------
 
 - Split out zip upload functionality into a separate function (#222) (contributed by lausek).
+- Do not allow JS injection into the Photo caption field (#223) (bug detected by Domiee13).
 
 
 3.15.1 (2022-02-23)
 -------------------
 
diff --git a/docs/pages/usage.rst b/docs/pages/usage.rst
index b21612a..508539f 100644
--- a/docs/pages/usage.rst
+++ b/docs/pages/usage.rst
@@ -41,5 +41,16 @@ These can be used in a custom template to display a thumbnail, e.g.::
     </a>
 
 This will display an image, sized to the dimensions specified in the Photosize ``display``,
 and provide a clickable link to the raw image. Please refer to the example templates for ideas on how to use
 ``Photo`` and ``Gallery`` instances!
+
+Data integrity
+--------------
+Photologue will store 'as-is' any data stored for galleries and photos.
+You may want to enforce some data integrity rules - e.g. to sanitise
+any javascript injected into a ``Photo`` ``caption`` field. An easy way to do this
+would be to add extra processing on a ``post-save`` signal.
+
+Photologue does not sanitise data itself as you may legitimately want to store html and
+javascript in a caption field e.g. if you use a rich-text editor.
+
diff --git a/photologue/templates/photologue/photo_detail.html b/photologue/templates/photologue/photo_detail.html
index a93b507..21240fc 100644
--- a/photologue/templates/photologue/photo_detail.html
+++ b/photologue/templates/photologue/photo_detail.html
@@ -12,11 +12,11 @@
     </div>
 </div>
 
 <div class="row">
     <div class="col-md-6">
-        {% if object.caption %}<p>{{ object.caption|safe }}</p>{% endif %}
+        {% if object.caption %}<p>{{ object.caption }}</p>{% endif %}
         <a href="{{ object.image.url }}">
             <img src="{{ object.get_display_url }}" class="img-thumbnail" alt="{{ object.title }}">
         </a>
     </div>
     <div class="col-md-6">
diff --git a/photologue/tests/test_views_photo.py b/photologue/tests/test_views_photo.py
index 51f6db2..fb3ff12 100644
--- a/photologue/tests/test_views_photo.py
+++ b/photologue/tests/test_views_photo.py
@@ -52,5 +52,15 @@ class RequestPhotoTest(TestCase):
         self.assertEqual(response.status_code, 200)
 
     def test_detail_photo_works(self):
         response = self.client.get('/ptests/photo/2011/12/23/fake-photo/')
         self.assertEqual(response.status_code, 200)
+    def test_detail_photo_xss(self):
+        """Check that the default templates handle XSS."""
+        self.photo.title = '<img src=x onerror=alert("title")>'
+        self.photo.caption = '<img src=x onerror=alert(origin)>'
+        self.photo.save()
+        response = self.client.get('/ptests/photo/2011/12/23/fake-photo/')
+        self.assertContains(response, 'Photologue Demo - &lt;img src=x onerror=alert(&quot;title&quot;)&gt;')
+        self.assertNotContains(response, '<img src=x onerror=alert("title")>')
+        self.assertContains(response, '&lt;img src=x onerror=alert(origin)&gt;')
+        self.assertNotContains(response, '<img src=x onerror=alert(origin)>')

Vulnerable code:         {% if object.caption %}<p>{{ object.caption|safe }}</p>{% endif %}
Fixed code: - Do not allow JS injection into the Photo caption field (#223) (bug detected by Domiee13).

Data integrity
--------------
Photologue will store 'as-is' any data stored for galleries and photos.
You may want to enforce some data integrity rules - e.g. to sanitise
any javascript injected into a ``Photo`` ``caption`` field. An easy way to do this
would be to add extra processing on a ``post-save`` signal.

Photologue does not sanitise data itself as you may legitimately want to store html and
javascript in a caption field e.g. if you use a rich-text editor.

        {% if object.caption %}<p>{{ object.caption }}</p>{% endif %}
    def test_detail_photo_xss(self):
        """Check that the default templates handle XSS."""
        self.photo.title = '<img src=x onerror=alert("title")>'
        self.photo.caption = '<img src=x onerror=alert(origin)>'
        self.photo.save()
        response = self.client.get('/ptests/photo/2011/12/23/fake-photo/')
        self.assertContains(response, 'Photologue Demo - &lt;img src=x onerror=alert(&quot;title&quot;)&gt;')
        self.assertNotContains(response, '<img src=x onerror=alert("title")>')
        self.assertContains(response, '&lt;img src=x onerror=alert(origin)&gt;')
        self.assertNotContains(response, '<img src=x onerror=alert(origin)>')