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 ). 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 oldFilePath := path . Join ( localPath , opts . OldTreeName ) filePath := path . Join ( localPath , opts . NewTreeName ) if err = os . MkdirAll ( path . Dir ( filePath ), os . ModePerm ); err != nil { return err } // If it's meant to be a new file, make sure it doesn't exist. if opts . IsNewFile { if com . IsExist ( filePath ) { return ErrRepoFileAlreadyExist { filePath } } } // Ignore move step if it's a new file under a directory. // Otherwise, move the file when name changed. if osutil . IsFile ( oldFilePath ) && opts . OldTreeName != opts . NewTreeName { if err = git . Move ( localPath , opts . OldTreeName , opts . NewTreeName ); err != nil { return fmt . Errorf ( "git mv %q %q: %v" , opts . OldTreeName , opts . NewTreeName , err ) } } if err = os . WriteFile ( filePath , [] byte ( opts . Content ), 0600 ); err != nil { return fmt . Errorf ( "write file: %v" , err ) } if err = git . Add ( localPath , git . AddOptions { All : true }); err != nil { return fmt . Errorf ( "git add --all: %v" , err ) } err = git . CreateCommit ( localPath , & git . Signature { Name : doer . DisplayName (), Email : doer . Email , When : time . Now (), }, opts . Message , ) 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 ). 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 var newTreePath string for index , part := range treeNames { newTreePath = path . Join ( newTreePath , part ) entry , err := c . Repo . Commit . TreeEntry ( newTreePath ) if err != nil { if gitutil . IsErrRevisionNotExist ( err ) { // Means there is no item with that name, so we're good break } c . Error ( err , "get tree entry" ) return } if index != len ( treeNames ) - 1 { if ! entry . IsTree () { c . FormErr ( "TreePath" ) c . RenderWithErr ( c . Tr ( "repo.editor.directory_is_a_file" , part ), tmplEdit
Gogs contains a symbolic link path traversal vulnerability (CVE-2024-4