GHSA-gx3v-q759-g323: Gitea: TOTP TOCTOU race on web 2FA paths + missing replay check on Basic-Auth `X-Gitea-OTP` surface
Summary
I'm reporting two related TOTP one-time-use defects in Gitea that survive the CVE-2021-45331 fix. The 2018 fix (PR #3878) introduced the TwoFactor.LastUsedPasscode field and added an in-memory inequality check on the web 2FA login path. That check works correctly in the single-request case, but it leaves two follow-up gaps:
1. A TOCTOU race on the web surfaces (Defect 1). The read-validate-check-save sequence against the two_factor row is not atomic. Two parallel submissions of the same passcode each load their own in-memory copy where LastUsedPasscode still holds the prior value; both pass the inequality check, both authenticate, and both then write the same new value back. Net effect: the same OTP redeems for two independent logged-in sessions.
2. No LastUsedPasscode check at all on the Basic-Auth API surface (Defect 2). services/auth/basic.go calls twofa.ValidateTOTP(...) for X-Gitea-OTP without ever reading or writing LastUsedPasscode. The same six-digit code is replayable for the full totp.Validate acceptance window (~60–90 s with the default Skew=1). This is a clean RFC 6238 §5.2 violation independent of timing, shaped identically to the pre-CVE-2021-45331 behaviour but scoped to the API / Git-over-HTTPS basic-auth path instead of the web form.
Both defects post-date the 2018 fix; neither is referenced in any published Gitea advisory I could find. I'm filing this as a follow-up to CVE-2021-45331, not a duplicate.
Vulnerable code
Defect 1 — TOCTOU race on web 2FA login
routers/web/auth/2fa.go:55-88:
54 id := idSess.(int64)
55 twofa, err := auth.GetTwoFactorByUID(ctx, id) // (A) read row
56 ...
62 ok, err := twofa.ValidateTOTP(form.Passcode) // (B) pure-function RFC 6238 check
...
68 if ok && twofa.LastUsedPasscode != form.Passcode { // (C) check against in-memory copy
...
84 twofa.LastUsedPasscode = form.Passcode // (D) mutate in-memory
85 if err = auth.UpdateTwoFactor(ctx, twofa); err != nil {// (E) UPDATE … AllCols where id=?
Step (E) is plai
Details
Original advisory: https://github.com/advisories/GHSA-gx3v-q759-g323
Exploitation outlook
EPSS (FIRST.org) estimates each CVE’s probability of exploitation in the next 30 days — here is the CSIRTS.com read on those numbers.
- Low exploitation riskCVE-2026-207790.40% 30-day exploitation probability — currently an unlikely target, but scores change as exploit code circulates. Riskier than 33% of all scored CVEs.
Referenced CVEs
| CVE | CSIRTS overview | External |
|---|---|---|
| CVE-2026-20779 | coverage & exploitation status | NVD · CVE.org |
Same CVEs, other sources
How other CERTs, PSIRTs and databases cover the vulnerabilities in this advisory.
More from GitHub Security Advisories
- mediumGHSA-jr6p-8pjj-mfx6: Capsule has an incomplete fix of CVE-2026-22872: TenantResource RawItems and Generators s…2026-07-31
- mediumGHSA-68cj-mvg9-rgm2: Capsule: CapsuleConfiguration NodeMetadata regex fields lack webhook validation, allowing…2026-07-31
- mediumGHSA-ff84-5f28-78qj: re2: Out-of-bounds heap read in `exec`/`test`/`match` via attacker-influenced `lastIndex`…2026-07-31
- mediumGHSA-6hxr-mr5r-9836: re2: Global `String.prototype.match` with an empty-matchable pattern never advances → inf…2026-07-31
- mediumGHSA-x83g-979r-f5fh: Sylius Mollie Plugin has unauthenticated IDOR that leaks order token and customer PII2026-07-31