CVE-2022-2872 (Python, published 2022-09-21T10:15Z)
Description: value: Unrestricted Upload of File with Dangerous Type in GitHub repository octoprint/octoprint prior to 1.8.3.
Severity: nan (CVSS v3 base score: 5.4)
Weakness: CWE-434 \u2014 Unrestricted Upload of File with Dangerous Type
The product allows the upload or transfer of dangerous file types that are automatically processed within its environment.
Repository: https://github.com/octoprint/octoprint
Files changed:
src/octoprint/filemanager/storage.py
src/octoprint/server/__init__.py
src/octoprint/server/api/files.py
Commit message: 🔒️ Enforce valid type on copy/move of uploads

Also enforce that on downloads and mirror it on the
API.

Prevents accidental or intentional renaming of
valid files to something like .html to e.g. attempt
to run arbitrary JS code in the browser on
"download" in the context of the OctoPrint URL.
Stealing login credentials via XSS is very unlikely
here given their http-only nature, however it's
still better to prevent this from happening anyhow.


Vulnerable code:
            if command == "copy":
                # destination already there? error...
                if _verifyFileExists(target, destination) or _verifyFolderExists(
                    target, destination
                ):
                    abort(409, description="File or folder does already exist")

                if is_file:
                    fileManager.copy_file(target, filename, destination)
                else:
                    fileManager.copy_folder(target, filename, destination)

            elif command == "move":
                with Permissions.FILES_DELETE.require(403):
                    if _isBusy(target, filename):
                        abort(
                            409,
                            description="Trying to move a file or folder that is currently in use",
                        )

                    # destination already there AND not ourselves (= display rename)? error...
                    if (
                        _verifyFileExists(target, destination)
                        or _verifyFolderExists(target, destination)
                    ) and sanitized_destination != filename:
                    # deselect the file if it's currently selected
                    currentOrigin, currentFilename = _getCurrentFile()
                    if currentFilename is not None and filename == currentFilename:
                        printer.unselect_file()

                        fileManager.move_file(target, filename, destination)
                        fileManager.move_folder(target, filename, destination)

Fixed code:
        if not octoprint.filemanager.valid_file_type(destination_data["name"]):
            raise StorageError(
                f"{destination_data['name']} is an unrecognized file type",
                code=StorageError.INVALID_FILE,
            )

        if not octoprint.filemanager.valid_file_type(destination_data["name"]):
            raise StorageError(
                f"{destination_data['name']} is an unrecognized file type",
                code=StorageError.INVALID_FILE,
            )

import octoprint.filemanager
        only_known_types_validator = {
            "path_validation": util.tornado.path_validation_factory(
                lambda path: octoprint.filemanager.valid_file_type(
                    os.path.basename(path)
                ),
                status_code=404,
            )
        }

                    only_known_types_validator,
            try:
                if command == "copy":
                    # destination already there? error...
                    if _verifyFileExists(target, destination) or _verifyFolderExists(
                        target, destination
                    ):
                        fileManager.copy_file(target, filename, destination)
                        fileManager.copy_folder(target, filename, destination)

                elif command == "move":
                    with Permissions.FILES_DELETE.require(403):
                        if _isBusy(target, filename):
                            abort(
                                409,
                                description="Trying to move a file or folder that is currently in use",
                            )

                        # destination already there AND not ourselves (= display rename)? error...
                        if (
                            _verifyFileExists(target, destination)
                            or _verifyFolderExists(target, destination)
                        ) and sanitized_destination != filename:
                            abort(409, description="File or folder does already exist")

                        # deselect the file if it's currently selected
                        currentOrigin, currentFilename = _getCurrentFile()
                        if currentFilename is not None and filename == currentFilename:
                            printer.unselect_file()

                        if is_file:
                            fileManager.move_file(target, filename, destination)
                        else:
                            fileManager.move_folder(target, filename, destination)

            except octoprint.filemanager.storage.StorageError as e:
                if e.code == octoprint.filemanager.storage.StorageError.INVALID_FILE:
                    abort(
                        415,
                        description=f"Could not {command} {filename} to {destination}, invalid type",
                    )
                else:
                    abort(
                        500,
                        description=f"Could not {command} {filename} to {destination}",
                    )