djangorestframework-simplejwt 5.3.1 - Information Disclosure

Exploit Author: Dhrumil Mistry Analysis Author: www.bubbleslearn.ir Category: WebApps Language: Python Published Date: 2024-04-15
# Exploit Title: djangorestframework-simplejwt 5.3.1 - Information Disclosure
# Date: 26/01/2024
# Exploit Author: Dhrumil Mistry (dmdhrumilmistry)
# Vendor Homepage: https://github.com/jazzband/djangorestframework-simplejwt/
# Software Link:https://github.com/jazzband/djangorestframework-simplejwt/releases/tag/v5.3.1
# Version: <= 5.3.1
# Tested on: MacOS
# CVE : CVE-2024-22513

# The version of djangorestframework-simplejwt up to 5.3.1 is vulnerable.
# This vulnerability has the potential to cause various security issues,
# including Business Object Level Authorization (BOLA), Business Function
# Level Authorization (BFLA), Information Disclosure, etc. The vulnerability
# arises from the fact that a user can access web application resources even
# after their account has been disabled, primarily due to the absence of proper
# user validation checks.

# If a programmer generates a JWT token for an inactive user using
`AccessToken`
# class and `for_user` method then a JWT token is returned which can
be used for
# authentication across the django and django rest framework application.

# Start Django Shell using below command:
# python manage.py shell
# ----------------------------------------

# Create inactive user and generate token for the user
from django.contrib.auth.models import User
from rest_framework_simplejwt.tokens import AccessToken

# create inactive user
inactive_user_id = User.objects.create_user('testuser',
'test@example.com', 'testPassw0rd!', is_active=False).id

# django application programmer generates token for the inactive user
AccessToken.for_user(User.objects.get(id=inactive_user_id))  # error
should be raised since user is inactive

# django application verifying user token
AccessToken.for_user(User.objects.get(id=inactive_user_id)).verify() #
no exception is raised during verification of inactive user token


djangorestframework-simplejwt 5.3.1 — Information Disclosure (CVE-2024-22513)

Summary: djangorestframework-simplejwt versions up to and including 5.3.1 contain a flaw that allows JSON Web Tokens (JWTs) to be created and accepted for users whose accounts are marked inactive. This can lead to information disclosure and authorization bypass (BOLA/BFLA) because tokens previously created for deactivated users remain valid and are accepted by token verification and authentication routines.

Vulnerability overview

The library exposes a method for generating access tokens for a Django user (AccessToken.for_user). In affected releases, this method and the token verification/authentication flow do not enforce a check that the user is active (User.is_active). As a result, an application that issues a token for an inactive or disabled user — intentionally or accidentally — will obtain a token that is accepted by the default verification/authentication process. This may allow access to resources even after accounts are disabled.

CVE

  • CVE identifier: CVE-2024-22513
  • Affected versions: djangorestframework-simplejwt <= 5.3.1
  • Fixed in: the vendor patch/release after disclosure (see references)

Why this matters

  • Account deactivation is a common control used to prevent a user from accessing a system. If tokens remain valid for disabled accounts, deactivation no longer prevents resource access.
  • Business Object Level Authorization (BOLA) and Business Function Level Authorization (BFLA) failures can arise because a formerly-disabled user can present a valid token and access records or functions they should not.
  • Information disclosure: resources that should be protected after an account is disabled may be accessible.

Reproducing the issue (example)

python manage.py shell

Open a Django shell and run the following demonstration (development environment only):

from django.contrib.auth.models import User
from rest_framework_simplejwt.tokens import AccessToken

# create user marked inactive
inactive_user = User.objects.create_user(
    username='testuser',
    email='test@example.com',
    password='testPassw0rd!',
    is_active=False
)

# generate a token for that inactive user
token = AccessToken.for_user(inactive_user)

# token can be verified using the library (no exception for inactive user)
token.verify()
print(str(token))

Explanation: This snippet creates a Django user with is_active=False, generates an AccessToken using AccessToken.for_user, calls token.verify() and then prints the token. On affected versions the token is generated and verified without raising an exception for inactive user state.

Why the above demonstrates the bug

The expectation in most applications is that account deactivation should prevent new/ongoing authentication sessions or at least cause tokens to be rejected. Because the library does not enforce user.is_active during token creation or verification, the token continues to be usable, creating a gap in the security model.

Impact and real-world scenarios

  • Administrators disable a compromised or ex-employee account expecting immediate loss of access, but pre-issued tokens remain valid and allow continuing access.
  • Automated account suspension (e.g., due to policy violations) may not terminate sessions if tokens are accepted for inactive accounts.
  • APIs that rely solely on JWT verification without an additional user-active check may inadvertently expose protected data or functions to inactive accounts.

