VYPR
Moderate severityNVD Advisory· Published Aug 21, 2024· Updated Aug 22, 2024

CKAN has a Cross-site Scripting vector in the Datatables view plugin

CVE-2024-41675

Description

CKAN is an open-source data management system for powering data hubs and data portals. The Datatables view plugin did not properly escape record data coming from the DataStore, leading to a potential XSS vector. Sites running CKAN >= 2.7.0 with the datatables_view plugin activated. This is a plugin included in CKAN core, that not activated by default but it is widely used to preview tabular data. This vulnerability has been fixed in CKAN 2.10.5 and 2.11.0.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
ckanPyPI
>= 2.7.0, < 2.10.52.10.5

Affected products

1

Patches

2
9e89ce8220ab

Escape data in datatables view

https://github.com/ckan/ckanamercaderAug 19, 2024via ghsa
2 files changed · +55 2
  • ckanext/datatablesview/blueprint.py+3 2 modified
    @@ -3,6 +3,7 @@
     
     from typing import Any
     from urllib.parse import urlencode
    +from html import escape
     
     from flask import Blueprint
     
    @@ -124,8 +125,8 @@ def ajax(resource_view_id: str):
             data = []
             null_label = h.datatablesview_null_label()
             for row in response[u'records']:
    -            record = {colname: str(null_label if row.get(colname, u'')
    -                                   is None else row.get(colname, u''))
    +            record = {colname: escape(str(null_label if row.get(colname, u'')
    +                                          is None else row.get(colname, u'')))
                           for colname in cols}
                 # the DT_RowId is used in DT to set an element id for each record
                 record['DT_RowId'] = 'row' + str(row.get(u'_id', u''))
    
  • ckanext/datatablesview/tests/test_ajax.py+52 0 added
    @@ -0,0 +1,52 @@
    +# encoding: utf-8
    +
    +import json
    +import pytest
    +
    +from ckan.tests import factories, helpers
    +from ckan.lib.helpers import url_for
    +
    +
    +@pytest.mark.ckan_config("ckan.plugins", "datastore datatables_view")
    +@pytest.mark.usefixtures("with_plugins")
    +def test_ajax_data(app, user):
    +    dataset = factories.Dataset()
    +    ds = helpers.call_action(
    +        'datastore_create',
    +        resource={'package_id': dataset['id']},
    +        fields=[{'id': 'a', 'type': 'text'}, {'id': 'b', 'type': 'int'}],
    +        records=[
    +            {'a': 'one', 'b': 1},
    +            {'a': 'two', 'b': 2},
    +            {'a': 'a < b && a > 0', 'b': None}
    +        ],
    +    )
    +    view = factories.ResourceView(
    +        view_type='datatables_view',
    +        resource_id=ds['resource_id']
    +    )
    +    resp = app.post(
    +        url=url_for('datatablesview.ajax', resource_view_id=view["id"]),
    +        data={
    +            'draw': 1,
    +            'search[value]': '',
    +            'start': 0,
    +            'length': 50,
    +        },
    +    )
    +    ajax = json.loads(b''.join(resp.response).decode('utf-8'))
    +    assert ajax == {
    +        'draw': 1,
    +        'recordsFiltered': 3,
    +        'recordsTotal': 3,
    +        'data': [
    +            {'_id': '1', 'a': 'one', 'b': '1', 'DT_RowId': 'row1'},
    +            {'_id': '2', 'a': 'two', 'b': '2', 'DT_RowId': 'row2'},
    +            {
    +                '_id': '3',
    +                'a': 'a &lt; b &amp;&amp; a &gt; 0',
    +                'b': '',
    +                'DT_RowId': 'row3',
    +            },
    +        ]
    +    }
    
d7dfe8c427b1

Escape data in datatables view

https://github.com/ckan/ckanamercaderAug 19, 2024via ghsa
2 files changed · +55 2
  • ckanext/datatablesview/blueprint.py+3 2 modified
    @@ -3,6 +3,7 @@
     
     from typing import Any
     from urllib.parse import urlencode
    +from html import escape
     
     from flask import Blueprint
     
    @@ -124,8 +125,8 @@ def ajax(resource_view_id: str):
             data = []
             null_label = h.datatablesview_null_label()
             for row in response[u'records']:
    -            record = {colname: str(null_label if row.get(colname, u'')
    -                                   is None else row.get(colname, u''))
    +            record = {colname: escape(str(null_label if row.get(colname, u'')
    +                                          is None else row.get(colname, u'')))
                           for colname in cols}
                 # the DT_RowId is used in DT to set an element id for each record
                 record['DT_RowId'] = 'row' + str(row.get(u'_id', u''))
    
  • ckanext/datatablesview/tests/test_ajax.py+52 0 added
    @@ -0,0 +1,52 @@
    +# encoding: utf-8
    +
    +import json
    +import pytest
    +
    +from ckan.tests import factories, helpers
    +from ckan.lib.helpers import url_for
    +
    +
    +@pytest.mark.ckan_config("ckan.plugins", "datastore datatables_view")
    +@pytest.mark.usefixtures("with_plugins")
    +def test_ajax_data(app, user):
    +    dataset = factories.Dataset()
    +    ds = helpers.call_action(
    +        'datastore_create',
    +        resource={'package_id': dataset['id']},
    +        fields=[{'id': 'a', 'type': 'text'}, {'id': 'b', 'type': 'int'}],
    +        records=[
    +            {'a': 'one', 'b': 1},
    +            {'a': 'two', 'b': 2},
    +            {'a': 'a < b && a > 0', 'b': None}
    +        ],
    +    )
    +    view = factories.ResourceView(
    +        view_type='datatables_view',
    +        resource_id=ds['resource_id']
    +    )
    +    resp = app.post(
    +        url=url_for('datatablesview.ajax', resource_view_id=view["id"]),
    +        data={
    +            'draw': 1,
    +            'search[value]': '',
    +            'start': 0,
    +            'length': 50,
    +        },
    +    )
    +    ajax = json.loads(b''.join(resp.response).decode('utf-8'))
    +    assert ajax == {
    +        'draw': 1,
    +        'recordsFiltered': 3,
    +        'recordsTotal': 3,
    +        'data': [
    +            {'_id': '1', 'a': 'one', 'b': '1', 'DT_RowId': 'row1'},
    +            {'_id': '2', 'a': 'two', 'b': '2', 'DT_RowId': 'row2'},
    +            {
    +                '_id': '3',
    +                'a': 'a &lt; b &amp;&amp; a &gt; 0',
    +                'b': '',
    +                'DT_RowId': 'row3',
    +            },
    +        ]
    +    }
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.