CVE-2025-24372
Description
CKAN is an open-source DMS (data management system) for powering data hubs and data portals. Using a specially crafted file, a user could potentially upload a file containing code that when executed could send arbitrary requests to the server. If that file was opened by an administrator, it could lead to escalation of privileges of the original submitter or other malicious actions. Users must have been registered to the site to exploit this vulnerability. This vulnerability has been fixed in CKAN 2.10.7 and 2.11.2. Users are advised to upgrade. On versions prior to CKAN 2.10.7 and 2.11.2, site maintainers can restrict the file types supported for uploading using the ckan.upload.user.mimetypes / ckan.upload.user.types and ckan.upload.group.mimetypes / ckan.upload.group.types config options. To entirely disable file uploads users can use: ckan.upload.user.types = none
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
ckanPyPI | < 2.10.7 | 2.10.7 |
ckanPyPI | >= 2.11.0, < 2.11.2 | 2.11.2 |
Patches
49f2094b267215bd556eb4370a4fc5e06634eUpdate changelog with 2.10.7 and 2.11.2 entries
1 file changed · +70 −0
CHANGELOG.rst+70 −0 modified@@ -22,6 +22,41 @@ Migration notes the string ``*`` as value in both options (this is dangerous and **not** recommended). +v.2.11.2 2025-02-05 +=================== + +Migration notes +--------------- + +* Going forward, if both ``ckan.upload.[type].mimetypes`` and + ``ckan.upload.[type].types`` are empty, no uploads will be allowed + for this object type (e.g. ``user`` or ``group``). It previoulsy + meant that all file types were allowed. To keep the old behaviour use + the string ``*`` as value in both options (this is dangerous and + **not** recommended). + +Minor changes +------------- + +- Adapt login failure message if reCAPTCHA is enabled (`#8627 + <https://github.com/ckan/ckan/pull/8627>`_) +- Update release process docs (`#8586 + <https://github.com/ckan/ckan/pull/8586>`_) + + +Bugfixes +-------- + +- `CVE-2025-24372 <https://github.com/ckan/ckan/security/advisories/GHSA-7pq5-qcp6-mcww>`_: Fix potential + XSS vector through user and group/organization images. +- Invalidate cached pages and load fresh ones if cookies change (`#6955 + <https://github.com/ckan/ckan/pull/6955>`_) +- Fix `check_access` order for resource create view (`#8588 + <https://github.com/ckan/ckan/pull/8588>`_) +- Fix CSV export error by ensuring BOM is written correctly as a string for + Excel compatibility. (`#8635 <https://github.com/ckan/ckan/pull/8635>`_) +- Fix auth check for datastore data dictionary view (`#8639 + <https://github.com/ckan/ckan/pull/8639>`_) v.2.11.1 2024-12-11 @@ -690,6 +725,41 @@ Removals and deprecations class SecondPlugin(p.SingletonPlugin, BasePlutin): p.implements(IAnything) + + +v.2.10.7 2025-02-05 +=================== + +Migration notes +--------------- + +* Going forward, if both ``ckan.upload.[type].mimetypes`` and + ``ckan.upload.[type].types`` are empty, no uploads will be allowed + for this object type (e.g. ``user`` or ``group``). It previoulsy + meant that all file types were allowed. To keep the old behaviour use + the string ``*`` as value in both options (this is dangerous and + **not** recommended). + +Minor changes +------------- +- Adapt login failure message if reCAPTCHA is enabled (`#8627 + <https://github.com/ckan/ckan/pull/8627>`_) +- Update release process docs (`#8586 + <https://github.com/ckan/ckan/pull/8586>`_) +- Support 2.11 version of the Solr schema in CKAN 2.10 (``5acfeda6e``) + + +Bugfixes +-------- +- `CVE-2025-24372 <https://github.com/ckan/ckan/security/advisories/GHSA-7pq5-qcp6-mcww>`_: Fix potential + XSS vector through user and group/organization images. +- Invalidate cached pages and load fresh ones if cookies change (`#6955 + <https://github.com/ckan/ckan/pull/6955>`_) +- Fix `check_access` order for resource create view (`#8588 + <https://github.com/ckan/ckan/pull/8588>`_) +- Fix auth check for datastore data dictionary view (`#8639 + <https://github.com/ckan/ckan/pull/8639>`_) + v.2.10.6 2024-12-11 ===================
7da6a26c6183Verify user,group/org images upload type, enforce extension
6 files changed · +146 −24
CHANGELOG.rst+15 −0 modified@@ -9,6 +9,21 @@ Changelog .. towncrier release notes start +v.2.12.0 (Not yet released) +=========================== + +Migration notes +--------------- + +* Going forward, if both ``ckan.upload.[type].mimetypes`` and + ``ckan.upload.[type].types`` are empty, no uploads will be allowed + for this object type (e.g. ``user`` or ``group``). It previoulsy + meant that all file types were allowed. To keep the old behaviour use + the string ``*`` as value in both options (this is dangerous and + **not** recommended). + + + v.2.11.1 2024-12-11 ===================
ckan/config/config_declaration.yaml+17 −6 modified@@ -1328,25 +1328,36 @@ groups: type: list default: image example: image text - description: File types allowed to upload as user's avatar. No restrictions applied when empty + description: File types allowed to upload as user's avatar. If empty and :ref:`ckan.upload.user.mimetypes` + is also empty, no uploads are allowed. To allow any kind of file upload, use the ``*`` string in both + options (this is dangerous and **not** recommended). Note also that ``text/svg`` can contain embedded + javascript code so it only should be used in trusted environments. - key: ckan.upload.user.mimetypes type: list default: image/png image/gif image/jpeg - example: image/png text/svg - description: File MIMETypes allowed to upload as user's avatar. No restrictions applied when empty + example: image/png + description: File MIMETypes allowed to upload as user's avatar. If empty and :ref:`ckan.upload.user.types` + is also empty, no uploads are allowed. To allow any kind of file upload, use the ``*`` string in both + options (this is dangerous and **not** recommended). - key: ckan.upload.group.types type: list default: image example: image text - description: File types allowed to upload as group image. No restrictions applied when empty + description: File types allowed to upload as group or organization image. If empty + and :ref:`ckan.upload.group.mimetypes` is also empty, no uploads are allowed. To allow any kind of + file upload, use the ``*`` string in both options (this is dangerous and **not** recommended). - key: ckan.upload.group.mimetypes type: list default: image/png image/gif image/jpeg - example: image/png text/svg - description: File MIMETypes allowed to upload as group image. No restrictions applied when empty + example: image/png + description: File MIMEtypes allowed to upload as group or organization image. If empty + and :ref:`ckan.upload.group.types` is also empty, no uploads are allowed. To allow any kind of + file upload, use the ``*`` string in both options (this is dangerous and **not** recommended). + Note also that ``text/svg`` can contain embedded javascript code so it only should be used in + trusted environments. - annotation: Webassets Settings options:
ckan/lib/uploader.py+54 −13 modified@@ -7,6 +7,7 @@ import logging import magic import mimetypes +from pathlib import Path from typing import Any, IO, Optional, Union from urllib.parse import urlparse @@ -159,10 +160,14 @@ def update_data_dict(self, data_dict: dict[str, Any], url_field: str, self.filename = str(datetime.datetime.utcnow()) + self.filename self.filename = munge.munge_filename_legacy(self.filename) self.filepath = os.path.join(self.storage_path, self.filename) - data_dict[url_field] = self.filename self.upload_file = _get_underlying_file( self.upload_field_storage) self.tmp_filepath = self.filepath + '~' + + self.verify_type() + + data_dict[url_field] = self.filename + # keep the file if there has been no change elif self.old_filename and not self.old_filename.startswith('http'): if not self.clear: @@ -177,7 +182,6 @@ def upload(self, max_size: int = 2) -> None: anything unless the request is actually good. max_size is size in MB maximum of the file''' - self.verify_type() if self.filename: assert self.upload_file and self.filepath @@ -202,29 +206,66 @@ def upload(self, max_size: int = 2) -> None: pass def verify_type(self): - if not self.filename or not self.upload_file: + + if not self.upload_file: return - mimetypes = config.get( + allowed_mimetypes = config.get( f"ckan.upload.{self.object_type}.mimetypes") - types = config.get(f"ckan.upload.{self.object_type}.types") - if not mimetypes and not types: - return + allowed_types = config.get(f"ckan.upload.{self.object_type}.types") + if not allowed_mimetypes and not allowed_types: + raise logic.ValidationError( + { + self.file_field: [f"No uploads allowed for object type {self.object_type}"] + } + ) + + # Check that the declared types in the request are supported + declared_mimetype_from_filename = mimetypes.guess_type( + self.upload_field_storage.filename + )[0] + declared_content_type = self.upload_field_storage.content_type + for declared_mimetype in ( + declared_mimetype_from_filename, + declared_content_type, + ): + if ( + declared_mimetype + and allowed_mimetypes + and allowed_mimetypes[0] != "*" + and declared_mimetype not in allowed_mimetypes + ): + raise logic.ValidationError( + { + self.file_field: [ + f"Unsupported upload type: {declared_mimetype}" + ] + } + ) + + # Check that the actual type guessed from the contents is supported + # (2KB required for detecting xlsx mimetype) + content = self.upload_file.read(2048) + guessed_mimetype = magic.from_buffer(content, mime=True) - # 2KB required for detecting xlsx mimetype - actual = magic.from_buffer(self.upload_file.read(2048), mime=True) self.upload_file.seek(0, os.SEEK_SET) + err: ErrorDict = { - self.file_field: [f"Unsupported upload type: {actual}"] + self.file_field: [f"Unsupported upload type: {guessed_mimetype}"] } - if mimetypes and actual not in mimetypes: + if allowed_mimetypes and allowed_mimetypes[0] != "*" and guessed_mimetype not in allowed_mimetypes: raise logic.ValidationError(err) - type_ = actual.split("/")[0] - if types and type_ not in types: + type_ = guessed_mimetype.split("/")[0] + if allowed_types and allowed_types[0] != "*" and type_ not in allowed_types: raise logic.ValidationError(err) + preferred_extension = mimetypes.guess_extension(guessed_mimetype) + if preferred_extension: + self.filename = str(Path(self.filename).with_suffix(preferred_extension)) + self.filepath = str(Path(self.filepath).with_suffix(preferred_extension)) + class ResourceUpload(object): mimetype: Optional[str]
ckan/tests/cli/test_clean.py+4 −4 modified@@ -11,8 +11,8 @@ def test_output_if_there_are_not_invalid_users(self, cli): result = cli.invoke(ckan, ["clean", "users"]) assert "No users were found with invalid images." in result.output - @pytest.mark.ckan_config("ckan.upload.user.mimetypes", "") - @pytest.mark.ckan_config("ckan.upload.user.types", "") + @pytest.mark.ckan_config("ckan.upload.user.mimetypes", "*") + @pytest.mark.ckan_config("ckan.upload.user.types", "*") def test_confirm_dialog_if_no_force( self, cli, monkeypatch, create_with_upload, faker, ckan_config ): @@ -54,8 +54,8 @@ def test_confirm_dialog_if_no_force( users = call_action("user_list") assert len(users) == 2 - @pytest.mark.ckan_config("ckan.upload.user.mimetypes", "") - @pytest.mark.ckan_config("ckan.upload.user.types", "") + @pytest.mark.ckan_config("ckan.upload.user.mimetypes", "*") + @pytest.mark.ckan_config("ckan.upload.user.types", "*") def test_correct_users_are_deleted( self, cli, monkeypatch, create_with_upload, faker, ckan_config ):
ckan/tests/lib/test_uploader.py+1 −1 modified@@ -90,7 +90,7 @@ def test_group_upload(self, monkeypatch, tmpdir, make_app, ckan_config, faker): u'upload': FileStorage( BytesIO(faker.image()), filename=u'logo.png', - content_type=u'PNG' + content_type=u'image/png' ), u'name': u'test-group-upload'} group_upload = Upload(u'group')
ckan/tests/logic/action/test_create.py+55 −0 modified@@ -2226,6 +2226,47 @@ def test_upload_non_picture_with_png_extension( logic.ValidationError, match="Unsupported upload type"): create_with_upload("hello world", "file.png", **params) + @pytest.mark.ckan_config("ckan.upload.user.mimetypes", "") + @pytest.mark.ckan_config("ckan.upload.user.types", "") + def test_uploads_not_allowed_when_empty_mimetypes_and_types( + self, create_with_upload, faker): + params = { + "name": faker.user_name(), + "email": faker.email(), + "password": "12345678", + "action": "user_create", + "upload_field_name": "image_upload", + } + with pytest.raises( + logic.ValidationError, match="No uploads allowed for object type"): + create_with_upload("hello world", "file.png", **params) + + @pytest.mark.ckan_config("ckan.upload.user.mimetypes", "*") + @pytest.mark.ckan_config("ckan.upload.user.types", "image") + def test_upload_all_types_allowed_needs_both_options(self, create_with_upload, faker): + params = { + "name": faker.user_name(), + "email": faker.email(), + "password": "12345678", + "action": "user_create", + "upload_field_name": "image_upload", + } + with pytest.raises( + logic.ValidationError, match="Unsupported upload type"): + assert create_with_upload(faker.json(), "file.json", **params) + + @pytest.mark.ckan_config("ckan.upload.user.mimetypes", "*") + @pytest.mark.ckan_config("ckan.upload.user.types", "*") + def test_upload_all_types_allowed(self, create_with_upload, faker): + params = { + "name": faker.user_name(), + "email": faker.email(), + "password": "12345678", + "action": "user_create", + "upload_field_name": "image_upload", + } + assert create_with_upload(faker.json(), "file.json", **params) + @pytest.mark.ckan_config("ckan.upload.user.types", "image") def test_upload_picture(self, create_with_upload, faker): params = { @@ -2237,6 +2278,20 @@ def test_upload_picture(self, create_with_upload, faker): } assert create_with_upload(faker.image(), "file.png", **params) + @pytest.mark.ckan_config("ckan.upload.user.types", "image") + def test_upload_picture_extension_enforced(self, create_with_upload, faker): + params = { + "name": faker.user_name(), + "email": faker.email(), + "password": "12345678", + "action": "user_create", + "upload_field_name": "image_upload", + } + user = create_with_upload(faker.image(image_format="jpeg"), "file.png", **params) + + assert user["image_url"].endswith(".jpg") + assert user["image_display_url"].endswith(".jpg") + class TestVocabularyCreate(object): @pytest.mark.usefixtures("non_clean_db")
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
9- github.com/advisories/GHSA-7pq5-qcp6-mcwwghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-24372ghsaADVISORY
- docs.ckan.org/en/latest/maintaining/configuration.htmlnvdWEB
- docs.ckan.org/en/latest/maintaining/configuration.htmlnvdWEB
- docs.ckan.org/en/latest/maintaining/configuration.htmlnvdWEB
- docs.ckan.org/en/latest/maintaining/configuration.htmlnvdWEB
- github.com/ckan/ckan/commit/7da6a26c6183e0a97a356d1b1d2407f3ecc7b9c8nvdWEB
- github.com/ckan/ckan/commit/a4fc5e06634ed51d653ab819a7efc8e62f816f68ghsaWEB
- github.com/ckan/ckan/security/advisories/GHSA-7pq5-qcp6-mcwwnvdWEB
News mentions
0No linked articles in our index yet.