The git-shell is a restricted shell maintained by the git developers and is meant to be used as the upstream peer in a git remote session over a ssh tunnel. The basic idea behind this shell is to restrict the allowed commands in a ssh session to the ones required by git which are as follows:

git-receive-pack Receives repository updates from the client.

git-upload-pack Pushes repository updates to the client.

git-upload-archive Pushes a repository archive to the client.



Besides those built-in commands, an administrator can also provide it’s own commands via shell scripts or other executable files. As those are typically completely custom, this post will concentrate on the built-in ones.

Note: This has nothing to do with the also recently fixed vulnerabilities in gitlab [1] [2].

If you are familiar with git, you’ll maybe know that most of the servers encapsulate the git protocol inside additional protocols like SSH or HTTP/S [3]. That’s because the git protocol itself, while being a simple text based protocol [4], does not provide any authentication or protection mechanisms for the transferred data. The most common choice for write access to a repository is SSH as it provides multiple authentication mechanisms, a stable encryption, low protocol overhead once established and is widely approved.

The downside of using SSH is that it was primarily designed to provide a shell access to remote users (“Secure SHell”). Typically, one does not give that to git users. To restrict the connection to be used only for accessing repositories, one has to replace the original shell (typically bash or something similar) by another, more restrictive shell. Big hosting companies often implemented their own version which mimics the commands listed above. But it is also possible to use the shell provided by the git developers, which restricts you to use only whitelisted commands and calls them accordingly.

The setup is fairly simple. The recommended way is to create a dedicated git user on your server and use the git-shell command as the login shell for that user [5]. Another option is to use so called SSH force commands, which allows you to decide on a per client base (depending on the used key during authentication), but more on this later.

If you’ve configured a ssh remote repository in your local repository, a git push essentially starts the following command (received data, sent data):

ssh git@remoteserver “git-receive-pack ‘/myrepository.git'”

008957d650a081a34bcbacdcdb5a94bddb506adfe8e0 refs/heads/develop report-status delete-refs side-band-64k quiet ofs-delta agent=git/2.1.4

003fbe8910f121957e3326c4fdd328ab9aabd05abdb5 refs/heads/master

00000000

If both repositories have the same commits. If you try to execute commands which are not in this whitelist (either the builtin commands listed above or inside of a git-shell-commands directory in the home directory) you’ll get an error that this command is not recognized. Typical command injection attacks also do not work, as there is no interactive shell used. Instead the command line is simply split by spaces (but respecting quotes) and used by execve.

This convinced me to take a look to the protocol handling binaries itself. Additionally, I remembered that git has an inbuilt help command which opens the man page for the given command. Example:

$ git help init

GIT-INIT(1) Git Manual GIT-INIT(1) NAME git-init - Create an empty Git repository or reinitialize an existing one [...]

Some commands do also have the neat feature to invoke this command by using the –help commandline option:

$ git init --help

GIT-INIT(1) Git Manual GIT-INIT(1) NAME git-init - Create an empty Git repository or reinitialize an existing one [...]

This also applies to the commands git-receive-pack and git-upload-archive. If we try this on a server:

$ ssh git@remoteserver "git-receive-pack '--help'"

GIT-RECEIVE-PACK(1) Git Manual GIT-RECEIVE-PACK(1) NAME git-receive-pack - Receive what is pushed into the repository [...]

Neat! But how does this help us to bypass the restrictions? On most systems, if you open a man page (by the man command), the man specification is parsed, rendered to an ANSI output and piped into a pager (most of the time the less command). This allows you to scroll and search within the main page, independent of your terminal size and capabilities.

Besides being a simple pager, less has also some additional interactive features. It allows you for example to open additional files (for reading), write the current output to a logfile and execute system commands in the current shell (!). To be able to use those features, it is required to run less in interactive mode. This mode is automatically enabled if a pty is available. This is typically the case if you simply connect to a SSH server, but is not the case if you directly run commands (as we are required to do in the default git-shell configuration (no custom commands)). Luckily we can force the ssh client to allocate a pty (if it is not disabled on the server side, which is most of the time not the case):

$ ssh -t git@remoteserver "git-receive-pack '--help'"

GIT-RECEIVE-PACK(1) Git Manual GIT-RECEIVE-PACK(1) NAME git-receive-pack - Receive what is pushed into the repository Manual page git-receive-pack(1) line 1 (press h for help or q to quit)

Nice! We are now able to use all interactive features of less :-). In the recommended setup there is, however, one restriction. As I said before, the shell execution feature tries to execute commands in the current shell. This is the git-shell in our case, therefore we have the same restrictions here as if we had with the commands specified over ssh. Nevertheless, we are able to read files, list directories (by (ab)using the tab completion) and write the current shown output to a file (which might help us further if we are able to control a part of the output).



But as you might remember from the beginning of the post, there is also a second method to use git-shell (although not that common, as far as I can tell). This could for example be used if you want to restrict only a subset of the users with access to your hosted repositories, or if you are not allowed to change the shell for your git user (e.g. in a managed environment without root access).

This time, we leave the the login shell as is (bash) and restrict the users by specifying the git-shell command in the .ssh/authorized_keys file. Example:

command="git-shell -c \"$SSH_ORIGINAL_COMMAND\"" ssh-rsa AAAAB3NzaC1yc2EA[...]

This behaves exactly the same as if it was configured as the login shell, except that less is able to run commands in the login shell 🙂

But it has to be noted here, that you are able to supply additional (optin) flags to the forced command which restrict the ssh features. The most notable flag is the no-pty flag [6]. This prevents clients from requesting a pty and therefore does not allow to run less in an interactive mode.

I recommend to update to one of the fixed versions v2.4.12, v2.5.6, v2.6.7, v2.7.5, v2.8.5, v2.9.4, v2.10.3, v2.11.2, v2.12.3 or v2.13.0.

Best,

Timo

@bluec0re

Timeline

2017-04-25 Reported to the git-security mailing list

2017-05-01 Assigned CVE-2017-8386

2017-05-10 Release of the fixed versions v2.4.12, v2.5.6, v2.6.7, v2.7.5, v2.8.5, v2.9.4, v2.10.3, v2.11.2, v2.12.3 and v2.13.0

References