How to Spoof Any User on Github…and What to Do to Prevent It
Why GitHub Commit Signing is Important
Here at Gruntwork, we want to ensure that no one introduces malicious code into our GitHub repositories and realized the native GitHub Branch Protection is missing some features; not the least of which is the ability to notify us when Branch Protection is turned off! To this end, I was recently working on building an internal code scanning tool that ensures every commit in our GitHub repos has been (a) added via a Pull Request and (b) that at least one person who didn’t write the code has reviewed and approved the Pull Request. It was during this work that I discovered it’s actually pretty easy to impersonate another user when committing code to GitHub with the git CLI!
Let’s take a look at how this is possible:
I’m Matt and my co-worker here at Gruntwork is Jim. Take a look at how easy it is to commit some code to GitHub pretending to be Jim:
$ git config user.name "Jim Brikman" $ git config user.email "jims-email@gruntwork.io" $ touch badcode.js $ git add . $ git commit -m 'add some bad code as jim' $ git push
Now, if we head over to the GitHub UI, we can see a new commit that appears to have been added by Jim!
Signed Commits to the Rescue
Yikes, that’s not good! I just committed code as Jim! So how can this be possible? It turns out that, by default, git, and therefore GitHub, doesn’t validate the author of commits. So as long as you have write access to the repository, you can push code as any valid repo user. Luckily there’s a solution to this problem…signed commits!
If you cryptographically sign each commit with a key that only you have access to, GitHub will verify your signature and mark your commits as Verified when they appear in the GitHub Web UI.
Any commits you make directly through the GitHub Web UI editor are automatically signed, so let’s look at how we can set up our local machine to sign our git CLI commits by default, so they show up as verified on GitHub.
Please note that these steps apply specifically to MacOS and can easily be adapted on other *nix platforms, but the steps to set this up on Windows, I suspect, will be much different and is not covered here.
- Install packages
$ brew install gnupg pinentry
- Generate and list your GPG Key
$ gpg --gen-key
Real name: Matt Calhoun Email address: myemail@gruntwork.io You selected this USER-ID: "Matt Calhoun <myemail@gruntwork.io>"
Change (N)ame, (E)mail, or (O)kay/(Q)uit? O [Enter]
┌──────────────────────────────────────────────────────┐ │ Please enter the passphrase to │ │ protect your new key │ │ │ │ Passphrase: ________________________________________ │ │ │ │ <OK> <Cancel> │ └──────────────────────────────────────────────────────┘
$ gpg --list-secret-keys --keyid-format LONG
You should see output that looks like this:
/Users/matt/.gnupg/pubring.kbx ------------------------------ sec rsa2048/FB3F3623BCF1391C 2020-10-05 [SC] [expires: 2022-10-05]
NOTE: FB3F3623BCF1391C is the unique id of the key I generated on my machine. Yours will be different.
- Add Your Additional Email Addresses
Follow the procedure below to add any additional email addresses you use with GitHub to your key:
$ gpg --edit-key FB3F3623BCF1391C $ gpg> adduid
Real Name: Matt Calhoun Email address: myemailaddress@mypersonaldomain.com Comment: GitHub key Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O [Enter]
Q [Enter]
- Upload Your Key to GitHub
$ gpg --armor --export FB3F3623BCF1391C
You’ll see output similar to this:
-----BEGIN PGP PUBLIC KEY BLOCK-----
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 =uoVP -----END PGP PUBLIC KEY BLOCK-----
Now, browse to the SSH and GPG keys section of GitHub’s settings page, click the New GPG Key
button and paste the contents of the key (including the BEGIN and END lines) into the box and click the Add GPG Key
button.
5. Configure GPG Agent
In your shell’s init script (.bash_profile or .zshrc), add the following to ensure the GPG Agent is running on your machine:
# This check to make sure the GPG Agent is running and if not, starts it if [[ -f "~/.gnupg/.gpg-agent-info" && -n "$(pgrep gpg-agent)" ]]; then source ~/.gnupg/.gpg-agent-info export GPG_AGENT_INFO else eval $(eval $(gpg-agent --daemon --options ~/.gnupg/gpg-agent.conf)) fi
Now let’s add a couple of new config files:
~/.gnupg/gpg-agent.conf
# Allows gpg-agent to use the OSX keychain to store the gpg key's # passphrase so you don't have to type it each time you commit to # git pinentry-program /usr/local/bin/pinentry-mac
~/.gnupg/gpg.conf
use-agent batch no-tty
Finally, let’s restart the gpg-agent
for our new config to take effect:
$ gpg-connect-agent reloadagent /bye
6. Setup Git to Sign Commits
Finally, let’s tell git to sign all of our commits with our gpg key. Switch into any directory that is a git repo on your machine and run the following commands:
$ git config user.signingkey FB3F3623BCF1391C $ git config commit.gpgsign true
OK, let’s test it out! Start a new terminal session and then run the following:
$ touch goodcode.js $ git add . $ git commit -m 'good code as matt' $ git push
Now, if we take a look at the GitHub UI, we can see the new commit is marked as Verified!
Next Steps: Protected Branches
Now, you can see in the GitHub UI which commits are Verified and which are not so you can have confidence who the author of each commit really is. But, it is a manual process to scan through the list of commits in order to ensure each one is signed. If you want to take your code protection to the next level, you can enable Github’s Branch Protection to only allow commits that are signed.
Let’s take a look at how to do this:
- Browse to GitHub’s Branch Protection settings for your repo (https://github.com/YOUR-ORG/YOUR-REPO/settings/branches)
- Add a new rule for your default branch (master) by clicking the
Add rule
button - Click the “Require signed commits” option along with the other options that are applicable to your organization and click
Save changes
And that’s it; GitHub will now only allow signed commits into your default branch.
A Gruntwork, we are now using a combination of GitHub Branch Protection, Signed Commits, the GitHub Checks API, and a custom code scanner to ensure all code that enters our repos has been reviewed and approved by someone who wasn’t involved in writing the code. If you’d like more info on our setup, feel free to email us at support@gruntwork.io.