Jul 22, 2026·6 min read·1 visit
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 critical validation bypass exists in FasterXML jackson-core due to an incomplete fix for GHSA-72hv-8253-57qq. When parsing JSON asynchronously using NonBlockingUtf8JsonParserBase, the StreamReadConstraints.maxNumberLength constraint is bypassed when numeric inputs are received in small, non-terminating chunks. The parser continually accumulates digit-only input in an internal buffer without triggering validation constraints, resulting in potential heap memory exhaustion and application-level Denial of Service.
A critical second-order code injection vulnerability exists in the migration generation engine of TypeORM. When TypeORM introspects a database schema to automatically generate migration files, it writes schema metadata directly into JavaScript/TypeScript migration files inside ES2015 template literals. Because the generator failed to sanitize template literal string interpolation markers and backslashes, attackers with control over database metadata can execute arbitrary code on the developer environment or within a CI/CD pipeline.
A security logic flaw in Loofah, a Ruby HTML/XML sanitization library, allowed unauthenticated attackers to bypass local-reference restrictions on SVG elements. Prior to version 2.25.2, Loofah only sanitized the legacy namespaced "xlink:href" attribute when enforcing local URI fragments on SVG elements like "<use>". It did not validate or sanitize the plain, non-namespaced "href" attribute introduced in the SVG 2 standard. This structural omission allowed attackers to construct SVG elements referencing external domains, paving the way for arbitrary resource loading and cross-site scripting (XSS).
A critical HTML sanitization bypass vulnerability in the Ruby library Loofah allows unauthenticated remote attackers to execute Stored Cross-Site Scripting (XSS) attacks. By using semicolon-less numeric character references (NCRs) to encode protocol characters, attackers can evade backend URI verification filters while ensuring the malicious protocol is successfully decoded and executed by client-side web browsers.
GHSA-HRXH-6V49-42GF is a critical security advisory addressing two distinct vulnerabilities within gRPC-Go: an HTTP/2 Control Buffer Flooding weakness that allows unauthenticated denial of service, and xDS Role-Based Access Control parser weaknesses that lead to authorization bypasses and application panics.
An unauthenticated memory-leak Denial of Service (DoS) vulnerability exists in the Node.js Adapter for Hono (@hono/node-server) during the WebSocket handshake upgrade process. If a client initiates a WebSocket upgrade but the process is aborted or fails, resources are permanently retained in memory, leading to heap exhaustion.