CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-20897

Unlock Everything: The Gitea LFS IDOR (CVE-2026-20897)

Amit Schendel
Amit Schendel
Senior Security Researcher

Jan 24, 2026·7 min read·66 visits

Executive Summary (TL;DR)

Gitea failed to scope Git LFS lock lookups to the specific repository requesting the deletion. By sending a request to a repository they control, an attacker can supply the Lock ID of a victim's repository. Gitea would verify permissions on the attacker's repo, but fetch and delete the victim's lock from the global database table. This allows for massive workflow disruption and potential binary file corruption.

A critical Insecure Direct Object Reference (IDOR) in Gitea's Git LFS implementation allows authenticated users to delete file locks across any repository on the instance.

The Hook: Binary Wars

Git is amazing at handling text. It merges lines, highlights diffs, and generally makes collaboration possible. But Git is terrible at binary files. Try merging two versions of a 500MB Photoshop file or a compiled game asset. You can't. That's where Git LFS (Large File Storage) comes in.

To prevent two people from editing the same binary file at once (which would result in a 'last save wins' race condition), LFS introduced 'File Locking'. It's the digital equivalent of taking the bathroom key from the gas station counter. Only one person holds the key; everyone else has to wait.

Now, imagine if you walked into that gas station, asked for the key to the bathroom, and the attendant checked your ID, nodded, and then proceeded to remotely unlock the bathroom door at a bank across the street. That is essentially what is happening in CVE-2026-20897.

Gitea, the popular self-hosted Git service, made a classic blunder in database lookup logic. They verified who you were, but they didn't verify where the object you were touching actually lived. The result? A Critical (CVSS 9.1) vulnerability that turns any user with write access into a chaos agent capable of unlocking every file on the server.

The Flaw: The Global Lookup Fallacy

The root cause here is a textbook case of Insecure Direct Object Reference (IDOR), specifically via a 'Global Lookup'. When you build a multi-tenant system (or a multi-repository system like Gitea), nearly every database query needs two WHERE clauses: the ID of the object, and the ID of the parent container (the repository).

In the vulnerable version of Gitea, the developers implemented the API endpoint for deleting an LFS lock. The API route looks something like this:

POST /:owner/:repo/info/lfs/locks/delete/:lock_id

The logic flow seemed sound on the surface:

  1. Authentication: Is the user logged in?
  2. Authorization: Does the user have write access to :repo?
  3. Action: Delete the lock with ID :lock_id.

The fatal flaw was in step 3. The authorization check proved the user owned the repo in the URL. However, the subsequent database query to find the lock ignored the repo entirely. It just asked the database: "Give me the lock with ID 12345." Since database IDs are usually auto-incrementing integers unique across the entire table (not scoped to the repo), lock ID 12345 is globally unique.

If lock 12345 belonged to a private, sensitive repository that the attacker couldn't see, the code didn't care. It fetched the lock object, saw the attacker had permission on the context repository (the one in the URL), and happily deleted the lock belonging to the victim repository.

The Code: The Smoking Gun

Let's look at the Go code responsible for this. The vulnerability resided in models/git/lfs_lock.go. In the function GetLFSLockByID, the code was dangerously simple.

The Vulnerable Code

// models/git/lfs_lock.go (Vulnerable)
 
func GetLFSLockByID(ctx context.Context, id int64) (*LFSLock, error) {
    lock := new(LFSLock)
    // CRITICAL FLAW: Lookup is only by 'id'
    has, err := db.GetEngine(ctx).ID(id).Get(lock)
    if err != nil {
        return nil, err
    }
    // ... returns lock
}

Do you see the missing constraint? It trusts the id implicitly. If I request ID 500, I get the lock object for ID 500, regardless of who owns it. The controller layer then proceeds to delete it.

The Fix

The patch (Commit da036f3) introduces the necessary scoping. They renamed the function to GetLFSLockByIDAndRepo and forced the repository ID into the query.

// models/git/lfs_lock.go (Patched)
 
func GetLFSLockByIDAndRepo(ctx context.Context, id, repoID int64) (*LFSLock, error) {
    lock := new(LFSLock)
    // FIX: Scoped by ID AND repo_id
    has, err := db.GetEngine(ctx).ID(id).And("repo_id = ?", repoID).Get(lock)
    if err != nil {
        return nil, err
    }
    // ...
}

This And("repo_id = ?", repoID) is the difference between a secure application and a critical vulnerability. Now, if an attacker tries to delete lock 12345 via their own repo (Repo ID 99), the database query looks for id=12345 AND repo_id=99. Since lock 12345 actually belongs to Repo ID 1, the query returns nothing, and the exploit fails.

The Exploit: Breaking Locks

Exploiting this requires an authenticated account and write access to any repository. It does not require access to the victim's repository. Here is how an attacker would weaponize this.

The Setup

  1. Attacker: Creates a throwaway repository: attacker/malice-repo.
  2. Target: A high-value repo corp/secret-project uses LFS locks for large assets.

The Attack Chain

First, the attacker needs to guess the Lock ID. Since Gitea uses incremental integers for IDs, this is trivial. If the attacker creates a lock in their own repo and gets ID 5000, they can assume IDs 1 through 4999 exist for other repositories.

The attacker constructs a malicious curl command:

# Target: Lock ID 1337 (belonging to corp/secret-project)
# Context: attacker/malice-repo (where attacker has Admin/Write)
 
curl -X POST "https://gitea.target.com/api/v1/repos/attacker/malice-repo/git/lfs/locks/1337/unlock" \
     -H "Authorization: token <ATTACKER_TOKEN>" \
     -H "Content-Type: application/json" \
     -d '{"force": true}'

What happens internally?

The lock is gone. The developer working on secret-project has no idea their file is now unlocked.

The Impact: Workflow Denial of Service

Why is this a CVSS 9.1? It's not a data leak (Confidentiality is None). You can't steal the code. But the Integrity and Availability impacts are severe.

In environments that rely heavily on LFS (Game Development, ML Engineering, Video Production), file locking is the only thing preventing data corruption. If an attacker writes a script to loop from ID 1 to 100000 and unlocks everything:

  1. Merge Conflicts: Developers will overwrite each other's work on binary files. Since binaries can't be merged, work is lost. Hours of rendering or compiling are wasted.
  2. Silent Corruption: If two people push changes to a locked binary, the state of the repository becomes inconsistent.
  3. Denial of Service: The team cannot trust the locking mechanism. They are forced to stop working or move to manual coordination (email/Slack), grinding productivity to a halt.

The vulnerability has a scope change (S:C) because the vulnerability in the LFS component affects the integrity of data in completely unrelated security contexts (other repositories).

The Fix: Scope Your Queries

If you are running Gitea, check your footer. If it says anything less than 1.25.4, you are vulnerable.

Remediation

  1. Upgrade: Update to Gitea 1.25.4 immediately.
  2. Configuration: If you cannot upgrade, you can disable LFS support in your app.ini by setting LFS_START_SERVER = false, though this will break LFS functionality for your users.

Developer Takeaway

This is a classic lesson in Defense in Depth. Never assume that because a user is authorized to enter a controller method, they are authorized to access the data requested within that method.

Always chain your lookups.

  • Bad: find(id)
  • Good: find(id).where(owner_id: current_user.id)

If your ORM or database layer allows global lookups by primary key, you must manually ensure that the object returned belongs to the security context of the request.

Official Patches

GiteaGitea v1.25.4 Release Notes

Fix Analysis (1)

Technical Appendix

CVSS Score
9.1/ 10
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:H/A:H
EPSS Probability
0.02%
Top 97% most exploited

Affected Systems

Gitea Server < 1.25.4

Affected Versions Detail

Product
Affected Versions
Fixed Version
Gitea
Gitea
<= 1.25.31.25.4
AttributeDetail
CWECWE-639 (Authorization Bypass Through User-Controlled Key)
CVSS9.1 (Critical)
Attack VectorNetwork (Authenticated)
ImpactIntegrity / Availability
Exploit StatusPoC Available (Theoretical)
EPSS Score0.00017

MITRE ATT&CK Mapping

T1222File and Directory Permissions Modification
Defense Evasion
T1499Endpoint Denial of Service
Impact
CWE-639
Authorization Bypass Through User-Controlled Key

The application does not verify that the user-provided ID matches the object associated with the current security context.

Known Exploits & Detection

Internal ResearchThe logic flaw is evident in the source code prior to version 1.25.4. No public exploit script is required to reproduce; standard curl commands suffice.

Vulnerability Timeline

Fix commit merged
2026-01-11
Gitea v1.25.4 Released
2026-01-22
CVE-2026-20897 Assigned
2026-01-22

References & Sources

  • [1]GHSA-393c-qgvj-3xph
  • [2]Gitea Blog: Release 1.25.4

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.

More Reports

•22 minutes ago•CVE-2026-118777
4.8

CVE-2026-118777: Prototype Pollution in Forge HTTP Client Cookie Management

