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.
| Package | Affected versions | Patched versions |
|---|---|---|
ckanPyPI | >= 2.7.0, < 2.10.5 | 2.10.5 |
Affected products
1Patches
22 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 < b && a > 0', + 'b': '', + 'DT_RowId': 'row3', + }, + ] + }
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 < b && a > 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- github.com/advisories/GHSA-r3jc-vhf4-6v32ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-41675ghsaADVISORY
- github.com/ckan/ckan/commit/9e89ce8220ab1445e0bd85a67994a51d9d3d2688ghsax_refsource_MISCWEB
- github.com/ckan/ckan/commit/d7dfe8c427b1c63c75d788a609f3b7d7620a25a1ghsax_refsource_MISCWEB
- github.com/ckan/ckan/security/advisories/GHSA-r3jc-vhf4-6v32ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.