Gogs 命令执行漏洞绕过史
最近看到Gogs命令执行漏洞(CVE-2024-56731)通告,发现Gogs因Git特性出现了很多次命令执行漏洞的修复与绕过,所以一起分析了下。
CVE-2022-0415
影响范围 <0.12.6
调试环境搭建
我一般偏向用Docker搭建漏洞环境,但不像Java应用可以很方便的开启远程调试,Go应用一般需要重新编译和三方工具Delve。
源码版本选择了0.12.5,从Dockerfile看到使用了Make编译Gogs源码,修改Makefile以支持调试:
1 | #LDFLAGS += -X "gogs.io/gogs/internal/conf.BuildCommit=$(shell git rev-parse HEAD)" |
修改Dockerfile以安装Delve:
1 | #FROM alpine:3.14 |
服务启动用了s6-svscan,修改docker/s6/gogs/run以Delve启动应用:
1 | exec gosu "$USER" /app/gogs/gogs web |
构建镜像并启动:
1 | docker build . -t gogs_debug:0.12.5 |
然后用GoLand连接远程调试即可。
漏洞复现与分析
注册登陆并初始化一个仓库:

后端文件系统中会创建一个Git裸仓库(bare repository),即只包含版本控制数据,不包含工作目录。

通过Web界面上传文件涉及两个请求,首先是文件上传,会返回一个uuid:
1 | POST /test/testrepo/upload-file |
1 | 200 OK |
对应后端代码在internal/route/repo/editor.go:514
:

其中的filename用户可控,不过无法传入含/
的路径,只能是基础文件名。(但\
是可以的,说不定在windows下可以进一步利用?)
在internal/db/repo_editor.go:330
中,文件内容会被保存到临时路径,文件名和uuid存入数据库:

第二个请求是文件提交,其中files
就是前一个请求返回的uuid:
1 | POST /test/testrepo/_upload/main/ |
对应代码在internal/route/repo/editor.go:UploadFilePost
,处理完一些请求参数后,会在internal/db/repo_editor.go:UploadRepoFiles
完成文件提交,首先通过uuid获取上传的文件对象:

然后重置并更新本地临时工作目录:

此时文件系统会存在该仓库的临时工作目录:

然后上传的文件会复制进指定目录:

接下来会在该仓库目录执行git add
git commit
git push
命令将变动同步到原裸仓库中。

其中拼接到路径中的TreePath没有经过检查,并且用户可以在请求中设置该值,所以导致用户可以上传任意文件到.git目录,那么就可以覆盖.git/config文件,通过core.fsmonitor执行任意命令(将会在git add
和git commit
时触发):
1 | [core] |
也可以设置core.sshCommand并将url设置为git地址(将会在git push
时触发):
1 | [core] |
漏洞修复
https://github.com/gogs/gogs/commit/0fef3c9082269e9a4e817274942a5d7c50617284
对TreePath也进行了检查:


CVE-2022-1884
影响范围 <0.12.8
漏洞原理
参考 https://huntr.com/bounties/9cd4e7b7-0979-4e5e-9a1c-388b58dea76b ,将TreePath设置为.git.
可以绕过isRepositoryGitPath函数检查。但.git.
仅在Windows下被视为.git
。
漏洞修复
https://github.com/gogs/gogs/pull/6970/files
添加了.git.
:

CVE-2022-1986
影响范围 <=0.12.8
漏洞原理
参考 https://huntr.com/bounties/776e8f29-ff5e-4501-bb9f-0bd335007930 ,编辑文件时设置tree_path为.git/config
可以绕过,不过也仅在Windows下可以,因为在Windows下os.PathSeparator
为\
,但/
可以正常作为路径分隔符使用。
漏洞修复
https://github.com/gogs/gogs/commit/38aff73251cc46ced96dd608dab6190415032a82

