Common Weakness Enumeration

CWE-613

Allowed-with-Review

Insufficient Session Expiration

Abstraction: Base · Status: Incomplete

According to WASC, "Insufficient Session Expiration is when a web site permits an attacker to reuse old session credentials or session IDs for authorization."

875 vulnerabilities reference this CWE, most recent first.

GHSA-FJFH-84XH-5HV3

Vulnerability from github – Published: 2022-08-02 00:00 – Updated: 2022-08-09 00:00
VLAI
Details

NLnet Labs Unbound, up to and including version 1.16.1, is vulnerable to a novel type of the "ghost domain names" attack. The vulnerability works by targeting an Unbound instance. Unbound is queried for a rogue domain name when the cached delegation information is about to expire. The rogue nameserver delays the response so that the cached delegation information is expired. Upon receiving the delayed answer containing the delegation information, Unbound overwrites the now expired entries. This action can be repeated when the delegation information is about to expire making the rogue delegation information ever-updating. From version 1.16.2 on, Unbound stores the start time for a query and uses that to decide if the cached delegation information can be overwritten.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2022-30699"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-613"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2022-08-01T15:15:00Z",
    "severity": "MODERATE"
  },
  "details": "NLnet Labs Unbound, up to and including version 1.16.1, is vulnerable to a novel type of the \"ghost domain names\" attack. The vulnerability works by targeting an Unbound instance. Unbound is queried for a rogue domain name when the cached delegation information is about to expire. The rogue nameserver delays the response so that the cached delegation information is expired. Upon receiving the delayed answer containing the delegation information, Unbound overwrites the now expired entries. This action can be repeated when the delegation information is about to expire making the rogue delegation information ever-updating. From version 1.16.2 on, Unbound stores the start time for a query and uses that to decide if the cached delegation information can be overwritten.",
  "id": "GHSA-fjfh-84xh-5hv3",
  "modified": "2022-08-09T00:00:27Z",
  "published": "2022-08-02T00:00:25Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2022-30699"
    },
    {
      "type": "WEB",
      "url": "https://lists.debian.org/debian-lts-announce/2023/03/msg00024.html"
    },
    {
      "type": "WEB",
      "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/5L3ZFWZZFPBIL654BG75RWXUMPFQJ5EC"
    },
    {
      "type": "WEB",
      "url": "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/D35CX4SCZVNKZTWJXPDFTHWZHINMGEZD"
    },
    {
      "type": "WEB",
      "url": "https://security.gentoo.org/glsa/202212-02"
    },
    {
      "type": "WEB",
      "url": "https://www.nlnetlabs.nl/downloads/unbound/CVE-2022-30698_CVE-2022-30699.txt"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-FJJV-535C-93P3

Vulnerability from github – Published: 2025-07-28 21:31 – Updated: 2025-07-28 21:31
VLAI
Details

Improper session invalidation in the component /bbdms/change-password.php of PHPGurukul Blood Bank & Donor Management System v2.4 allows attackers to execute a session hijacking attack.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2025-50487"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-613"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2025-07-28T19:15:42Z",
    "severity": "HIGH"
  },
  "details": "Improper session invalidation in the component /bbdms/change-password.php of PHPGurukul Blood Bank \u0026 Donor Management System v2.4 allows attackers to execute a session hijacking attack.",
  "id": "GHSA-fjjv-535c-93p3",
  "modified": "2025-07-28T21:31:34Z",
  "published": "2025-07-28T21:31:34Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-50487"
    },
    {
      "type": "WEB",
      "url": "https://github.com/VasilVK/CVE/tree/main/CVE-2025-50487"
    },
    {
      "type": "WEB",
      "url": "http://blood.com"
    },
    {
      "type": "WEB",
      "url": "http://phpgurukul.com"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-FMQH-2M79-343W

Vulnerability from github – Published: 2025-05-12 18:31 – Updated: 2025-05-12 18:31
VLAI
Details

A suspended or recently logged-out user could continue to interact with Blueframe until the time-out period occurred.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2025-46741"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-269",
      "CWE-613"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2025-05-12T17:15:47Z",
    "severity": "MODERATE"
  },
  "details": "A suspended or recently logged-out user could continue to interact with Blueframe until the time-out period occurred.",
  "id": "GHSA-fmqh-2m79-343w",
  "modified": "2025-05-12T18:31:47Z",
  "published": "2025-05-12T18:31:47Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-46741"
    },
    {
      "type": "WEB",
      "url": "https://selinc.com/products/software/latest-software-versions"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:H/I:H/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-FMQW-VQH5-CWQ9

Vulnerability from github – Published: 2019-12-02 18:19 – Updated: 2021-08-19 16:03
VLAI
Summary
Apache NiFi user log out issue
Details

When using an authentication mechanism other than PKI, when the user clicks Log Out in NiFi versions 1.0.0 to 1.9.2, NiFi invalidates the authentication token on the client side but not on the server side. This permits the user's client-side token to be used for up to 12 hours after logging out to make API requests to NiFi.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Maven",
        "name": "org.apache.nifi:nifi-web-security"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "1.3.0"
            },
            {
              "fixed": "1.10.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "Maven",
        "name": "org.apache.nifi:nifi-web-api"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "1.3.0"
            },
            {
              "fixed": "1.10.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2019-12421"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-613"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2019-12-02T17:32:47Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "When using an authentication mechanism other than PKI, when the user clicks Log Out in NiFi versions 1.0.0 to 1.9.2, NiFi invalidates the authentication token on the client side but not on the server side. This permits the user\u0027s client-side token to be used for up to 12 hours after logging out to make API requests to NiFi.",
  "id": "GHSA-fmqw-vqh5-cwq9",
  "modified": "2021-08-19T16:03:16Z",
  "published": "2019-12-02T18:19:39Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2019-12421"
    },
    {
      "type": "WEB",
      "url": "https://github.com/apache/nifi/pull/3362"
    },
    {
      "type": "WEB",
      "url": "https://github.com/apache/nifi/commit/cf6f5172503ce438c6c22c334c9367f774db7b24"
    },
    {
      "type": "WEB",
      "url": "https://lists.apache.org/thread.html/rca37935d661f4689cb4119f1b3b224413b22be161b678e6e6ce0c69b@%3Ccommits.nifi.apache.org%3E"
    },
    {
      "type": "WEB",
      "url": "https://nifi.apache.org/security.html#CVE-2019-12421"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Apache NiFi user log out issue"
}

GHSA-FP3Q-9278-8PPC

Vulnerability from github – Published: 2022-05-24 17:35 – Updated: 2022-10-02 00:00
VLAI
Details

IBM Cloud Pak for Security 1.3.0.1(CP4S) does not invalidate session after logout which could allow an authenticated user to obtain sensitive information from the previous session. IBM X-Force ID: 186789.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2020-4696"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-613"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2020-11-30T16:15:00Z",
    "severity": "MODERATE"
  },
  "details": "IBM Cloud Pak for Security 1.3.0.1(CP4S) does not invalidate session after logout which could allow an authenticated user to obtain sensitive information from the previous session. IBM X-Force ID: 186789.",
  "id": "GHSA-fp3q-9278-8ppc",
  "modified": "2022-10-02T00:00:33Z",
  "published": "2022-05-24T17:35:19Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2020-4696"
    },
    {
      "type": "WEB",
      "url": "https://exchange.xforce.ibmcloud.com/vulnerabilities/186789"
    },
    {
      "type": "WEB",
      "url": "https://www.ibm.com/support/pages/node/6372528"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-FP6Q-GCCW-7QQM

Vulnerability from github – Published: 2024-10-22 17:55 – Updated: 2024-10-22 19:22
VLAI
Summary
Umbraco CMS logout page displayed before session expiration
Details

Impact

The Backoffice displays the logout page with a session timeout message before the server session has fully expired, causing users to believe they have been logged out approximately 30 seconds before they actually are.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "NuGet",
        "name": "Umbraco.CMS"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "13.0.0"
            },
            {
              "fixed": "13.5.2"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "NuGet",
        "name": "Umbraco.CMS"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "10.0.0"
            },
            {
              "fixed": "10.8.7"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "NuGet",
        "name": "UmbracoCMS"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "8.0.0"
            },
            {
              "fixed": "8.18.15"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2024-48926"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-613"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2024-10-22T17:55:01Z",
    "nvd_published_at": "2024-10-22T16:15:08Z",
    "severity": "MODERATE"
  },
  "details": "### Impact\nThe Backoffice displays the logout page with a session timeout message before the server session has fully expired, causing users to believe they have been logged out approximately 30 seconds before they actually are.\n",
  "id": "GHSA-fp6q-gccw-7qqm",
  "modified": "2024-10-22T19:22:29Z",
  "published": "2024-10-22T17:55:01Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/umbraco/Umbraco-CMS/security/advisories/GHSA-fp6q-gccw-7qqm"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-48926"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/umbraco/Umbraco-CMS"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Umbraco CMS logout page displayed before session expiration"
}

GHSA-FPCR-4RR5-HPCP

Vulnerability from github – Published: 2022-05-24 17:21 – Updated: 2026-02-10 14:16
VLAI
Summary
Mattermost Server vulnerable to user account takeover when Single Sign-On OAuth2 is used
Details

An issue was discovered in Mattermost Server before 4.0.0, 3.10.2, and 3.9.2, when Single Sign-On OAuth2 is used. An attacker could claim somebody else's account.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/mattermost/mattermost-server"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "3.9.2-0.20170714134023-b17fca0d5ee7"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/mattermost/mattermost-server"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "3.10.0"
            },
            {
              "fixed": "3.10.2"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2017-18906"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-613"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-02-06T22:42:23Z",
    "nvd_published_at": "2020-06-19T20:15:00Z",
    "severity": "HIGH"
  },
  "details": "An issue was discovered in Mattermost Server before 4.0.0, 3.10.2, and 3.9.2, when Single Sign-On OAuth2 is used. An attacker could claim somebody else\u0027s account.",
  "id": "GHSA-fpcr-4rr5-hpcp",
  "modified": "2026-02-10T14:16:10Z",
  "published": "2022-05-24T17:21:06Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2017-18906"
    },
    {
      "type": "WEB",
      "url": "https://github.com/mattermost/mattermost/commit/259ad46f30d0fac2f7c5c14f3b76b2170f7e90c7"
    },
    {
      "type": "WEB",
      "url": "https://github.com/mattermost/mattermost/commit/b17fca0d5ee7557e3df1cf1d1da8bd749859e35f"
    },
    {
      "type": "WEB",
      "url": "https://github.com/mattermost/mattermost/commit/fbc170733e86f09b46ba754dd03304733d2f482f"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/mattermost/mattermost"
    },
    {
      "type": "WEB",
      "url": "https://mattermost.com/security-updates"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Mattermost Server vulnerable to user account takeover when Single Sign-On OAuth2 is used"
}

GHSA-FPG4-3688-Q56C

Vulnerability from github – Published: 2024-02-27 03:31 – Updated: 2024-08-16 18:30
VLAI
Details

An issue was discovered in Linksys Router E1700 1.0.04 (build 3), allows authenticated attackers to escalate privileges via a crafted GET request to the /goform/* URI or via the ExportSettings function.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2024-22543"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-613"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2024-02-27T01:15:06Z",
    "severity": "MODERATE"
  },
  "details": "An issue was discovered in Linksys Router E1700 1.0.04 (build 3), allows authenticated attackers to escalate privileges via a crafted GET request to the /goform/* URI or via the ExportSettings function.",
  "id": "GHSA-fpg4-3688-q56c",
  "modified": "2024-08-16T18:30:55Z",
  "published": "2024-02-27T03:31:01Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-22543"
    },
    {
      "type": "WEB",
      "url": "https://mat4mee.notion.site/Leaked-SessionID-can-lead-to-authentication-bypass-on-the-Linksys-Router-E1700-f56f9c4b15e7443fa237bd1b101a18d2"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-FPRX-3CV4-96VC

Vulnerability from github – Published: 2024-07-23 09:30 – Updated: 2025-07-10 18:31
VLAI
Details

On versions before 2.1.4, session is not invalidated after logout. When the user logged in successfully, the Backend service returns "Authorization" as the front-end authentication credential. "Authorization" can still initiate requests and access data even after logout.

Mitigation:

all users should upgrade to 2.1.4

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2024-29070"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-613"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2024-07-23T09:15:02Z",
    "severity": "CRITICAL"
  },
  "details": "On versions before 2.1.4,\u00a0session is not invalidated after logout. When the user logged in successfully, the Backend service returns \"Authorization\" as the front-end authentication credential. \"Authorization\" can still initiate requests and access data even after logout.\n\nMitigation:\n\nall users should upgrade to 2.1.4",
  "id": "GHSA-fprx-3cv4-96vc",
  "modified": "2025-07-10T18:31:13Z",
  "published": "2024-07-23T09:30:39Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-29070"
    },
    {
      "type": "WEB",
      "url": "https://lists.apache.org/thread/zslblrz1l0n9t67mqdv42yv75ncfn9zl"
    },
    {
      "type": "WEB",
      "url": "http://www.openwall.com/lists/oss-security/2024/07/22/4"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:H",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-FPW6-HRG5-Q5X5

Vulnerability from github – Published: 2026-05-07 21:34 – Updated: 2026-05-07 21:34
VLAI
Summary
ech0's acess tokens with expiry=never cannot be revoked: logout panics, delete does not blacklist JTI
Details

Summary

Access tokens created with the "never expire" option have no exp JWT claim. Three independent revocation mechanisms fail for this token type. Logout at internal/handler/auth/auth.go:154 and :163 dereferences claims.ExpiresAt.Time, panicking on the nil field so the token never hits the blacklist. RevokeToken at internal/repository/auth/auth.go:45-50 skips when remainTTL <= 0. The admin's "Delete token" panel action at internal/service/setting/access_token_service.go:183-185 removes the database record but does not call RevokeToken to blacklist the JTI. Once a never-expire token leaks, the JWT stays cryptographically valid until the admin rotates the signing key across the entire instance.

Details

Creation path at internal/util/jwt/jwt.go:103-105:

// expiry = 0 表示永不过期
if expiry > 0 {
    claims.ExpiresAt = jwt.NewNumericDate(time.Now().UTC().Add(time.Duration(expiry) * time.Second))
}

For NEVER_EXPIRY, expiry = 0 and the conditional skips. The resulting JWT has no exp claim. The middleware at internal/middleware/auth.go accepts it; the jwt/v5 parser does not require exp by default.

Failure mode 1, logout panic at internal/handler/auth/auth.go:163:

// Refresh-token revocation at line 154 (safe in practice: refresh tokens always have exp).
// Access-token revocation, same pattern, at line 163 (the bug):
if claims, err := jwtUtil.ParseToken(authHeader[7:]); err == nil && claims.ID != "" {
    remaining := time.Until(claims.ExpiresAt.Time)  // nil deref when ExpiresAt is nil
    h.authService.RevokeToken(claims.ID, remaining)
}

For a never-expire access token, claims.ExpiresAt is nil. claims.ExpiresAt.Time panics. Gin's Recovery middleware catches it and returns HTTP 500; the JTI never reaches RevokeToken. Line 154 shares the same pattern against refresh tokens, but refresh tokens are always issued with an expiry so the nil dereference does not fire there in practice.

Failure mode 2, RevokeToken skip at internal/repository/auth/auth.go:45-50:

func (authRepository *AuthRepository) RevokeToken(jti string, remainTTL time.Duration) {
    if jti == "" || remainTTL <= 0 {
        return
    }
    authRepository.cache.SetWithTTL(fmt.Sprintf("%s%s", blacklistPrefix, jti), true, 1, remainTTL)
}

Even if the logout path were patched to handle nil ExpiresAt, a caller computing remainTTL = 0 would still skip the blacklist write.

Failure mode 3, admin delete at internal/service/setting/access_token_service.go:183-185:

return settingService.transactor.Run(ctx, func(txCtx context.Context) error {
    return settingService.settingRepository.DeleteAccessTokenByID(txCtx, id)
})

Deletion removes the token's metadata row from the database. No call to RevokeToken, no write to the JTI blacklist. The JWT continues to validate because the signature is still authentic and the middleware does not consult the metadata table.

The only way to invalidate a compromised never-expire token is to rotate JWT_SECRET, which invalidates every token for every user across the whole instance.

Proof of Concept

Default install. Admin creates a never-expire access token; its revocation pathways all fail:

import requests, base64, json
TARGET = "http://localhost:8300"

owner = requests.post(f"{TARGET}/api/login",
                      json={"username": "owner", "password": "owner-pw"}
                     ).json()["data"]["access_token"]

# 1) Create a never-expire access token.
r = requests.post(f"{TARGET}/api/access-tokens",
                  headers={"Authorization": f"Bearer {owner}",
                           "content-type": "application/json"},
                  json={"name": "poc-irrevocable",
                        "expiry": "never",
                        "scopes": ["profile:read"],
                        "audience": "cli"})
tok = r.json()["data"]
pad = lambda s: s + "=" * (-len(s) % 4)
payload = json.loads(base64.urlsafe_b64decode(pad(tok.split(".")[1])))
print(f"  exp claim: {payload.get('exp')}  (None = never expires)")
print(f"  jti: {payload['jti']}")

# 2) Confirm it works.
r = requests.get(f"{TARGET}/api/user", headers={"Authorization": f"Bearer {tok}"})
print(f"  token -> /api/user: HTTP {r.status_code}")

# 3) Failure mode #1 — logout panics on nil ExpiresAt.
r = requests.post(f"{TARGET}/api/auth/logout",
                  headers={"Authorization": f"Bearer {tok}"})
print(f"  logout: HTTP {r.status_code} (500 = Recovery middleware caught the panic)")

# 4) Failure mode #3 — admin delete does not blacklist the JTI.
listed = requests.get(f"{TARGET}/api/access-tokens",
                      headers={"Authorization": f"Bearer {owner}"}).json()["data"]
poc_row = next(t for t in listed if t["name"] == "poc-irrevocable")
r = requests.delete(f"{TARGET}/api/access-tokens/{poc_row['id']}",
                    headers={"Authorization": f"Bearer {owner}"})
print(f"  admin delete: HTTP {r.status_code} {r.text}")

# 5) Token should now be invalid if delete blacklisted. Test it.
r = requests.get(f"{TARGET}/api/user", headers={"Authorization": f"Bearer {tok}"})
print(f"  after delete, token -> /api/user: HTTP {r.status_code}")
print(f"  response body: {r.text[:150]}")

Observed on v4.5.6 in the test container:

exp claim: None  (None = never expires)
jti: 019daf86-6354-7c2d-9ff1-180de87667b3
token -> /api/user: HTTP 200
logout: HTTP 500 (500 = Recovery middleware caught the panic)
admin delete: HTTP 200 {"code":1,"msg":"删除访问令牌成功","data":null}
after delete, token -> /api/user: HTTP 200
response body: {"code":1,"msg":"获取用户信息成功","data":{"id":"019daf76-b5d2-7778-a90a-e943872b2946","username":"owner","email":"owner@test.local","is_admin":true,"is_owner":true,...}}

After the admin "deleted" the token, the same JWT string still returns the owner's profile data. The token stays valid with no path to invalidate it short of rotating JWT_SECRET.

Impact

The "never expire" option is intended for CLI and integration use cases where rotating tokens is expensive. When one of those tokens leaks (configuration file committed to a public repo, developer laptop compromised, log file uploaded by mistake), the admin has no remediation that does not nuke every other user's session.

A compromised token gives the attacker:

  • Perpetual authenticated access at whatever scopes the token holds until the JWT secret is rotated.
  • Admin's "revoke" UI button lies. The token row disappears from the panel but the bearer keeps working. The admin believes they mitigated the incident.
  • Instance-wide blast radius on proper revocation. The only working fix (rotate JWT_SECRET) forces every user to log in again and invalidates every other access token. Security incidents force operators into an all-or-nothing choice.

Precondition: token theft. A stolen token is the standard threat model for any long-lived credential; the point of revocation is that stolen credentials can be invalidated. Ech0 currently has no working path to do that for the "never expire" class.

Recommended Fix

Three coordinated changes, matching the three failure modes:

  1. Replace "never expire" with a very long expiry (for example 10 years) so every token has a finite exp claim. This removes the conditional at jwt.go:103 entirely:
if expiry == model.NEVER_EXPIRY {
    expiry = int64((10 * 365 * 24 * time.Hour).Seconds())
}
claims.ExpiresAt = jwt.NewNumericDate(time.Now().UTC().Add(time.Duration(expiry) * time.Second))
  1. If the "never expire" semantics must be preserved, make logout handle nil ExpiresAt explicitly:
if claims, err := jwtUtil.ParseToken(authHeader[7:]); err == nil && claims.ID != "" {
    var remaining time.Duration
    if claims.ExpiresAt != nil {
        remaining = time.Until(claims.ExpiresAt.Time)
    } else {
        remaining = 365 * 24 * time.Hour
    }
    h.authService.RevokeToken(claims.ID, remaining)
}

And accept non-positive TTLs in RevokeToken by substituting a long default.

  1. Blacklist the JTI when admin deletes an access token:
tok, err := settingService.settingRepository.GetAccessTokenByID(ctx, id)
if err != nil {
    return err
}
if tok.JTI != "" {
    settingService.authRepo.RevokeToken(tok.JTI, 365*24*time.Hour)
}
return settingService.transactor.Run(ctx, func(txCtx context.Context) error {
    return settingService.settingRepository.DeleteAccessTokenByID(txCtx, id)
})

Any two of the three changes close the gap; all three together make the revocation semantics match the admin's mental model.


Found by aisafe.io

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/lin-snow/Ech0"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "1.4.8-0.20260503041146-eab62379c795"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-613",
      "CWE-755"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-05-07T21:34:01Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "## Summary\n\nAccess tokens created with the \"never expire\" option have no `exp` JWT claim. Three independent revocation mechanisms fail for this token type. Logout at `internal/handler/auth/auth.go:154` and `:163` dereferences `claims.ExpiresAt.Time`, panicking on the nil field so the token never hits the blacklist. `RevokeToken` at `internal/repository/auth/auth.go:45-50` skips when `remainTTL \u003c= 0`. The admin\u0027s \"Delete token\" panel action at `internal/service/setting/access_token_service.go:183-185` removes the database record but does not call `RevokeToken` to blacklist the JTI. Once a never-expire token leaks, the JWT stays cryptographically valid until the admin rotates the signing key across the entire instance.\n\n## Details\n\nCreation path at `internal/util/jwt/jwt.go:103-105`:\n\n```go\n// expiry = 0 \u8868\u793a\u6c38\u4e0d\u8fc7\u671f\nif expiry \u003e 0 {\n    claims.ExpiresAt = jwt.NewNumericDate(time.Now().UTC().Add(time.Duration(expiry) * time.Second))\n}\n```\n\nFor `NEVER_EXPIRY`, `expiry = 0` and the conditional skips. The resulting JWT has no `exp` claim. The middleware at `internal/middleware/auth.go` accepts it; the `jwt/v5` parser does not require `exp` by default.\n\nFailure mode 1, logout panic at `internal/handler/auth/auth.go:163`:\n\n```go\n// Refresh-token revocation at line 154 (safe in practice: refresh tokens always have exp).\n// Access-token revocation, same pattern, at line 163 (the bug):\nif claims, err := jwtUtil.ParseToken(authHeader[7:]); err == nil \u0026\u0026 claims.ID != \"\" {\n    remaining := time.Until(claims.ExpiresAt.Time)  // nil deref when ExpiresAt is nil\n    h.authService.RevokeToken(claims.ID, remaining)\n}\n```\n\nFor a never-expire access token, `claims.ExpiresAt` is nil. `claims.ExpiresAt.Time` panics. Gin\u0027s Recovery middleware catches it and returns HTTP 500; the JTI never reaches `RevokeToken`. Line 154 shares the same pattern against refresh tokens, but refresh tokens are always issued with an expiry so the nil dereference does not fire there in practice.\n\nFailure mode 2, `RevokeToken` skip at `internal/repository/auth/auth.go:45-50`:\n\n```go\nfunc (authRepository *AuthRepository) RevokeToken(jti string, remainTTL time.Duration) {\n    if jti == \"\" || remainTTL \u003c= 0 {\n        return\n    }\n    authRepository.cache.SetWithTTL(fmt.Sprintf(\"%s%s\", blacklistPrefix, jti), true, 1, remainTTL)\n}\n```\n\nEven if the logout path were patched to handle nil `ExpiresAt`, a caller computing `remainTTL = 0` would still skip the blacklist write.\n\nFailure mode 3, admin delete at `internal/service/setting/access_token_service.go:183-185`:\n\n```go\nreturn settingService.transactor.Run(ctx, func(txCtx context.Context) error {\n    return settingService.settingRepository.DeleteAccessTokenByID(txCtx, id)\n})\n```\n\nDeletion removes the token\u0027s metadata row from the database. No call to `RevokeToken`, no write to the JTI blacklist. The JWT continues to validate because the signature is still authentic and the middleware does not consult the metadata table.\n\nThe only way to invalidate a compromised never-expire token is to rotate `JWT_SECRET`, which invalidates every token for every user across the whole instance.\n\n## Proof of Concept\n\nDefault install. Admin creates a never-expire access token; its revocation pathways all fail:\n\n```python\nimport requests, base64, json\nTARGET = \"http://localhost:8300\"\n\nowner = requests.post(f\"{TARGET}/api/login\",\n                      json={\"username\": \"owner\", \"password\": \"owner-pw\"}\n                     ).json()[\"data\"][\"access_token\"]\n\n# 1) Create a never-expire access token.\nr = requests.post(f\"{TARGET}/api/access-tokens\",\n                  headers={\"Authorization\": f\"Bearer {owner}\",\n                           \"content-type\": \"application/json\"},\n                  json={\"name\": \"poc-irrevocable\",\n                        \"expiry\": \"never\",\n                        \"scopes\": [\"profile:read\"],\n                        \"audience\": \"cli\"})\ntok = r.json()[\"data\"]\npad = lambda s: s + \"=\" * (-len(s) % 4)\npayload = json.loads(base64.urlsafe_b64decode(pad(tok.split(\".\")[1])))\nprint(f\"  exp claim: {payload.get(\u0027exp\u0027)}  (None = never expires)\")\nprint(f\"  jti: {payload[\u0027jti\u0027]}\")\n\n# 2) Confirm it works.\nr = requests.get(f\"{TARGET}/api/user\", headers={\"Authorization\": f\"Bearer {tok}\"})\nprint(f\"  token -\u003e /api/user: HTTP {r.status_code}\")\n\n# 3) Failure mode #1 \u2014 logout panics on nil ExpiresAt.\nr = requests.post(f\"{TARGET}/api/auth/logout\",\n                  headers={\"Authorization\": f\"Bearer {tok}\"})\nprint(f\"  logout: HTTP {r.status_code} (500 = Recovery middleware caught the panic)\")\n\n# 4) Failure mode #3 \u2014 admin delete does not blacklist the JTI.\nlisted = requests.get(f\"{TARGET}/api/access-tokens\",\n                      headers={\"Authorization\": f\"Bearer {owner}\"}).json()[\"data\"]\npoc_row = next(t for t in listed if t[\"name\"] == \"poc-irrevocable\")\nr = requests.delete(f\"{TARGET}/api/access-tokens/{poc_row[\u0027id\u0027]}\",\n                    headers={\"Authorization\": f\"Bearer {owner}\"})\nprint(f\"  admin delete: HTTP {r.status_code} {r.text}\")\n\n# 5) Token should now be invalid if delete blacklisted. Test it.\nr = requests.get(f\"{TARGET}/api/user\", headers={\"Authorization\": f\"Bearer {tok}\"})\nprint(f\"  after delete, token -\u003e /api/user: HTTP {r.status_code}\")\nprint(f\"  response body: {r.text[:150]}\")\n```\n\nObserved on v4.5.6 in the test container:\n\n```\nexp claim: None  (None = never expires)\njti: 019daf86-6354-7c2d-9ff1-180de87667b3\ntoken -\u003e /api/user: HTTP 200\nlogout: HTTP 500 (500 = Recovery middleware caught the panic)\nadmin delete: HTTP 200 {\"code\":1,\"msg\":\"\u5220\u9664\u8bbf\u95ee\u4ee4\u724c\u6210\u529f\",\"data\":null}\nafter delete, token -\u003e /api/user: HTTP 200\nresponse body: {\"code\":1,\"msg\":\"\u83b7\u53d6\u7528\u6237\u4fe1\u606f\u6210\u529f\",\"data\":{\"id\":\"019daf76-b5d2-7778-a90a-e943872b2946\",\"username\":\"owner\",\"email\":\"owner@test.local\",\"is_admin\":true,\"is_owner\":true,...}}\n```\n\nAfter the admin \"deleted\" the token, the same JWT string still returns the owner\u0027s profile data. The token stays valid with no path to invalidate it short of rotating `JWT_SECRET`.\n\n## Impact\n\nThe \"never expire\" option is intended for CLI and integration use cases where rotating tokens is expensive. When one of those tokens leaks (configuration file committed to a public repo, developer laptop compromised, log file uploaded by mistake), the admin has no remediation that does not nuke every other user\u0027s session.\n\nA compromised token gives the attacker:\n\n- **Perpetual authenticated access** at whatever scopes the token holds until the JWT secret is rotated.\n- **Admin\u0027s \"revoke\" UI button lies.** The token row disappears from the panel but the bearer keeps working. The admin believes they mitigated the incident.\n- **Instance-wide blast radius on proper revocation.** The only working fix (rotate JWT_SECRET) forces every user to log in again and invalidates every other access token. Security incidents force operators into an all-or-nothing choice.\n\nPrecondition: token theft. A stolen token is the standard threat model for any long-lived credential; the point of revocation is that stolen credentials can be invalidated. Ech0 currently has no working path to do that for the \"never expire\" class.\n\n## Recommended Fix\n\nThree coordinated changes, matching the three failure modes:\n\n1. Replace \"never expire\" with a very long expiry (for example 10 years) so every token has a finite `exp` claim. This removes the conditional at `jwt.go:103` entirely:\n\n```go\nif expiry == model.NEVER_EXPIRY {\n    expiry = int64((10 * 365 * 24 * time.Hour).Seconds())\n}\nclaims.ExpiresAt = jwt.NewNumericDate(time.Now().UTC().Add(time.Duration(expiry) * time.Second))\n```\n\n2. If the \"never expire\" semantics must be preserved, make logout handle nil `ExpiresAt` explicitly:\n\n```go\nif claims, err := jwtUtil.ParseToken(authHeader[7:]); err == nil \u0026\u0026 claims.ID != \"\" {\n    var remaining time.Duration\n    if claims.ExpiresAt != nil {\n        remaining = time.Until(claims.ExpiresAt.Time)\n    } else {\n        remaining = 365 * 24 * time.Hour\n    }\n    h.authService.RevokeToken(claims.ID, remaining)\n}\n```\n\nAnd accept non-positive TTLs in `RevokeToken` by substituting a long default.\n\n3. Blacklist the JTI when admin deletes an access token:\n\n```go\ntok, err := settingService.settingRepository.GetAccessTokenByID(ctx, id)\nif err != nil {\n    return err\n}\nif tok.JTI != \"\" {\n    settingService.authRepo.RevokeToken(tok.JTI, 365*24*time.Hour)\n}\nreturn settingService.transactor.Run(ctx, func(txCtx context.Context) error {\n    return settingService.settingRepository.DeleteAccessTokenByID(txCtx, id)\n})\n```\n\nAny two of the three changes close the gap; all three together make the revocation semantics match the admin\u0027s mental model.\n\n---\n*Found by [aisafe.io](https://aisafe.io)*",
  "id": "GHSA-fpw6-hrg5-q5x5",
  "modified": "2026-05-07T21:34:01Z",
  "published": "2026-05-07T21:34:01Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/lin-snow/Ech0/security/advisories/GHSA-fpw6-hrg5-q5x5"
    },
    {
      "type": "WEB",
      "url": "https://github.com/lin-snow/Ech0/commit/eab62379c795c3f4850a9ca938ae3f27d7171541"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/lin-snow/Ech0"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "ech0\u0027s acess tokens with expiry=never cannot be revoked: logout panics, delete does not blacklist JTI"
}

Mitigation
Implementation

Set sessions/credentials expiration date.

No CAPEC attack patterns related to this CWE.