CVE-2022-4495: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') (CWE-79)
value: A vulnerability, which was classified as problematic, has been found in collective.dms.basecontent up to 1.6. This issue affects the function renderCell of the file src/collective/dms/basecontent/browser/column.py. The manipulation leads to cross site scripting. The attack may be initiated remotely. Upgrading to version 1.7 is able to address this issue. The name of the patch is 6c4d616fcc771822a14ebae5e23f3f6d96d134bd. It is recommended to upgrade the affected component. The identifier VDB-215813 was 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-14T15:15Z
Repository: https://github.com/collective/collective.dms.basecontent
Language: Python
Files changed: CHANGES.rst, setup.py, src/collective/dms/basecontent/browser/column.py
Commit date: 2022-06-09 11:04:34 +0200
Commit message: Updated columns to work with z3c.table 2.2. Escaped rendering special caracters to avoid xss

Diff: diff --git a/CHANGES.rst b/CHANGES.rst
index f68aa70..3a16057 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -2,12 +2,12 @@ Changelog
 =========
 
 1.7 (unreleased)
 ----------------
 
-- Nothing changed yet.
-
+- Updated columns to work with z3c.table 2.2. Escaped rendering special caracters to avoid xss.
+  [sgeulette]
 
 1.6 (2021-04-20)
 ----------------
 
 - RelatedDocs field can handle object_provides criteria.
diff --git a/setup.py b/setup.py
index ad6275e..cb7824d 100644
--- a/setup.py
+++ b/setup.py
@@ -47,21 +47,22 @@ setup(
     install_requires=[
         'setuptools',
         'five.grok',
         'collective.documentviewer',
         'dexterity.localrolesfield',
+        'future',
         'plone.api',
         'plone.app.dexterity',
         'plone.directives.form',
         'plone.namedfile',
         'z3c.blobfile',
         'plone.app.contenttypes',
         'plone.app.relationfield',
         'plone.formwidget.contenttree',
         'plone.principalsource',
         'collective.z3cform.chosen',
-        'z3c.table',
+        'z3c.table>=2.2',
     ],
     extras_require={
         'test': ['plone.app.testing',
                  'ecreall.helpers.testing',
                  'plone.app.vocabularies'
diff --git a/src/collective/dms/basecontent/browser/column.py b/src/collective/dms/basecontent/browser/column.py
index 5f93e6c..7d13c9c 100644
--- a/src/collective/dms/basecontent/browser/column.py
+++ b/src/collective/dms/basecontent/browser/column.py
@@ -1,21 +1,24 @@
-import os.path
-import Missing
-from Acquisition import aq_base
 from AccessControl import getSecurityManager
-from Products.CMFCore.utils import getToolByName
+from Acquisition import aq_base
+from collective.dms.basecontent import _
 from five import grok
+from html import escape
+from Products.CMFCore.utils import getToolByName
+from Products.CMFCore.WorkflowCore import WorkflowException
+from Products.CMFPlone.utils import safe_unicode
 from z3c.table import interfaces
 from zope.component import getMultiAdapter
-from zope.i18nmessageid import MessageFactory
 from zope.i18n import translate
-import z3c.table.table
-import z3c.table.column
-from Products.CMFCore.WorkflowCore import WorkflowException
+from zope.i18nmessageid import MessageFactory
+
+import Missing
+import os.path
 import plone.api
+import z3c.table.column
+import z3c.table.table
 
-from collective.dms.basecontent import _
 
 PMF = MessageFactory('plone')
 
 grok.templatedir('templates')
 
@@ -72,15 +75,15 @@ class PrincipalColumn(Column):
         mtool = getToolByName(plone.api.portal.get(), 'portal_membership')
         principals = []
         for principal_id in value:
             user = mtool.getMemberById(principal_id)
             if user is not None:
-                principals.append(user.getProperty('fullname', None) or user.getId())
+                principals.append(escape(user.getProperty('fullname', None)) or user.getId())
             else:
                 group = gtool.getGroupById(principal_id)
                 if group is not None:
-                    principals.append(group.getProperty('title', None) or group.getId())
+                    principals.append(escape(group.getProperty('title', None)) or group.getId())
 
         return ', '.join(principals).decode('utf-8')
 
 
 class LinkColumn(z3c.table.column.LinkColumn, Column):
@@ -90,29 +93,36 @@ class LinkColumn(z3c.table.column.LinkColumn, Column):
         """Setup link url."""
         if self.linkName is not None:
             return '%s/%s' % (item.getURL(), self.linkName)
         return item.getURL()
 
+    def renderCell(self, item):
+        # setup a tag
+        return '<a href="%s"%s%s%s>%s</a>' % (
+            self.getLinkURL(item),  # originally escaped
+            self.getLinkTarget(item),
+            self.getLinkCSS(item),
+            self.getLinkTitle(item),
+            self.getLinkContent(item),  # originally escaped
+        )
+
 
 class TitleColumn(LinkColumn):
     grok.baseclass()
     header = PMF("Title")
     weight = 10
 
     def getLinkContent(self, item):
         title = get_value(item, 'Title')
-        if isinstance(title, unicode):
-            return title
-        else:
-            return unicode(title, 'utf-8', 'ignore')
+        return escape(safe_unicode(title))
 
 
 class IconColumn(LinkColumn):
     grok.baseclass()
 
     def getLinkContent(self, item):
-        content = super(IconColumn, self).getLinkContent(item)
+        content = super(IconColumn, self).getLinkContent(item)  # escaped
         return u"""<img title="%s" src="%s" />""" % (
             content,
             '%s/%s' % (self.table.portal_url, self.iconName))
 
 

Vulnerable code: - Nothing changed yet.

        'z3c.table',
import os.path
import Missing
from Acquisition import aq_base
from Products.CMFCore.utils import getToolByName
from zope.i18nmessageid import MessageFactory
import z3c.table.table
import z3c.table.column
from Products.CMFCore.WorkflowCore import WorkflowException
from collective.dms.basecontent import _
                principals.append(user.getProperty('fullname', None) or user.getId())
                    principals.append(group.getProperty('title', None) or group.getId())
        if isinstance(title, unicode):
            return title
        else:
            return unicode(title, 'utf-8', 'ignore')
        content = super(IconColumn, self).getLinkContent(item)
Fixed code: - Updated columns to work with z3c.table 2.2. Escaped rendering special caracters to avoid xss.
  [sgeulette]
        'future',
        'z3c.table>=2.2',
from Acquisition import aq_base
from collective.dms.basecontent import _
from html import escape
from Products.CMFCore.utils import getToolByName
from Products.CMFCore.WorkflowCore import WorkflowException
from Products.CMFPlone.utils import safe_unicode
from zope.i18nmessageid import MessageFactory

import Missing
import os.path
import z3c.table.column
import z3c.table.table
                principals.append(escape(user.getProperty('fullname', None)) or user.getId())
                    principals.append(escape(group.getProperty('title', None)) or group.getId())
    def renderCell(self, item):
        # setup a tag
        return '<a href="%s"%s%s%s>%s</a>' % (
            self.getLinkURL(item),  # originally escaped
            self.getLinkTarget(item),
            self.getLinkCSS(item),
            self.getLinkTitle(item),
            self.getLinkContent(item),  # originally escaped
        )

        return escape(safe_unicode(title))
        content = super(IconColumn, self).getLinkContent(item)  # escaped