漏洞介绍
GitLab是一款Ruby开发的Git项目管理平台。在8.9版本后添加的“导出、导入项目”功能,因为没有处理好压缩包中的软连接,已登录用户可以利用这个功能读取服务器上的任意文件。
漏洞范围
GitLab CE/EE 8.9
GitLab CE/EE 8.10
GitLab CE/EE 8.11
GitLab CE/EE 8.12
GitLab CE/EE 8.13
漏洞靶场
使用vulhub的靶场:
使用环境为GitLab 8.13.1
vulhub-master/gitlab/CVE-2016-9086/
启动靶场环境:
1 | docker-compose up -d |
环境运行后,访问http://your-ip:8080即可查看GitLab主页,其ssh端口为10022,默认管理员账号、密码是root、vulhub123456。
漏洞原理
从8.9.0版本开始,GitLab新增了导入导出项目的功能。
一个空的gitlab项目导出后结构如下:

其中VERSION文件内容为GitLab的导出模块的版本,project.json则包含了项目的配置文件。
当我们导入GitLab的导出文件的时候,GitLab会按照如下步骤处理: 1.服务器根据VERSION文件内容检测导出文件版本,如果版本符合,则导入。 2.服务器根据Project.json文件创建一个新的项目,并将对应的项目文件拷贝到服务器上对应的位置。
检测VERSION文件的代码位于:/lib/gitlab/import_export/version_checker.rb中:
1 | ... |
gitlab-v8.13.0-ee/lib/gitlab/import_export/version_checker.rb#L12-L32
我们可以看到这里的逻辑是读取VERSION文件的第一行赋值给变量version,然后检测verison与当前版本是否相同,相同返回true,不相同则返回错误信息(错误信息中包括变量version的值). 于是漏洞发现者Jobert Abma巧妙的使用了软链接来达到读取任意文件的目的。首先,我们给VERSION文件加上软链接并重新打包。
1 | ln -sf /etc/passwd VERSION |

这样,读取VERSION文件的时候服务器就会根据软链接读取到/etc/passwd的第一行内容并赋值给version。但是由于version与当前版本不相同,所以会输出version的值,也就是/etc/passwd第一行的内容。
访问之前搭建好的GitLab服务器,创建一个新的项目,填写完项目名称后在Import project from一栏中选择GitLab export,上传我们修改后的导入包,然后就可以看到/etc/passwd文件第一行

但是,如果只读取任意文件的第一行,能做的事情还是太少了。漏洞发现者显然不满足这一结果,他继续找了下去.
读取Project.json这一配置文件的代码位于:/lib/gitlab/import_export/project_tree_restorer.rb中:
1 | ... |
gitlab-v8.13.0-ee/lib/gitlab/import_export/project_tree_restorer.rb#L11-L22
在这里,我们可以再次使用软链接使变量json获取到任意文件的内容,但是由于获取的文件不是json格式,无法decode,导致异常抛出,最终在前端显示出任意文件的内容。 添加软链接并打包:
1 | ln -sf /etc/passwd project.json |

上传导出包,页面上显示的结果:

漏洞复现
注册并登录用户,新建一个项目,点击GitLab export:

在导入页面,将test.tar.gz上传

点击Import repository

读取到/etc/passwd文件内容:

参考
https://github.com/vulhub/vulhub/blob/master/gitlab/CVE-2016-9086/README.zh-cn.md