CVE-2022-2822 (Python, published 2022-08-15T11:21Z)
Description: value: An attacker can freely brute force username and password and can takeover any account. An attacker could easily guess user passwords and gain access to user and administrative accounts.
Severity: nan (CVSS v3 base score: 7.5)
Weakness: CWE-307 \u2014 Improper Restriction of Excessive Authentication Attempts
The product does not implement sufficient measures to prevent multiple failed authentication attempts within a short time frame, making it more susceptible to brute force attacks.
Repository: https://github.com/octoprint/octoprint
Files changed:
setup.py
src/octoprint/server/__init__.py
src/octoprint/server/api/__init__.py
src/octoprint/static/css/login.css
src/octoprint/static/js/login/login.js
src/octoprint/static/less/login.less
src/octoprint/templates/login.jinja2
Commit message: 🔒️ Rate limit failed login attempts

Should reduce likelihood of brute force attacks
on unsecure networks if users decide to deploy/make
OctoPrint accessible there against all advice to
the contrary.


Vulnerable code:
body{padding-top:40px;padding-bottom:40px;background-color:#f5f5f5}.form-signin{max-width:300px;padding:19px 29px 29px;margin:0 auto 20px;background-color:#fff;border:1px solid #e5e5e5;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.form-signin .form-signin-heading,.form-signin .checkbox{margin-bottom:10px}.form-signin input[type="text"],.form-signin input[type="password"]{font-size:16px;height:auto;margin-bottom:15px;padding:7px 9px}#login-error,#login-offline{display:none}#login-error.in,#login-offline.in{display:block}#login-overlay{position:fixed;top:0;left:0;width:100%;height:100%;z-index:10000;display:none}#login-overlay.in{display:block}#login-overlay .background{position:fixed;top:0;left:0;width:100%;height:100%;background-color:#000000;filter:alpha(opacity=50);-moz-opacity:.5;-khtml-opacity:.5;opacity:.5}#login-overlay .wrapper{position:absolute;top:0;bottom:0;left:0;right:0}#login-overlay .wrapper .outer{display:table;width:100%;height:100%}#login-overlay .wrapper .outer .inner{display:table-cell;vertical-align:middle}#login-overlay .wrapper .outer .inner .content{text-align:center;color:white}#noscript{position:fixed;top:0;left:0;width:100%;height:100%;background-color:#ffffff;z-index:12310}#noscript .wrapper{position:absolute;top:0;bottom:0;left:0;right:0}#noscript .wrapper .outer{display:table;width:100%;height:100%}#noscript .wrapper .outer .inner{display:table-cell;vertical-align:middle}#noscript .wrapper .outer .inner .content{text-align:center}
    var errorElement = $("#login-error");
        errorElement.removeClass("in");
            .fail(() => {
                errorElement.addClass("in");
#login-error,
            <div id="login-error" class="alert alert-error" data-test-id="login-error">{{ _('Incorrect username or password. Hint: Both are case sensitive!') }}</div>

Fixed code:
    "Flask-Limiter>=2.6,<3",
limiter = None
        global limiter

        from flask_limiter import Limiter
        from flask_limiter.util import get_remote_address

        app.config["RATELIMIT_STRATEGY"] = "fixed-window-elastic-expiry"

        limiter = Limiter(app, key_func=get_remote_address)

@octoprint.server.limiter.limit(
    "3/minute;5/10 minutes;10/hour",
    deduct_when=lambda response: response.status_code == 403,
    error_message="You have made too many failed login attempts. Please try again later.",
)
body{padding-top:40px;padding-bottom:40px;background-color:#f5f5f5}.form-signin{max-width:300px;padding:19px 29px 29px;margin:0 auto 20px;background-color:#fff;border:1px solid #e5e5e5;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.form-signin .form-signin-heading,.form-signin .checkbox{margin-bottom:10px}.form-signin input[type="text"],.form-signin input[type="password"]{font-size:16px;height:auto;margin-bottom:15px;padding:7px 9px}#login-error-credentials,#login-error-rate,#login-offline{display:none}#login-error-credentials.in,#login-error-rate.in,#login-offline.in{display:block}#login-overlay{position:fixed;top:0;left:0;width:100%;height:100%;z-index:10000;display:none}#login-overlay.in{display:block}#login-overlay .background{position:fixed;top:0;left:0;width:100%;height:100%;background-color:#000000;filter:alpha(opacity=50);-moz-opacity:.5;-khtml-opacity:.5;opacity:.5}#login-overlay .wrapper{position:absolute;top:0;bottom:0;left:0;right:0}#login-overlay .wrapper .outer{display:table;width:100%;height:100%}#login-overlay .wrapper .outer .inner{display:table-cell;vertical-align:middle}#login-overlay .wrapper .outer .inner .content{text-align:center;color:white}#noscript{position:fixed;top:0;left:0;width:100%;height:100%;background-color:#ffffff;z-index:12310}#noscript .wrapper{position:absolute;top:0;bottom:0;left:0;right:0}#noscript .wrapper .outer{display:table;width:100%;height:100%}#noscript .wrapper .outer .inner{display:table-cell;vertical-align:middle}#noscript .wrapper .outer .inner .content{text-align:center}
    var errorCredentialsElement = $("#login-error-credentials");
    var errorRateElement = $("#login-error-rate");
        errorCredentialsElement.removeClass("in");
        errorRateElement.removeClass("in");
            .fail((xhr) => {
                if (xhr.status === 429) {
                    errorRateElement.addClass("in");
                } else {
                    errorCredentialsElement.addClass("in");
                }
#login-error-credentials,
#login-error-rate,
            <div id="login-error-credentials" class="alert alert-error" data-test-id="login-error">{{ _('Incorrect username or password. Hint: Both are case sensitive!') }}</div>
            <div id="login-error-rate" class="alert alert-error" data-test-id="login-error-rate">{{ _('You have made too many failed login attempts. Please try again later.') }}</div>