GHSA-5c3f-6486-3g7g: Gogs's password-reset tokens use account-activation lifetime, ignoring RESET_PASSWORD_CODE_LIVES
SummaryPassword-reset tokens are generated using `conf.Auth.ActivateCodeLives` (the account-activation lifetime), not `conf.Auth.ResetPasswordCodeLives`. The token lifetime is baked into the token itself at generation time and is re-extracted from the token at verification time, making `RESET_PASSWORD_CODE_LIVES` irrelevant to actual enforcement. When an administrator configures a shorter reset window (e.g., 10 minutes) for compliance or security reasons, reset tokens remain exploitable for the full activation lifetime instead, while the reset email falsely advertises the shorter expiry.Severity**Medium** (CVSS 3.1: 6.8)`CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N`**Attack Vector:** Network — the reset endpoint is reachable over HTTP/S.**Attack Complexity:** High — successful exploitation requires (1) the instance to be configured with `RESET_PASSWORD_CODE_LIVES < ACTIVATE_CODE_LIVES`, AND (2) the attacker to have intercepted the victim's reset token (e.g., from a compromised or shared email inbox).**Privileges Required:** None — no Gogs account is required.**User Interaction:** Required — the victim must have triggered a password-reset request.**Scope:** Unchanged — the impact is confined to the victim's Gogs account.**Confidentiality Impact:** High — successful exploitation leads to account takeover, exposing all private repositories and data.**Integrity Impact:** High — the attacker can change the victim's password and gain full write access.**Availability Impact:** None.Affected component`internal/userx/userx.go` — `GenerateActivateCode()` (line 39)`internal/email/email.go` — `SendResetPasswordMail()` (line 132)`internal/route/user/auth.go` — `verifyUserActiveCode()` (lines 426–439) and `ResetPasswdPost()` (line 621)CWE**CWE-324**: Use of a Key Past Its Expiration Date**CWE-613**: Insufficient Session ExpirationDescriptionThe reset token lifetime is hardcoded to `ActivateCodeLives` at generation`GenerateActivateCode` (called for both account activation and password reset) bakes `conf.Auth.ActivateCodeLives` — not `ResetPasswordCodeLives` — into the token as a 6-digit field:`CreateTimeLimitCode` embeds the `minutes` value at positions 12–17 of the token:`SendResetPasswordMail` calls `u.GenerateEmailActivateCode(u.Email())` — which resolves to `GenerateActivateCode` — with no option to pass a different lifetime:`ResetPasswordCodeLives` is used only for display, not enforcement`VerifyTimeLimitCode` discards the `minutes` argument and re-extracts the lifetime directly from the token itself:The `verifyUserActiveCode` caller passes `conf.Auth.ActivateCodeLives` as `minutes`, but it makes no difference:`ResetPasswdPost` validates the reset token through `verifyUserActiveCode`, so it inherits the same flaw:`ResetPasswordCodeLives` appears only in email template data and in the admin config display — it has zero effect on actual token validation:Full execution chain**Victim requests reset**: `POST /user/forget_password` → `SendResetPasswordMail` generates a token embedding `ActivateCodeLives = 180` at bytes 12–17.**Email delivered**: The reset email says "link valid for 10 minutes" (from `ResetPwdCodeLives` in the template) but the embedded lifetime is 180.**`RESET_PASSWORD_CODE_LIVES` window closes**: After 10 minutes the victim believes the link has expired.**Attacker submits the token**: `POST /user/reset_password?code=<TOKEN>` → `ResetPasswdPost` → `verifyUserActiveCode` → `VerifyTimeLimitCode` extracts `000180` from the token → confirms the token has not yet reached the 180-minute mark → returns the user object → password is updated.**Account takeover**: Attacker sets a new password and authenticates as the victim.Proof of ConceptImpactAn administrator who sets `RESET_PASSWORD_CODE_LIVES` shorter than `ACTIVATE_CODE_LIVES` to limit the window of exposure for intercepted reset emails gets no security benefit from that configuration.Reset tokens remain valid for the full activation lifetime (default 3 hours), giving an attacker who has intercepted a reset email a much larger window to use it.The reset email actively misleads users by advertising a shorter expiry that is never enforced.All password-reset operations are affected; there is no per-user or per-request way to issue a correctly-expiring token.Recommended remediationOption 1: Add a `ResetPasswordCodeLives`-aware generation function (preferred)Introduce a dedicated code-generation path that passes `conf.Auth.ResetPasswordCodeLives` instead of `ActivateCodeLives`:Update `email.User` to expose this through the interface:Update `SendResetPasswordMail` to call it:Because `VerifyTimeLimitCode` reads the lifetime from the token itself, no change to the verification side is required — tokens generated with `ResetPasswordCodeLives` will automatically expire at the correct time.Option 2: Validate the extracted lifetime against the configured maximumAdd a post-extraction check in `VerifyTimeLimitCode` or in the reset-specific verification function to reject tokens whose embedded lifetime exceeds `ResetPasswordCodeLives`:This is a defence-in-depth measure but does not fix the root cause; Option 1 is preferred.CreditThis vulnerability was discovered and reported by [bugbunny.ai](https://bugbunny.ai).
CVSS v3.1: 6.8