Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VULN: Auth Bypass in jwt_required Decorator #565

Closed
jacobmillsfl opened this issue Dec 26, 2024 · 1 comment
Closed

VULN: Auth Bypass in jwt_required Decorator #565

jacobmillsfl opened this issue Dec 26, 2024 · 1 comment

Comments

@jacobmillsfl
Copy link

I'm using jwt_required() to ensure my API endpoints are protected. Here is my common usage pattern

@file_bp.route("/upload", methods=["POST"])
@jwt_required()
def upload_file():
    """ Stores files to the server """

The above works as intended, but if the decorators are in another order the JWT token isn't validated whatsoever.

@jwt_required()
@file_bp.route("/upload", methods=["POST"])
def upload_file():
    """ Stores files to the server, regardless of authentication """

In the above, a user can still call this endpoint with an invalid JWT or no JWT at all. This presents a major authentication bypass security issue that is bound to affect many users. I wish to responsibly disclose but I couldn't find where else to report this.

@vimalloc
Copy link
Owner

vimalloc commented Dec 26, 2024

This is not a vulnerability, it's simply how decorators work. A decorator is a function that takes another function as an argument, and returns a new function based on whatever the decorator is doing and the original function passed in as an argument.

For example, lets say you had a route that looked like this:

@app.route("/foo", methods=["GET"])
@jwt_required()
def foo():
    return jsonify(foo="bar"), 200

If we remove the decorator syntactic sugar and write this same code without decorators, it would look like this:

def foo():
    return jsonify(foo="bar"), 200

protected_foo = jwt_required()(foo)
app.route("/foo", methods=["GET"])(protected_foo)

As you can see, in this case we are passing a function that has been protected via jwt_required() to the app.route call. All the app.route decorator is doing is taking whatever function you pass in, and binding it to the given route.

So what happens when we reverse the order of the decorators?

@jwt_required()
@app.route("/foo", methods=["GET"])
def foo():
    return jsonify(foo="bar"), 200

Desugaring it, it would look like this:

def foo():
    return jsonify(foo="bar"), 200

routed_foo = app.route("/foo", methods=["GET"])(foo)
jwt_required()(routed_foo)

When written out this way, it should be clear what is happening. You are binding the unprotected function to the route, so whenever a request comes in that matches that route, it will use the function that was not protected. Then you created a protected function, but this protected function is never being called anywhere, because it was not bound to the route.

As a side note, if you did have the orders of the decorators reversed, and tried to call any of the flask-jwt-extended helpers inside your view function, you would run into an error. For example, if you tried to hit this endpoint:

@jwt_required()
@app.route("/protected", methods=["GET"])
def protected():
    return jsonify(logged_in_as=get_jwt_identity()), 200

You would get the following error:

  File "/Users/lily/code/flask-jwt-extended/simple.py", line 35, in protected
    return jsonify(logged_in_as=get_jwt_identity()), 200
                                ~~~~~~~~~~~~~~~~^^
  File "/Users/lily/code/flask-jwt-extended/flask_jwt_extended/utils.py", line 63, in get_jwt_identity
    return get_jwt().get(config.identity_claim_key, None)
           ~~~~~~~^^
  File "/Users/lily/code/flask-jwt-extended/flask_jwt_extended/utils.py", line 29, in get_jwt
    raise RuntimeError(
    ...<2 lines>...
    )
RuntimeError: You must call `@jwt_required()` or `verify_jwt_in_request()` before using this method

tl;dr - there is nothing wrong here. Just make sure you understand how decorators work and use them correctly in your application.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants