Gogs RCE: When 'Fixed' Just Means 'Try Harder' (CVE-2025-8110)
Jan 15, 2026·7 min read
Executive Summary (TL;DR)
Gogs versions <= 0.13.3 contain a logic flaw in how they handle symbolic links during file updates. An authenticated attacker can push a symlink to a repository and then use the file update API to write data through that link, overwriting sensitive system files like `/etc/passwd`. This results in full Remote Code Execution. The vulnerability is actively exploited in the wild.
A critical remote code execution vulnerability in Gogs, a popular self-hosted Git service. An incomplete patch for a previous symlink vulnerability allowed attackers to bypass checks by nesting malicious paths, permitting arbitrary file overwrites on the host system via the repository API.
The Hook: Go(gs)ing, Going, Gone
Gogs (Go Git Service) is the darling of the self-hosted developer world. It is lightweight, written in Go, and easy to spin up on a Raspberry Pi or a corporate ec2 instance. Because it hosts source code, it is already a goldmine for attackers looking for credentials or proprietary logic. But what makes CVE-2025-8110 truly spicy is that it is a 'zombie' vulnerability—a bug that was supposedly killed, only to rise from the grave because the double-tap wasn't thorough enough.
In the world of memory-safe languages like Go, we don't usually see buffer overflows or use-after-frees. Instead, we see logic errors. Specifically, we see developers wrestling with the filesystem, which is arguably the most treacherous API in any operating system. The Gogs team had already battled path traversal issues (CVE-2024-55947), patching a hole where users could upload symlinks. They thought they had closed the door.
Unfortunately, while they locked the front door, they left the cat flap wide open. This vulnerability exploits the PutContents API, a feature designed to let you edit files in the browser. It turns out that if you ask the API nicely, it will happily write files to locations that shouldn't exist in a Git repo, like your server's SSH keys or password file. This isn't just a data leak; it is a full-blown hostile takeover.
The Flaw: Focusing on the Leaf, Ignoring the Branch
To understand this bug, you have to understand the previous fix. Gogs tried to stop people from writing to symlinks. Their logic was simple: before writing to a file path provided by the user (e.g., repo/README.md), check if README.md is a symlink. If it is, abort. This sounds reasonable on paper, but filesystems are hierarchical, and hierarchies are where security checks go to die.
The flaw in CVE-2025-8110 is a classic case of 'Time of Check to Time of Use' (TOCTOU) mixed with a shallow validation scope. The application validated the destination file (the leaf node), but it completely ignored the path components leading up to it. It assumed that if the final filename wasn't a symlink, the write was safe.
Here is the logic gap: If I ask Gogs to write to my_dir/secret.txt, Gogs checks if secret.txt is a link. It isn't. It's a new file I want to create. But what Gogs failed to check was my_dir. If my_dir is actually a symbolic link pointing to /etc/, then writing to my_dir/secret.txt actually writes to /etc/secret.txt. The operating system resolves the path transparently, and the application has no idea it just nuked a system config file. It is a directory traversal attack that doesn't need ../../ characters; it just needs a symlink and a naive API.
The Code: The Smoking Gun
The vulnerability lived in internal/database/repo_editor.go, specifically in functions like UpdateRepoFile. The original code relied on a check that looked something like osutil.IsSymlink(filePath). This function only looked at the exact path string passed to it. It did not walk the directory tree.
The fix, introduced in commit 553707f3fd5f68f47f531cfcff56aa3ec294c6f6, is a testament to how annoying secure file handling actually is. You cannot just check the file; you have to check every single segment of the path. Here is the breakdown of the patch:
// The Fix: Recursive Path Validation
func hasSymlinkInPath(base, relPath string) bool {
parts := strings.Split(filepath.ToSlash(relPath), "/")
for i := range parts {
// Construct path segment by segment:
// 1. base/part1
// 2. base/part1/part2
filePath := path.Join(append([]string{base}, parts[:i+1]...)...)
// Check if this specific segment is a symlink
if osutil.IsSymlink(filePath) {
return true
}
}
return false
}Before this patch, the code essentially trusted that base/part1 was a real directory. Now, Gogs iterates through every slash-separated component. If relPath is evil_link/payload, the loop first checks evil_link. When it sees that evil_link is a symlink, it bails out immediately, preventing the OS from resolving the path to a sensitive location. It is a brute-force approach, but when dealing with filesystem ambiguity, it is the only safe way.
The Exploit: Tunneling Through the API
Exploiting this requires authenticated access, but given that Gogs is often used by teams, getting a low-privileged account is usually trivial (or the instance might allow public registration). Once you have a user, the attack chain is elegant in its simplicity.
Step 1: The Setup First, the attacker creates a standard Git repository. On their local machine, they create a symbolic link. Let's say we want to target the Linux password file.
ln -s /etc/ nasty_link
git add nasty_link
git commit -m "Nothing to see here"
git pushAt this point, the Gogs server stores nasty_link as a symlink inside its internal repo storage. It points to /etc/ on the server's filesystem.
Step 2: The Trigger
The attacker cannot simply edit nasty_link because the previous patch prevents writing to a symlink. Instead, the attacker uses the Gogs Web API (or the web interface's "New File" button) to create a file inside the directory structure that nasty_link pretends to be.
The attacker sends a POST request to UpdateRepoFile aiming at nasty_link/passwd.
Step 3: The Execution
Gogs receives the request. It constructs the path: /app/gogs-repositories/user/repo.git/nasty_link/passwd. It checks: "Is passwd a symlink?" No, it doesn't exist yet. "Okay, write the file!"
The underlying OS call os.WriteFile takes over. It sees nasty_link, resolves it to /etc/, and writes the payload to /etc/passwd. The attacker has now overwritten the users file, likely adding a root user with a known password, or perhaps dropping an SSH key into /home/git/.ssh/authorized_keys.
The Impact: From Git Push to Root Shell
The impact here is catastrophic for the host server. This is not just about messing with Git repos; it is about escaping the application sandbox. Because Gogs often runs as a specific user (e.g., git), the attacker immediately inherits that user's permissions.
If Gogs is running inside a Docker container, the damage might be contained to the container's filesystem—still bad, as the attacker can steal source code, inject backdoors into other projects, or use the container as a pivot point for the internal network.
However, if Gogs is installed directly on a server (common in "bare metal" deployments) and the git user has write access to sensitive paths (or worse, if Gogs is running as root, which happens more often than we'd like to admit), the server is toast. The attacker can overwrite authorized_keys to gain immediate SSH access, modify cron jobs for persistence, or inject malicious binaries. The fact that this vulnerability is already in the CISA KEV catalog means threat actors are actively scanning for this. It is weaponized.
The Fix: Check Your Roots (And Your Branches)
The remediation is straightforward but urgent: Update to Gogs version 0.13.4 immediately. This version includes the recursive path validation that kills the exploit chain.
If you cannot patch immediately, you are in a tight spot. Disabling the web editor interface might help, but since this can be triggered via API endpoints, you would essentially need to block access to the /api/v1/repos/*/file/* endpoints at your reverse proxy (Nginx/Apache).
For the blue teamers: Check your Gogs repositories for suspicious symlinks. A simple find . -type l inside your repositories directory might reveal links pointing to root directories (/, /etc, /root). Also, monitor your logs for PutContents calls that seem to reference oddly named directories that match recent commits. If you see a commit adding a symlink followed immediately by an API call writing 'through' it, you have been hit.
Official Patches
Fix Analysis (1)
Technical Appendix
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:HAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
Gogs Gogs | <= 0.13.3 | 0.13.4 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-22 (Improper Limitation of a Pathname to a Restricted Directory) |
| CVSS v3.1 | 8.8 (High) |
| Attack Vector | Network (Authenticated) |
| EPSS Score | 0.95% |
| Exploit Status | Active Exploitation (CISA KEV) |
| Patch Commit | 553707f3fd5f68f47f531cfcff56aa3ec294c6f6 |
MITRE ATT&CK Mapping
The software uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the software does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
Known Exploits & Detection
Vulnerability Timeline
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.