CVE-2022-2024
影响范围 < 0.12.11
漏洞原理
参考 https://huntr.com/bounties/18cf9256-23ab-4098-a769-85f8da130f97 ,可以设置tree_path
为/.Git/
以在不区分路径大小写的系统上实现绕过,如Windows和MacOS。
漏洞修复
https://github.com/gogs/gogs/commit/15d0d6a94be0098a8227b6b95bdf2daed105ec41
在isRepositoryGitPath函数中首先进行strings.ToLower(path)
:

CVE-2024-39931
影响范围 <=0.13.0
调试环境搭建
源码版本选择了0.13.0,从Dockerfile看到使用了Task编译Gogs源码,修改Taskfile.yml以支持调试:
1 | tasks: |
修改Dockerfile以安装Delve:
1 | #FROM alpine:3.17 |
修改docker/s6/gogs/run以Delve启动应用:
1 | exec gosu "$USER" /app/gogs/gogs web |
构建镜像并启动:
1 | docker build . -t gogs_debug:0.13.0 |
漏洞复现与分析
新建仓库,并在其中创建一个裸仓库:
1 | git clone http://127.0.0.1:10880/test/testrepo.git |
在Web界面删除文件抓包得到接口,并用来删除.git/HEAD
文件:
1 | POST /test/testrepo/_delete/main/.git/HEAD |
然后再在Web界面新建或上传文件,将会触发git fetch
操作,而此时.git/HEAD
文件已经被删除,git将会检查当前目录是否是有效裸仓库,那么在config中的配置将会生效。具体可以参考 https://www.sonarsource.com/blog/securing-developer-tools-unpatched-code-vulnerabilities-in-gogs-2/
漏洞修复
https://github.com/gogs/gogs/pull/7870/files
删除文件时进行了路径检查:

CVE-2024-54148
影响范围 <0.13.1
漏洞复现与分析
依然使用0.12.5版本,在仓库创建指向.git/config
的软链接:

直接提交修改的话,会提示无法编辑:

代码在internal/route/repo/editor.go:122
,entry.IsSymlink()
检测的是tree_path
的值即新的文件路径:

所以只要修改文件路径就可以通过这里的检测,然后在internal/db/repo_editor.go:UpdateRepoFile
中会将原文件移动到新路径并修改内容:

漏洞修复
https://github.com/gogs/gogs/commit/c94baec9ca923f38c19f0c7c5af722b9ec04022a
对原文件也进行了检测:

CVE-2024-55947
影响范围 <0.13.1
漏洞复现与分析
参考 https://github.com/gogs/gogs/security/advisories/GHSA-qf5v-rp47-55gg ,访问 http://127.0.0.1:10880/user/settings/applications 生成令牌,然后使用以下接口即可目录穿越写入任意文件:
1 | curl --path-as-is -X PUT --url "http://localhost:10880/api/v1/repos/test/testrepo/contents/../../../../../../../../home/git/.ssh/authorized_keys" \ |
源码0.13.0,代码位置在internal/route/api/v1/repo/contents.go:PutContents
:
treePath
传入了UpdateRepoFile
:

在UpdateRepoFile
中会直接使用该路径并写入内容:

漏洞修复
对路径进行了处理防止目录穿越:

CVE-2024-56731
漏洞复现与分析
参考 https://github.com/gogs/gogs/security/advisories/GHSA-wj44-9vcg-wjq7 ,其实是对CVE-2024-39931的绕过,CVE-2024-39931对删除文件的路径进行了检查,防止删除.git目录下的文件,但是可以通过创建.git目录的软链接绕过检查。
新建仓库,并在其中创建一个裸仓库,同时创建.git目录的软链接:
1 | git clone http://127.0.0.1:10880/test/testrepo.git |
在Web界面删除文件抓包得到接口,尝试删除.git/HEAD
文件会发现失败:
1 | POST /test/testrepo/_delete/main/.git/HEAD |

通过软链接绕过:
1 | POST /test/testrepo/_delete/main/gitdir/HEAD |
漏洞修复
测试发现最新版本0.13.3也还未修复。