Leaving a Git repository in the document root

Introduction

For the WordPress installation on chaosdorf.de, we do not use the Debian package because it's outdated (obviously). Instead we run the current release straight from upstream.

We like to version control a lot in order to keep a history of things and the possibility to roll back breaking changes. That obviously means we are using etckeeper for /etc. Also we track /usr/local in Git, because it is a wasteland outside of the package manager's control.

So when we deployed WordPress from a source tarball, it was only natural to create another Git repository in /srv/www/de.chaosdorf. You might have an idea where this is going now ;-)

WordPress's configuration is stored in wp-config.php in the directory you installed WordPress to. Because of the way PHP works, that directory is accessible from the web. You can access wp-config.php in your web browser, but you cannot read its contents, because due to its file extension, it will be executed by PHP.

But next to wp-config.php, we had our .git directory which stores the contents of the Git repository. Although only root needs to have access to it, it was world-readable. An attacker could not read the wp-config.php, but they might be able to extract the WordPress configuration from the readable Git repository, where it would be stored as a Git blob object.

Exploiting the problem

As a first step, I tried to just git clone the repository

$ git clone http://chaosdorf.de/.git/
Cloning into 'chaosdorf.de'...
fatal: http://chaosdorf.de/.git/info/refs not found: did you run git update-server-info on the server?

Apparently accessing a repository over HTTP without any preparation on the server side does not work.

But Git stores its information in lots of small files, which we can access individually. We start by resolving the references:

$ curl http://chaosdorf.de/.git/HEAD
ref: refs/heads/master
$ curl http://chaosdorf.de/.git/refs/heads/master
57104ea967fc24763ead9c57d3463a80e9d05eea

That is the object ID of the latest commit. With the commit ID, we can use git-http-fetch to download the object files into a local repository:

$ mkdir foo
$ git init
$ git http-fetch -a 57104ea967fc24763ead9c57d3463a80e9d05eea http://chaosdorf.de/.git
$ git checkout 57104ea967fc24763ead9c57d3463a80e9d05eea

Now we have a local checkout of the HEAD commit of the repository, including the wp-config.php which should have been safe!

Fix

The core problem here is that PHP requires code in the document root and that PHP developers tend to put even the code in the document root that does not need to be there. Combined with my administrative oversight, this leads to problems like this.

The local fix I performed was, obviously, to set correct permissions on the .git directory.

chown -R root:root .git
chmod -R u=rwX,g=,o= .git
git config core.sharedRepository 0600

The last command makes sure that any newly created object files have the same permissions as well. I also changed the keys for session storage and the database password in the WordPress configuration.

Another option to altogether avoid this issue is to place the Git repository outside of the document root, by moving the document root one level down. For example you would have the repository in /srv/www/de.chaosdorf and the document root in /srv/www/de.chaosdorf/htdocs.