CVE-2026-118777 is a prototype pollution vulnerability (CWE-1321) in the Forge JavaScript cryptography and networking library (distributed on npm as node-forge) prior to version 1.4.1. The flaw resides in the forge.http client subsystem, where incoming HTTP cookie names and paths parsed from Set-Cookie response headers are stored directly into standard JavaScript objects without key validation or property isolation. By returning crafted Set-Cookie headers containing keys such as __proto__ or constructor, a malicious HTTP server can inject arbitrary properties into Object.prototype, attach attributes to the global Object constructor, or delete built-in prototype methods to cause an application-wide denial of service.

Amit Schendel
Amit Schendel
2 views•10 min read
•about 1 hour ago•CVE-2026-118775
3.7

CVE-2026-118775: ASN.1 OID First Arc Truncation and Ambiguity in node-forge

CVE-2026-118775 (GHSA-4642-5mcj-2rqw) is an integer overflow and validation flaw in the ASN.1 serialization module of Forge (node-forge) prior to version 1.4.1. The asn1.oidToDer() function fails to validate the range of the first two arcs of an Object Identifier (OID) and truncates the packed initial subidentifier to a single 8-bit integer. Applications that perform policy checks on untrusted OID strings before encoding them into Distinguished Encoding Rules (DER) format can be tricked into emitting DER bytes that correspond to a completely different, restricted OID.

Amit Schendel
Amit Schendel
3 views•10 min read
•about 2 hours ago•CVE-2026-119194
7.5

CVE-2026-119194: Algorithmic Complexity Denial of Service in pyasn1 Schemaless ASN.1 Decoder

CVE-2026-119194 is an algorithmic complexity vulnerability (CWE-407) in the pyasn1 library for Python, affecting versions 0.3.3 through 0.6.4. The flaw resides in the schemaless decoding routine ConstructedPayloadDecoderBase._decodeComponentsSchemaless used across BER, CER, and DER codecs. By sending an ASN.1 container populated with child components whose tag identifiers are separated by multiples of CPython's integer hash modulus (sys.hash_info.modulus), an unauthenticated attacker can force worst-case hash collisions in an unbounded Python set, degrading decoding performance from O(N) to O(N^2) and causing a Denial of Service (DoS).

Alon Barad
Alon Barad
5 views•9 min read
•about 3 hours ago•CVE-2026-119195
7.5

CVE-2026-119195: Quadratic-Time Denial of Service in pyasn1 BER/CER/DER Decoders

CVE-2026-119195 is an algorithmic complexity vulnerability (CWE-407) in the pyasn1 library for Python, affecting all versions prior to 0.6.5. When decoding constructed strings or indefinite-length ANY types across BER, CER, and certain DER code paths, the payload decoders repeatedly concatenate incoming fragments onto an immutable bytes object using the += operator. Because Python bytes objects cannot be resized in-place, each fragment iteration allocates a new buffer and copies all previously accumulated bytes, resulting in quadratic O(N^2) time and memory copy complexity. An unauthenticated remote attacker can supply a highly fragmented ASN.1 stream to exhaust CPU cycles and stall application worker threads.

Amit Schendel
Amit Schendel
5 views•10 min read
•about 4 hours ago•CVE-2026-119197
7.5

CVE-2026-119197: Quadratic Algorithmic Complexity Denial of Service in pyasn1 REAL Mantissa Decoder

An algorithmic complexity vulnerability (CWE-407) in pyasn1 versions prior to 0.6.5 allows unauthenticated attackers to cause a Denial of Service (DoS) via crafted ASN.1 payloads. The flaw resides in the binary-encoded ASN.1 REAL mantissa deserialization routine within RealPayloadDecoder.valueDecoder. By performing iterative byte-string slicing and repeated bitwise shifts on a growing arbitrary-precision integer, the decoder exhibits quadratic time and memory allocation complexity relative to the mantissa length. Supplying a BER-, CER-, or DER-encoded payload with a large REAL mantissa causes excessive CPU consumption and blocks application worker processes.

Amit Schendel
Amit Schendel
6 views•10 min read
•about 5 hours ago•CVE-2026-119196
7.5

CVE-2026-119196: Quadratic CPU Consumption in pyasn1 Constructed BIT STRING Decoding

A vulnerability classified under CWE-407 (Inefficient Algorithmic Complexity) exists in the pyasn1 library prior to version 0.6.5. When parsing constructed ASN.1 BIT STRING payloads under BER, CER, or schema-driven DER encoding rules, the BitStringPayloadDecoder repeatedly shifts and copies the entire accumulated bit value for each incoming fragment. Processing inputs with a large number of small constructed fragments induces quadratic CPU consumption, allowing unauthenticated remote attackers to cause a denial of service via resource exhaustion during the initial decoding phase.

Alon Barad
Alon Barad
6 views•10 min read