Jul 22, 2026·6 min read·25 visits
A high-severity authentication bypass in Gitea allows attackers to replay TOTP codes across web and API paths due to database race conditions and missing state tracking.
Gitea versions from 1.5.0 before 1.26.3 contain a security vulnerability in their multi-factor authentication (MFA) logic. This vulnerability allows valid TOTP codes to be accepted multiple times across web authentication flows and the Basic Auth X-Gitea-OTP header path. Due to a TOCTOU race condition and a lack of state tracking in programmatic auth pathways, attackers with valid credentials can replay single-use OTP codes.
Gitea is a widely used open-source, self-hosted Git service written in Go. The platform exposes a variety of attack surfaces, including web-based user interfaces, REST APIs, and programmatic endpoints for Git-over-HTTPS. To protect secure code repositories and administrative operations, Gitea incorporates two-factor authentication (2FA) utilizing Time-based One-Time Passwords (TOTP) as a secondary validation boundary for user credentials.\n\nCVE-2026-20779 identifies a high-severity security vulnerability in Gitea's TOTP validation flow. This security defect is categorized as CWE-294: Bypass of Authentication Mechanism Using Replay. The weakness permits an attacker who has acquired valid primary user credentials (username and password) to replay a single, mathematically valid TOTP code multiple times within its temporal validity window. This bypass nullifies the single-use guarantee fundamental to multi-factor authentication systems.\n\nThe vulnerability manifests in two primary areas: the web-based authentication endpoints and the HTTP Basic Authentication path. Both programmatic and user-facing entry points are affected, leaving organizations exposed to unauthorized session establishment and repository compromise. This technical analysis provides an in-depth examination of the root causes, patch architecture, exploitation methods, and verification processes.
The root cause of CVE-2026-20779 lies in two distinct implementation flaws within Gitea's multi-factor authentication design. The first flaw is a Time-of-Check to Time-of-Use (TOCTOU) race condition in the web authentication endpoints located in routers/web/auth/2fa.go and routers/web/auth/password.go. The validation process executed by the server relies on a non-atomic read-validate-write sequence on the database.\n\nWhen a user submits a TOTP passcode during authentication, Gitea reads the user's TwoFactor configuration from the database, performs an in-memory mathematical check, and evaluates if the submitted passcode is unequal to the LastUsedPasscode property. If these checks pass, Gitea writes the updated passcode back to the database. Because this sequence lacks synchronization, concurrency controls, or row-level database locks, parallel threads can read the database state before any single write has finalized. This allows multiple concurrent requests to authenticate successfully with the same passcode.\n\nThe second flaw is located in the programmatic authentication code path within services/auth/basic.go, which handles Basic Auth requests using the X-Gitea-OTP HTTP header. This path mathematically verified the passcode against the TOTP secret but completely failed to query, validate, or update the LastUsedPasscode database column. This rendered the API TOTP validation completely stateless. An attacker could reuse a single intercepted passcode continuously for any API or Git-over-HTTPS operation until the passcode expired.
To address these issues, the development team merged Pull Request #38151, applying a Compare-and-Swap (CAS) database-level lock. This patch introduced the ValidateAndConsumeTOTP method in models/auth/twofactor.go. This helper encapsulates both the mathematical check and the persistence of the used passcode within a single atomic database operation.\n\nIn the patched implementation, the application relies on the database engine to serialize write operations on the same row. By executing a conditional UPDATE statement that checks whether the stored passcode is unequal to the incoming passcode, the application prevents concurrent duplicates. If a parallel request attempts to write the same passcode, the database modifies zero rows and the application rejects the attempt.\n\ngo\nfunc (t *TwoFactor) ValidateAndConsumeTOTP(ctx context.Context, passcode string) (bool, error) {\n\tok, err := t.validateTOTP(passcode)\n\tif err != nil || !ok {\n\t\treturn false, err\n\t}\n\tt.LastUsedPasscode = passcode\n\tn, err := db.GetEngine(ctx).ID(t.ID).\n\t\tWhere(builder.Or(builder.IsNull{\"last_used_passcode\"}, builder.Neq{\"last_used_passcode\": passcode})).\n\t\tCols(\"last_used_passcode\").Update(t)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn n == 1, nil\n}\n\n\nThe following sequence diagram illustrates the serialized database transaction introduced by the patch:\n\nmermaid\ngraph LR\n A[\"Client Request\"] --> B[\"Validate TOTP Math\"]\n B --> C{\"Update Database?\"}\n C -- \"where last_used_passcode != input\" --> D[\"Update Row\"]\n D --> E[\"Success (1 Row Modified)\"]\n C -- \"where last_used_passcode == input\" --> F[\"Reject (0 Rows Modified)\"]\n
Exploitation of CVE-2026-20779 requires an attacker to satisfy several prerequisites. The attacker must first obtain the primary authentication credentials (username and password) of the target account. Furthermore, the attacker must have network-level access to the Gitea server and must intercept a valid TOTP token generated by the target user. The temporal window for exploitation corresponds to the 30-to-90-second validity period of the target token.\n\nTo exploit the TOCTOU race condition on the web flow, the attacker issues concurrent HTTP POST requests to /user/two_factor containing the identical TOTP passcode. When timed correctly, multiple threads read the same historical database record, bypass the validation checks, and generate separate active session cookies. This permits the attacker to establish a valid session parallel to the legitimate user.\n\nTo exploit the stateless programmatic path, the attacker targets endpoints such as /api/v1/user or Git-over-HTTPS. By passing the credentials via HTTP Basic Authentication and appending the single intercepted passcode to the X-Gitea-OTP HTTP header, the attacker can execute repeated requests. The attacker can make numerous API calls or generate persistent API keys using the single TOTP passcode before the time-step window expires.
Although the Compare-and-Swap (CAS) update prevents parallel TOCTOU race conditions and simple stateless replays, a theoretical weakness persists due to Gitea's tracking design. RFC 6238 §5.2 mandates that validators must not accept any one-time passwords generated for a time-step prior to or equal to the last validated step. Rather than maintaining a monotonic counter of the validated step (LastTotpStep), Gitea only records the literal string value of the last-used passcode.\n\nThis design decision permits an alternation bypass vector if an attacker captures two distinct valid passcodes from adjacent time-steps (such as passcode $P_1$ generated at step $T-1$, and passcode $P_2$ generated at step $T$). Because both passcodes are mathematically valid within the server's configured clock-skew window, the attacker can submit them sequentially.\n\nFirst, the attacker submits $P_2$, which Gitea accepts and sets last_used_passcode to $P_2$. Second, the attacker submits $P_1$. Since $T-1$ falls within the clock-skew tolerance, the mathematical check succeeds. The database checks if the submitted passcode ($P_1$) is unequal to the stored passcode ($P_2$). Since $P_1 \neq P_2$, the condition passes, and the application sets last_used_passcode to $P_1$. Third, the attacker resubmits $P_2$, satisfying the inequality $P_2 \neq P_1$. This alternating replay allows reuse of both tokens multiple times until the temporal window of the earlier step expires. Complete mitigation requires Gitea to record and validate monotonic time-steps instead of string-based comparisons.
The security impact of CVE-2026-20779 is classified as High, with a CVSS v3.1 score of 7.1. The attack vector is Network (AV:N), the complexity is Low (AC:L), and privileges are None (PR:N). However, User Interaction is Required (UI:R) because the attacker must capture a valid TOTP passcode generated by the victim.\n\nA successful compromise allows an attacker to bypass multi-factor authentication barriers completely. This leads to unauthorized access to the victim's repositories, including proprietary source code, secrets, and database credentials. Furthermore, attackers can perform administrative actions or generate persistent API tokens that survive beyond the life of the hijacked session.\n\nThe vulnerability does not directly impact the availability of the host platform. However, the integrity and confidentiality of the hosted data are severely compromised. Because this flaw is logical rather than memory-based, it provides a stable and predictable vector for authentication bypass without risking server instability.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:N| Attribute | Detail |
|---|---|
| CWE ID | CWE-294 (Bypass of Authentication Mechanism Using Replay) |
| Attack Vector | Network (AV:N) |
| CVSS v3.1 Score | 7.1 (High) |
| EPSS Score | 0.00481 (Percentile: 38.44%) |
| Exploit Status | Proof of Concept (PoC) available |
| CISA KEV Status | Not Listed |
| Affected Versions | v1.5.0 to < v1.26.3 |
A path traversal vulnerability (CWE-22) in Contao CMS allows unauthenticated remote attackers to bypass directory boundary restrictions in ImagesController and access files within the project directory.
Contao Open Source CMS versions 4.0.0 through 5.3.49 and 5.4.0-RC1 through 5.7.11 contain a Cross-Site Request Forgery (CSRF) vulnerability in backend parameter handling. The `RequestTokenListener` component validates anti-CSRF tokens solely for HTTP POST requests, while GET-based declarative guards run only when an `act` parameter is present in the query string. Consequently, custom backend actions dispatched via alternative parameters such as `key=` can execute without CSRF token verification when triggered by an authenticated user.
Contao CMS versions 4.1.0 through 5.3.49 and 5.4.0-RC1 through 5.7.11 fail to validate form submission tokens and enforce rate limiting when processing activation email resend requests via HTTP POST, enabling resource exhaustion and account state enumeration.
An information disclosure vulnerability in Contao CMS allows unauthenticated site visitors to view protected page titles, URLs, and text excerpts through search queries when protected page indexing is disabled after previously being enabled.
In Vikunja prior to version 2.6.0, relation creation via the CalDAV endpoint fails to invoke the TaskRelation.CanCreate authorization check. This missing access control allows an authenticated user to establish unauthorized relationships and perform write operations against any task, provided its unique identifier (UID) is known.
A cross-project information disclosure vulnerability in Vikunja allows authenticated users with read access to one project to view private task details from unauthorized projects via subtask expansion parameters.