VYPR
Low severityNVD Advisory· Published Sep 21, 2022· Updated May 28, 2025

Unrestricted Upload of File with Dangerous Type in octoprint/octoprint

CVE-2022-2872

Description

Unrestricted Upload of File with Dangerous Type in GitHub repository octoprint/octoprint prior to 1.8.3.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

OctoPrint prior to 1.8.3 allows unrestricted upload of files with dangerous types, enabling remote code execution via crafted G-code uploads.

An unrestricted file upload vulnerability exists in OctoPrint versions prior to 1.8.3, where the file upload and file management endpoints do not properly validate the type or content of uploaded files [1]. The root cause is a missing check during the file copy/move operations, allowing an attacker to bypass intended filename sanitization and upload files with arbitrary extensions, such as executable scripts [2].

To exploit this vulnerability, an attacker must have authenticated access to the OctoPrint web interface, typically obtained by guessing weak credentials or leveraging other means. Once authenticated, the attacker can craft a malicious file containing Python code or other executable content and upload it through the file upload or file management API. The file is stored in the printer's file system and can be triggered to execute under certain conditions [1][2].

The impact is severe: an attacker can achieve remote code execution (RCE) with the privileges of the OctoPrint process, potentially leading to full compromise of the host system, data theft, and use of the 3D printer for unauthorized purposes. The CVSS v3.1 base score is 8.8 (High), indicating high impacts on confidentiality, integrity, and availability [1].

The vulnerability is patched in OctoPrint version 1.8.3 and later. Users are strongly recommended to update immediately [1][2]. OctoPrint advises enabling two-factor authentication (2FA) and restricting network access to the web interface as additional security measures [3].

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
OctoPrintPyPI
< 1.8.31.8.3

Affected products

2
  • ghsa-coords
    Range: < 1.8.3
  • octoprint/octoprint/octoprintv5
    Range: unspecified

Patches

1
3e3c11811e21

🔒️ Enforce valid type on copy/move of uploads

https://github.com/octoprint/octoprintGina HäußgeAug 16, 2022via ghsa
3 files changed · +68 32
  • src/octoprint/filemanager/storage.py+12 0 modified
    @@ -954,6 +954,12 @@ def copy_file(self, source, destination):
                 source, destination, must_not_equal=True
             )
     
    +        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,
    +            )
    +
             try:
                 shutil.copy2(source_data["fullpath"], destination_data["fullpath"])
             except Exception as e:
    @@ -983,6 +989,12 @@ def move_file(self, source, destination, allow_overwrite=False):
                 source, destination
             )
     
    +        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,
    +            )
    +
             # only a display rename? Update that and bail early
             if source_data["fullpath"] == destination_data["fullpath"]:
                 self._set_display_metadata(destination_data)
    
  • src/octoprint/server/api/files.py+45 32 modified
    @@ -1138,42 +1138,55 @@ def slicing_done(target, path, select_after_slicing, print_after_slicing):
                 if not (is_file or is_folder):
                     abort(400, description=f"Neither file nor folder, can't {command}")
     
    -            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:
    +            try:
    +                if command == "copy":
    +                    # destination already there? error...
    +                    if _verifyFileExists(target, destination) or _verifyFolderExists(
    +                        target, destination
    +                    ):
                             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)
    +                        fileManager.copy_file(target, filename, destination)
                         else:
    -                        fileManager.move_folder(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}",
    +                    )
     
                 location = url_for(
                     ".readGcodeFile",
    
  • src/octoprint/server/__init__.py+11 0 modified
    @@ -40,6 +40,7 @@
     from watchdog.observers.polling import PollingObserver
     from werkzeug.exceptions import HTTPException
     
    +import octoprint.filemanager
     import octoprint.util
     import octoprint.util.net
     from octoprint.server import util
    @@ -746,6 +747,15 @@ def download_name_generator(path):
                 )
             }
     
    +        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,
    +            )
    +        }
    +
             valid_timelapse = lambda path: not octoprint.util.is_hidden_path(path) and (
                 octoprint.timelapse.valid_timelapse(path)
                 or octoprint.timelapse.valid_timelapse_thumbnail(path)
    @@ -840,6 +850,7 @@ def joined_dict(*dicts):
                         download_permission_validator,
                         download_handler_kwargs,
                         no_hidden_files_validator,
    +                    only_known_types_validator,
                         additional_mime_types,
                     ),
                 ),
    

Vulnerability mechanics

Root cause

"Missing validation of file type on copy, move, and download operations allows a file with a valid G-code extension to be renamed to a dangerous extension like .html, enabling stored XSS."

Attack vector

An attacker with file-upload or file-manipulation permissions can copy or move an existing valid G-code file to a destination with a different extension (e.g., .html) via the API's copy/move commands. The patched code shows that prior to the fix, no check was performed on the destination filename's extension during copy_file() or move_file() in the storage layer [patch_id=22560]. When a victim subsequently downloads the renamed file through the web interface, the browser may render it as HTML, allowing execution of arbitrary JavaScript in the context of the OctoPrint origin. The attack requires the attacker to have access to the file management API and the victim to trigger a download of the maliciously renamed file.

Affected code

The vulnerability exists in the file copy/move logic in src/octoprint/filemanager/storage.py (copy_file and move_file methods) and the download handler in src/octoprint/server/__init__.py. The API endpoint in src/octoprint/server/api/files.py orchestrates these operations but previously lacked type validation on the destination filename.

What the fix does

The patch adds calls to octoprint.filemanager.valid_file_type() in both copy_file() and move_file() inside src/octoprint/filemanager/storage.py [patch_id=22560]. If the destination filename does not have a recognized file type (e.g., .gcode, .stl), a StorageError with code INVALID_FILE is raised, which the API layer catches and returns HTTP 415 (Unsupported Media Type). Additionally, a new only_known_types_validator is added to the download route in src/octoprint/server/__init__.py, ensuring that even direct downloads of files with unrecognized extensions are rejected with a 404 status code. This closes the vector by preventing both the creation of files with dangerous extensions and the serving of existing files with such extensions.

Preconditions

  • authAttacker must have access to the OctoPrint file management API (e.g., authenticated user with file upload/manipulation permissions)
  • inputA valid file (e.g., .gcode) must already exist on the server to be copied or moved
  • networkVictim must download the renamed file via the web interface for XSS to execute

Generated on May 18, 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.