The Gogs self-hosted Git service is vulnerable to symbolic link path traversal that enables remote code execution (CVE-2024-44625). The latest version at the time of writing (0.13.0) is affected. This vulnerability is exploitable against a default install, with the only attacker requirement being access to an account that can push to a repository and edit that repository’s files from the web interface.
Per Gogs’ SECURITY.md
, I reported this issue to the maintainers as a GitHub advisory on August 10, 2024. Though I followed up multiple times, my report was never acknowledged and remains unaddressed at the time of writing. My experience here was not an anomaly; there is currently an open issue tracking several other high and critical vulnerabilities left unpatched due to a lack of response from the Gogs developers.
As the 90-day disclosure deadline I gave in my report has now passed, I am publishing the full technical details of CVE-2024-44625, as well as a proof-of-concept exploit. I advise anyone running Gogs to close off access to the public and untrusted users, disable user registration, set strong passwords and enable 2FA for existing accounts, and migrate to Gitea, which is an actively maintained fork of Gogs not affected by this vulnerability.
The vulnerability
Gogs’ web editor allows the user to modify and rename repository files directly from the web interface. When the user submits their edits, the frontend issues a POST request to the /:username/:reponame/_edit/:branch/:filepath
endpoint. The request is handled by the editFilePost
function, which validates/sanitizes the request parameters and eventually calls the UpdateRepoFile
function. UpdateRepoFile
then performs the requested changes as a series of direct filesystem operations on a clone of the repository and commits them.
Below is a snippet from UpdateRepoFile
that shows the core logic for modifying and renaming a file (internal/database/repo_editor.go#L159-L196
).
|
|
Because the code writes to client-controlled file paths (via a call to os.WriteFile
, highlighted above), care must be taken to prevent modifications to files outside of the clone directory. The classic example of this is path traversal, in which we might submit a path of ../../../foo
to write to a file other than the intended destination. Part of the aforementioned parameter sanitization in editFilePost
prevents this basic kind of traversal.
However, there is another type of vulnerability that can have the same effect as traditional path traversal: symbolic link following. If we added a symlink to /tmp/foo
to a repository, pushed it to Gogs, and modified the link’s contents using the web editor, we would have actually modified the contents of /tmp/foo
. Note that this potential exists because os.WriteFile
opens the file without specifying the O_NOFOLLOW
flag, causing UpdateRepoFile
to follow any symlinks present in the repository.
The developers anticipated this possibility and added a guard against it in editFilePost
, shown below (internal/route/repo/editor.go#L175-L198).
|
|
Here, treeNames
is a slice containing the components of the new path (in case it is being renamed) of the file being edited. The code iterates over these components, appending the current component to newTreePath
and checking the Git tree entry at the value of newTreePath
. If the entry is a symlink, the request is rejected and an error message is displayed to the user (highlighted above). For example, given the path foo/bar/baz
, if any one of foo
, foo/bar
, or foo/bar/baz
is a symlink, editFilePost
rejects the request.
However, notice the opportunity on lines 179-182 to break from the loop before the symlink check is performed. If newTreePath
is a path that is not already part of the repository (that is, a nonexistent file), the loop ends early, skipping over the symlink check. The consequence is that editing a symlink’s contents while simultaneously moving it to a path that does not already exist allows us to modify arbitrary files on the system.
With this primitive in hand, we can achieve code execution by taking advantage of server-side Git hooks. These are scripts that live in the repository’s hooks
directory on the server, executed at various points before and after a push to the server. We can create a symlink to one of these hook files, such as pre-receive
, and overwrite its contents with a command of our choice. The hook and our command will then automatically execute without any extra work on our part when Gogs updates the repository with the new symlink contents.
Exploit
A proof-of-concept exploit script can be found here.