跳转至

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

漏洞描述

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

影响版本

  • GitLab CE/EEversions 8.9, 8.10, 8.11, 8.12, and 8.13(任意文件读取)
  • Gitlab CE/EE versions 8.10.3-8.10.5(token泄露)

环境配置

  • Vulhub项目
  • Docker-compose启动

Text Only
PORT:8080
User:root
Passwd:vulhub123456

漏洞复现

新建一个普通用户用作测试

用户登录后,在创新项目页面找到导出选项

上传vulhub自带测试压缩包

在导入页面可以看到文件读取结果

漏洞原理

Linux 链接

硬链接

硬连接指通过索引节点来进行连接。在 Linux 的文件系统中,保存在磁盘分区中的文件不管是什么类型都给它分配一个编号,称为索引节点号(Inode Index)。在 Linux 中,多个文件名指向同一索引节点是存在的。比如:A 是 B 的硬链接(A 和 B 都是文件名),则 A 的目录项中的 inode 节点号与 B 的目录项中的 inode 节点号相同,即一个 inode 节点对应两个不同的文件名,两个文件名指向同一个文件,A 和 B 对文件系统来说是完全平等的。删除其中任何一个都不会影响另外一个的访问。

硬连接的作用是允许一个文件拥有多个有效路径名,这样用户就可以建立硬连接到重要文件,以防止“误删”的功能。其原因如上所述,因为对应该目录的索引节点有一个以上的连接。只删除一个连接并不影响索引节点本身和其它的连接,只有当最后一个连接被删除后,文件的数据块及目录的连接才会被释放。也就是说,文件真正删除的条件是与之相关的所有硬连接文件均被删除。

Bash
ln /var/www/test test
# 当前路径创建test 引向/var/www/test 文件夹 

软链接

另外一种连接称之为符号连接(Symbolic Link),也叫软连接。软链接文件有类似于 Windows 的快捷方式。它实际上是一个特殊的文件。在符号连接中,文件实际上是一个文本文件,其中包含的有另一文件的位置信息。比如:A 是 B 的软链接(A 和 B 都是文件名),A 的目录项中的 inode 节点号与 B 的目录项中的 inode 节点号不相同,A 和 B 指向的是两个不同的 inode,继而指向两块不同的数据块。但是 A 的数据块中存放的只是 B 的路径名(可以根据这个找到 B 的目录项)。A 和 B 之间是“主从”关系,如果 B 被删除了,A 仍然存在(因为两个是不同的文件),但指向的是一个无效的链接。

Bash
ln –s /var/www/test /var/test
# 创建/var/test 引向/var/www/test 文件夹 

删除软链接

Bash
#推荐使用
unlink test

#其次用法
rm test

#不推荐用法
rm -rf test

常用参数

Bash
-f : 链结时先将与 dist 同档名的档案删除
-d : 允许系统管理者硬链结自己的目录
-i : 在删除与 dist 同档名的档案时先进行询问
-n : 在进行软连结时,将 dist 视为一般的档案
-s : 进行软链结(symbolic link)
-v : 在连结之前显示其档名
-b : 将在链结时会被覆写或删除的档案进行备份
-S SUFFIX : 将备份的档案都加上 SUFFIX 的字尾
-V METHOD : 指定备份的方式
--help : 显示辅助说明
--version : 显示版本

Linux ln命令:在文件之间建立链接(硬链接和软链接)详解版

GitLab导入导出分析

从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中:

Ruby
...
def check!
  version = File.open(version_file, &:readline)
  verify_version!(version)
rescue => e
  shared.error(e)
  false
end
...
def verify_version!(version)
    if Gem::Version.new(version) != Gem::Version.new(Gitlab::ImportExport.version)
        raise Gitlab::ImportExport::Error.new("Import version mismatch: Required #{Gitlab::ImportExport.version} but was #{version}")
    else
        true
    end
end
...

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

Bash
ln -sf /etc/passwd VERSION
tar zcf change_version.tar.gz ./

这样,读取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中:

Ruby
...
def restore
  json = IO.read(@path)
  tree_hash = ActiveSupport::JSON.decode(json)
  project_members = tree_hash.delete('project_members')

  ActiveRecord::Base.no_touching do
    create_relations
  end
rescue => e
  shared.error(e)
  false
end
...

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

Bash
ln -sf /etc/passwd project.json
tar zcf change_version.tar.gz ./

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

img

Important

漏洞利用的关键点:

  • 有报错输出(意味着版本号/json格式必须不匹配)
  • 有软链接

修复建议

  • 升级到最新版本,使用强口令到后台,内部鉴权验证加强

补丁分析

symbolic_link_repair

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