RSS Feed
更好更安全的互联网

GitLab 任意文件读取漏洞 (CVE-2016-9086) 和任意用户 token 泄露漏洞 分析

2016-11-10

Author:dawu,LG(知道创宇404安全实验室) Data:2016-10-09

0x00 漏洞概述


1.漏洞简介

GitLab 是一个利用Ruby on Rails开发的开源应用程序,实现一个自托管的Git项目仓库,可通过Web界面进行访问公开的或者私人项目。近日研究者发现在其多个版本中存在文件读取漏洞(CVE-2016-9086)任意用户authentication_token泄漏漏洞,攻击者可以通过这两个漏洞来获取管理员的权限,进而控制所有gitlab项目。

2.漏洞影响

  • 任意文件读取漏洞(CVE-2016-9086):
    GitLab CE/EEversions 8.9, 8.10, 8.11, 8.12, and 8.13
  • 任意用户authentication_token泄露漏洞:
    Gitlab CE/EE versions 8.10.3-8.10.5

0x01 漏洞复现


1.环境搭建

这里使用8.10.3版本是为了任意用户authentication_token泄露漏洞的复现。

安装完成后,访问服务器80端口即可看到GitLab登录页面。

注:8.9.0-8.13.0版本的GitLab的项目导入功能需要管理员开启,8.13.0版本之后所有用户都可以使用导入功能。管理员可以访问http://domain/admin/application_settings 开启,开启之后用任意用户新建项目的时候,可以在import project from一项中看到gitlab export。

2.漏洞分析

任意文件读取漏洞(CVE-2016-9086)

8.9.0版本开始,GitLab新增了导入导出项目的功能。
一个空的gitlab项目导出后结构如下:

export

其中VERSION文件内容为GitLab的导出模块的版本,project.json则包含了项目的配置文件。

当我们导入GitLab的导出文件的时候,GitLab会按照如下步骤处理: 1.服务器根据VERSION文件内容检测导出文件版本,如果版本符合,则导入。
2.服务器根据Project.json文件创建一个新的项目,并将对应的项目文件拷贝到服务器上对应的位置。

检测VERSION文件的代码位于:/lib/gitlab/import_export/version_checker.rb中:

我们可以看到这里的逻辑是读取VERSION文件的第一行赋值给变量version,然后检测verison与当前版本是否相同,相同返回true,不相同则返回错误信息(错误信息中包括变量version的值). 于是漏洞发现者Jobert Abma巧妙的使用了软链接来达到读取任意文件的目的。首先,我们给VERSION文件加上软链接并重新打包。

version_link

这样,读取VERSION文件的时候服务器就会根据软链接读取到/etc/passwd的第一行内容并赋值给version。但是由于version与当前版本不相同,所以会输出version的值,也就是/etc/passwd第一行的内容。

访问之前搭建好的GitLab服务器,创建一个新的项目,填写完项目名称后在Import project from一栏中选择GitLab export,上传我们修改后的导入包,然后就可以看到/etc/passwd文件第一行

VERSION

但是,如果只读取任意文件的第一行,能做的事情还是太少了。漏洞发现者显然不满足这一结果,他继续找了下去.
读取Project.json这一配置文件的代码位于:/lib/gitlab/import_export/project_tree_restorer.rb中:

在这里,我们可以再次使用软链接使变量json获取到任意文件的内容,但是由于获取的文件不是json格式,无法decode,导致异常抛出,最终在前端显示出任意文件的内容。 添加软链接并打包:

json_link

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

json

任意用户authentication_token泄露漏洞

复现步骤为:

1.注册一个普通用户,创建一个新的项目
2.在项目的member选项中,添加管理员到项目中。

add_admin

3.点击edit project,找到Export project部分,点击Export project,等待几分钟去查看注册邮箱收到的下载地址或者刷新页面,点击Download export下载导出包。

download

4.导出包的project.json中已经含有了管理员的authentication_token

admin_token

得到authentication_token之后我们就可以通过api做管理员可以做的事情了,比如查看管理员所在的项目:

get_all_project

分析原因:

我们在\app\controllers\projects_controller.rb中找到了export函数,这个函数被用来导出项目文件。

往下跟add_export_job(),在\app\models\project.rb中:

继续到\app\workers\project_export_worker.rb文件的ProjectExportWorker.perform_async():

这里我们可以看到current获取的是User.find(current_user_id)的内容,然后调用::Projects::ImportExport::ExportService.new(project, current_user).execute 由于笔者之前没有接触过ruby,这里只好采用gitlab-rails console来找到User.find()的值。可以看到,在User.find()中,存在authentication_token的值。

User.find

跟到\app\services\project\import_export\export_service.rb,这里执行version_saver, avatar_saver, project_tree_saver, uploads_saver, repo_saver, wiki_repo_saver这五个函数来写各种导出文件,其中project_tree_saver()负责导出project.json

跳过之后的几个繁琐的调用之后,执行了lib/gitlab/import_export/json_hash_builder.rb中的create_model_value函数。

这里出现了逻辑问题,由于parsed_hash这个变量不是全局变量,所以create_model_value()中执行parse_hash()时,parse_hash()中的parsed_hash被改变,但是create_model_value()函数中的parsed_hash不会变,这就造成了parse_hash()这个函数执行后create_model_value()parsed_hash这个值并没有改变。因此最后导出的文件包含了authentication_token

我们在gitlab-rails console里展示了这两者的区别。当value=user的时候,parsed_hash={:include=>:user},输出的结果如同图中的user.as_json(),会将所有内容输出,包括authentication_token。当parsed_hash为经过parse_hash()处理后的{:include=>{:user=>{:only=>[:id, :email, :username]}}}时,输出结果与user.as_json(only: [:id, :email, :username])相同。

include_only

后续RCE方式的探讨

hackone的两个报告中,漏洞发现者都提到了leads to RCE,笔者尝试去实现这一点。由于GitLab源码在gitlab.com上,所以当获取了GitLab的管理员权限后,我们可以通过authentication_token修改GitLab项目的源码,留下自己的后门。 为了重现这种情况,我们在本地新建一个新的项目去通过authentication_tokenGitLab api来修改项目文件。

root账户创建一个项目:test_rce,其中README.md的内容为created by root

admin_test

接下来,我们要用gitlabapi来修改它。首先,根据projects的api找到test_rce项目对应的id,这里是18

find_project_id

我们再根据api读取一下文件

read_file

这里,contentY3JlYXRlZCBieSByb290,这是文件内容被base64加密后的结果,解密一下就可以看到created by root

base64decode1

根据api的要求,我们通过PUT数据来修改文件,将README.md修改为change by notroot。 当我们再读一次,content内容为:Y2hhbmdlIGJ5IG5vdHJvb3Q=,解码之后就是change by notroot

changefile

base64decode2

不得不说,笔者所实现的这种方式攻击时间跨度很长,能否执行命令取决于开发者下一次更新的时间,这也是这种方法的缺点之一。

0x02 官方修复分析


任意文件读取漏洞(CVE-2016-9086)修复分析

symbolic_link_repair

我们可以看到,官方先移除了导入包里的软连接,其次,读取VERSION的内容和project.json的内容出错后将内容输出到日志里而非返回到前端。

任意用户authentication_token泄露漏洞修复分析

token_repair

官方让json_config_hash[current_key]获取到parse_hash()处理后的值。

0x03 参考

 


作者:kk | Categories:安全研究技术分享 | Tags: