VYPR
Moderate severityNVD Advisory· Published Aug 22, 2022· Updated Aug 3, 2024

Unverified Password Change in octoprint/octoprint

CVE-2022-2930

Description

Unverified Password Change 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 allowed unverified password changes, enabling attackers to hijack user accounts without the current password.

Vulnerability

Description CVE-2022-2930 is an unverified password change vulnerability in OctoPrint versions prior to 1.8.3. The change_password_for_user API endpoint did not require the user's current password when the request was made without the SETTINGS permission. This allowed a logged-in user to change their password without verification, violating the principle of confirming the old password before setting a new one [1].

Exploitation

An attacker who gains access to a victim's OctoPrint session (e.g., through XSS or physical access) could exploit this vulnerability by sending a direct request to the password change endpoint. No authentication beyond the existing session was required to change the password, as the endpoint only checked if the user was logged in as the target user or had admin rights, but did not verify the current password. This made session hijacking particularly dangerous [2].

Impact

Successful exploitation allows an attacker to change the victim's password without knowing the current one, effectively locking the legitimate user out of their account. The attacker then gains full control over the OctoPrint instance, potentially altering printer settings, accessing sensitive data, or causing physical damage to the 3D printer.

Mitigation

The vulnerability was fixed in OctoPrint 1.8.3 by requiring the current password parameter in the request body for all users without the SETTINGS permission. Users are strongly advised to upgrade to version 1.8.3 or later. No workarounds are available for earlier versions [1].

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
1453076ee3e4

🔒️ Require the current password for changing it

https://github.com/octoprint/octoprintGina HäußgeAug 18, 2022via ghsa
6 files changed · +97 30
  • docs/api/access.rst+10 4 modified
    @@ -238,16 +238,22 @@ Change a user's password
     
        Changes the password of a user.
     
    -   Expects a JSON object with a single property ``password`` as request body.
    +   Expects a JSON object with a property ``password`` containing the new password as
    +   request body. Without the ``SETTINGS`` permission, an additional property ``current``
    +   is also required to be set on the request body, containing the user's current password.
     
    -   Requires the ``SETTINGS`` permission or to be logged in as the user.
    +   Requires the ``SETTINGS`` permission or to be logged in as the user. Note that ``current``
    +   will be evaluated even in presence of the ``SETTINGS`` permission, if set.
     
        :param username: Name of the user to change the password for
        :json password:  The new password to set
    +   :json current:   The current password
        :status 200:     No error
    -   :status 400:     If the request doesn't contain a ``password`` property or the request
    +   :status 400:     If the request doesn't contain a ``password`` property, doesn't
    +                    contain a ``current`` property even though required, or the request
                         is otherwise invalid
    -   :status 403:     No admin rights and not logged in as the user
    +   :status 403:     No admin rights, not logged in as the user or a current password
    +                    mismatch
        :status 404:     The user is unknown
     
     .. _sec-api-access-users-settings-get:
    
  • src/octoprint/server/api/access.py+12 2 modified
    @@ -241,7 +241,10 @@ def change_password_for_user(username):
         if (
             current_user is not None
             and not current_user.is_anonymous
    -        and (current_user.get_name() == username or current_user.is_admin)
    +        and (
    +            current_user.get_name() == username
    +            or current_user.has_permission(Permissions.SETTINGS)
    +        )
         ):
             if "application/json" not in request.headers["Content-Type"]:
                 abort(400, description="Expected content-type JSON")
    @@ -252,7 +255,14 @@ def change_password_for_user(username):
                 abort(400, description="Malformed JSON body in request")
     
             if "password" not in data or not data["password"]:
    -            abort(400, description="password is missing")
    +            abort(400, description="new password is missing")
    +
    +        if not current_user.has_permission(Permissions.SETTINGS) or "current" in data:
    +            if "current" not in data or not data["current"]:
    +                abort(400, description="current password is missing")
    +
    +            if not userManager.check_password(username, data["current"]):
    +                abort(403, description="Invalid current password")
     
             try:
                 userManager.change_user_password(username, data["password"])
    
  • src/octoprint/static/js/app/client/access.js+10 1 modified
    @@ -176,17 +176,26 @@
         OctoPrintAccessUsersClient.prototype.changePassword = function (
             name,
             password,
    +        oldpw,
             opts
         ) {
    +        if (_.isObject(oldpw)) {
    +            opts = oldpw;
    +            oldpw = undefined;
    +        }
    +
             if (!name || !password) {
                 throw new OctoPrintClient.InvalidArgumentError(
    -                "user name and password must be set"
    +                "user name and new password must be set"
                 );
             }
     
             var data = {
                 password: password
             };
    +        if (oldpw) {
    +            data["current"] = oldpw;
    +        }
             return this.base.putJson(this.url(name, "password"), data, opts);
         };
     
    
  • src/octoprint/static/js/app/viewmodels/access.js+21 6 modified
    @@ -40,10 +40,12 @@ $(function () {
                     groups: ko.observableArray([]),
                     permissions: ko.observableArray([]),
                     password: ko.observable(undefined),
    +                currentPassword: ko.observable(undefined),
                     repeatedPassword: ko.observable(undefined),
                     passwordMismatch: ko.pureComputed(function () {
                         return self.editor.password() !== self.editor.repeatedPassword();
                     }),
    +                currentPasswordMismatch: ko.observable(false),
                     apikey: ko.observable(undefined),
                     active: ko.observable(undefined),
                     permissionSelectable: function (permission) {
    @@ -128,6 +130,11 @@ $(function () {
                     }
                     self.editor.password(undefined);
                     self.editor.repeatedPassword(undefined);
    +                self.editor.currentPassword(undefined);
    +                self.editor.currentPasswordMismatch(false);
    +            });
    +            self.editor.currentPassword.subscribe(function () {
    +                self.editor.currentPasswordMismatch(false);
                 });
     
                 self.requestData = function () {
    @@ -244,13 +251,21 @@ $(function () {
                 self.confirmChangePassword = function () {
                     if (!CONFIG_ACCESS_CONTROL) return;
     
    -                self.updatePassword(self.currentUser().name, self.editor.password()).done(
    -                    function () {
    +                self.updatePassword(
    +                    self.currentUser().name,
    +                    self.editor.password(),
    +                    self.editor.currentPassword()
    +                )
    +                    .done(function () {
                             // close dialog
                             self.currentUser(undefined);
                             self.changePasswordDialog.modal("hide");
    -                    }
    -                );
    +                    })
    +                    .fail(function (xhr) {
    +                        if (xhr.status === 403) {
    +                            self.currentPasswordMismatch(true);
    +                        }
    +                    });
                 };
     
                 self.confirmGenerateApikey = function () {
    @@ -349,8 +364,8 @@ $(function () {
                         .done(self.fromResponse);
                 };
     
    -            self.updatePassword = function (username, password) {
    -                return OctoPrint.access.users.changePassword(username, password);
    +            self.updatePassword = function (username, password, current) {
    +                return OctoPrint.access.users.changePassword(username, password, current);
                 };
     
                 self.generateApikey = function (username) {
    
  • src/octoprint/static/js/app/viewmodels/usersettings.js+37 17 modified
    @@ -25,13 +25,17 @@ $(function () {
     
             self.access_password = ko.observable(undefined);
             self.access_repeatedPassword = ko.observable(undefined);
    +        self.access_currentPassword = ko.observable(undefined);
    +        self.access_currentPasswordMismatch = ko.observable(false);
             self.access_apikey = ko.observable(undefined);
             self.interface_language = ko.observable(undefined);
     
             self.currentUser = ko.observable(undefined);
             self.currentUser.subscribe(function (newUser) {
                 self.access_password(undefined);
                 self.access_repeatedPassword(undefined);
    +            self.access_currentPassword(undefined);
    +            self.access_currentPasswordMismatch(false);
                 self.access_apikey(undefined);
                 self.interface_language("_default");
     
    @@ -45,6 +49,9 @@ $(function () {
                     }
                 }
             });
    +        self.access_currentPassword.subscribe(function () {
    +            self.access_currentPasswordMismatch(false);
    +        });
     
             self.passwordMismatch = ko.pureComputed(function () {
                 return self.access_password() !== self.access_repeatedPassword();
    @@ -81,25 +88,38 @@ $(function () {
     
                 self.userSettingsDialog.trigger("beforeSave");
     
    -            if (self.access_password() && !self.passwordMismatch()) {
    -                self.users.updatePassword(
    -                    self.currentUser().name,
    -                    self.access_password(),
    -                    function () {}
    -                );
    +            function process() {
    +                var settings = {
    +                    interface: {
    +                        language: self.interface_language()
    +                    }
    +                };
    +                self.updateSettings(self.currentUser().name, settings).done(function () {
    +                    // close dialog
    +                    self.currentUser(undefined);
    +                    self.userSettingsDialog.modal("hide");
    +                    self.loginState.reloadUser();
    +                });
                 }
     
    -            var settings = {
    -                interface: {
    -                    language: self.interface_language()
    -                }
    -            };
    -            self.updateSettings(self.currentUser().name, settings).done(function () {
    -                // close dialog
    -                self.currentUser(undefined);
    -                self.userSettingsDialog.modal("hide");
    -                self.loginState.reloadUser();
    -            });
    +            if (self.access_password() && !self.passwordMismatch()) {
    +                self.users
    +                    .updatePassword(
    +                        self.currentUser().name,
    +                        self.access_password(),
    +                        self.access_currentPassword()
    +                    )
    +                    .done(function () {
    +                        process();
    +                    })
    +                    .fail(function (xhr) {
    +                        if (xhr.status === 403) {
    +                            self.access_currentPasswordMismatch(true);
    +                        }
    +                    });
    +            } else {
    +                process();
    +            }
             };
     
             self.copyApikey = function () {
    
  • src/octoprint/templates/dialogs/usersettings/access.jinja2+7 0 modified
    @@ -4,6 +4,13 @@
             <p>
                 {{ _('If you do not wish to change your password, just leave the following fields empty.') }}
             </p>
    +        <div class="control-group" data-bind="css: {error: access_currentPasswordMismatch()}">
    +            <label class="control-label" for="userSettings-access_currentPassword">{{ _('Current Password') }}</label>
    +            <div class="controls">
    +                <input type="password" class="input-block-level" id="userSettings-access_currentPassword" data-bind="value: access_currentPassword, valueUpdate: 'afterkeydown'" required>
    +                <span class="help-inline" data-bind="visible: access_currentPasswordMismatch()">{{ _('Passwords do not match') }}</span>
    +            </div>
    +        </div>
             <div class="control-group">
                 <label class="control-label" for="userSettings-access_password">{{ _('New Password') }}</label>
                 <div class="controls">
    

Vulnerability mechanics

Generated 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.