Detection and forensics

  • Search application logs for requests using tokens associated with users that have been deactivated.
  • If you store token metadata (e.g., jti, user_id claims), check whether tokens issued to a user persist after the user was disabled.
  • Look for unusual access patterns from users that should be inactive.

Remediation and mitigations

Primary mitigation — Upgrade

The vendor released a patch after disclosure. The recommended first action is to upgrade djangorestframework-simplejwt to the fixed version (the release that contains a proper check) as soon as possible. Upgrading ensures the official fix and reduces chances of missing edge cases.

Immediate mitigations (if you cannot upgrade right away)

  • Ensure your application enforces user.is_active in token issuance endpoints — do not issue tokens for inactive users.
  • Ensure your authentication layer checks request.user.is_active after token authentication and rejects requests where it is false.
  • Use token blacklisting or revocation to invalidate tokens for users that become inactive.
  • As an emergency measure, rotate your JWT signing keys or change the SECRET_KEY used to sign tokens to invalidate all existing tokens (note: disruptive).

Recommended code-level mitigations

Below are safe, conservative code examples you can add to your project to block tokens for inactive users until you upgrade.

1) Prevent issuing tokens to inactive users (custom TokenObtainPairView)

from rest_framework_simplejwt.views import TokenObtainPairView
from rest_framework.exceptions import AuthenticationFailed

class SafeTokenObtainPairView(TokenObtainPairView):
    def post(self, request, *args, **kwargs):
        # Delegate to serializer for credential checking, then ensure user is active
        response = super().post(request, *args, **kwargs)
        # The default flow sets a token only after credentials are valid.
        # To be explicit: ensure the user from validated credentials is active.
        user = getattr(self, 'user', None)
        if user is not None and not user.is_active:
            raise AuthenticationFailed('User account is disabled.')
        return response

Explanation: Subclassing the TokenObtainPairView allows you to insert an explicit check that prevents token issuance for users with is_active=False. Place this view in your URL configuration instead of the default one.

2) Enforce is_active in authentication (custom authentication class)

from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework.exceptions import AuthenticationFailed

class ActiveUserJWTAuthentication(JWTAuthentication):
    def get_user(self, validated_token):
        user = super().get_user(validated_token)
        if not user.is_active:
            raise AuthenticationFailed('User account is disabled.')
        return user

Explanation: When DRF authenticates a request using JWTAuthentication, it resolves the user object from token claims. By overriding get_user and raising AuthenticationFailed for inactive users, you ensure every request using a JWT is rejected if the underlying user is disabled.

3) Monkeypatch / helper for AccessToken.for_user (temporary)

If you must prevent creation of tokens via AccessToken.for_user across your codebase and cannot upgrade, add a guarded utility to centralize creation:

from rest_framework_simplejwt.tokens import AccessToken
from rest_framework.exceptions import PermissionDenied

def safe_access_token_for_user(user):
    if not user.is_active:
        raise PermissionDenied("Cannot create token for inactive user.")
    return AccessToken.for_user(user)

Explanation: This wrapper enforces the active-check at the point of creation. Replace direct calls to AccessToken.for_user with safe_access_token_for_user in your codebase.

Token revocation / blacklisting

To proactively invalidate active tokens for an account that is being disabled, consider:

  • Using the library's token blacklisting (if available and configured) to add existing refresh tokens to a blacklist.
  • Maintaining a server-side token revocation store keyed by a user identifier and token identifier (jti claim). Check the store during authentication.
  • If feasible, rotate signing keys to invalidate all outstanding tokens (high impact — breaks all clients).

Detection & recovery checklist

  • Upgrade djangorestframework-simplejwt to the patched version immediately.
  • Deploy the custom authentication guard above to block inactive users.
  • Blacklist/revoke tokens for accounts that were deactivated while using a vulnerable version.
  • Review logs for suspicious access after deactivation and take appropriate incident response actions.
  • Notify affected stakeholders if the vulnerability resulted in unauthorized data access.

Best practices to prevent similar issues

  • Enforce server-side authorization checks beyond token validity (e.g., user active flag, role checks) at every sensitive operation.
  • Do not rely solely on token verification to express business state; consult authoritative user state in the database during authentication/authorization.
  • Adopt token revocation/blacklist mechanisms for long-lived tokens.
  • Maintain an inventory and update cadence for security-critical dependencies.

References

  • Vendor / project: https://github.com/jazzband/djangorestframework-simplejwt/
  • Vulnerable release page (example): the project's release notes corresponding to v5.3.1
  • CVE: CVE-2024-22513

Notes

All code examples above are intended for defensive use in your own environment. Test changes in a staging environment before applying to production. Prioritize upgrading to the official patched release from the project to ensure a comprehensive fix.