pwnEd CTF :: Back2basicss
Challenge
Getting Started
By going to the site and navigating around, the most promising looking page is the login page. Inspecting the source we can find a comment with what looks like credentials.
<!--- user@sigint.mx:4297f44b13955235245b2497399d7a93 -->
If you try to enter these credentials it won’t work - this is because we have been given a hashed password, which will then be rehashed when checking against the correct password.
As the hash contains 32 hex characters, it is likely to be an md5 hash. We could attempt to crack this ourselves using dictionary attacks or brute force, but a quicker method we should check first is using a lookup table, such as that provided by crackstation. Using this we find that the password is "123123".
Attempting to log in again with this password is successful… but we only have user access!
Using the Clue
From gaining user access we were given the following clue: Hello user, only admins get flags :P You might need to git good!
.
"only admins get flags"
The first bit tells us we only have user access, but we need admin access to get the flag.
By looking at our cookies we can see that we are authorised with a cookie with the key login
.
Looking at the value it can be recognised as a JSON Web Token (JWT).
It is worth reading about JWTs if you haven’t
yet come across them in order to understand how they can provide secure authentication.
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjowfQ.7Ofwz0UBEa_rfiqGSIpc-KYlGR7nM1CNjmkT_-Re7O8
We can decode this at jwt.io.
We can see that 'user' has a user_id of 0. By changing this, we might be able to access the page as a different user - such as the admin.
We can test this by changing the user_id to 1 and replacing our login cookie with the changed encoded token from jwt.io.
This gives us an invalid signature error - as we don’t have the private key used by the server for signing and verifying the token’s payload. When we modified the payload with jwt.io, we didn’t provide a private key, so the default string "your-256-bit-secret" was used as the secret.
If the solution involves modifying the JWT payload, we need to find the secret used by the server.
"You might need to git good"
The typo git
instead of get
suggests that git is being used for source code
version control.
A mistake which is often made when serving a website, is pointing the web root (the file path from which we are serving the website) to the git repository.
The problem with this is that git uses a hidden directory .git
to store everything
it needs for version control - including commit messages, previous versions of code etc.
This could provide useful info such as a commit message that mentions a bug, or
previous code which contained a sensitive info such as a password, api token or a
secret for signing and verifying JWTs.
By going to the endpoint /.git
on the website we get a 403 forbidden response.
However if we go to something different such as /.gitAAA
we get a 404 not found response.
This suggests that the /.git
endpoint exists, but directory listing is not enabled.
After inspecting the tree of a local git repo (tree -a myrepo/.git
), I found some endpoints that
I should be able to test for on the site.
Going to /.git/logs/HEAD
we find a commit log for the HEAD of the git repo.
commit: Ups forgot to remove the secret lmao
This is promising - if the secret being referred to is the JWT secret, we should be able to get this from the git repo.
First we need to get a copy of the git repo.
As I’m dumb and didn’t think to look for tools to automate this, I downloaded the git objects manually until I had enough to get the secret.
First we make an empty repo to start rebuilding from:
mkdir -p b2b_repo
cd b2b_repo
git init
Now we can download the ref to the master branch:
wget http://34.89.80.112:11003/.git/refs/heads/master
mv master .git/refs/heads/master
We can download the commit object being pointed to by this by using the git
object path scheme .git/objects/[first-2-chars]/[last-38-chars]
, as shown in
the post here.
cat .git/refs/heads/master
# output: d18d93407b3267b8f261f5d85d9151e9df901f21
wget http://34.89.80.112:11003/.git/objects/d1/8d93407b3267b8f261f5d85d9151e9df901f21
mkdir -p .git/objects/d1
mv 8d93407b3267b8f261f5d85d9151e9df901f21 .git/objects/d1
We can get more info about this commit with the following:
$ git cat-file -p d18d93407b3267b8f261f5d85d9151e9df901f21
tree c31ee8d0e732ca47742f822c1bb79dc706a7f21b
parent 3d760de78c5b4e979d672d7a312451801917fc28
author Sud0 <sudo@sudo.sudo> 1614017924 +0000
committer Sud0 <sudo@sudo.sudo> 1614017924 +0000
Final version
We now know the hash for the tree object and parent object - so we can download them to our local repo.
We can make a shell function to download an object quicker:
function download_gitobj(){ # $1 = object hash
BASE_URL="http://34.89.80.112:11003"
OBJ_HASH=$1 # assign variable so easier to read
first_2=${OBJ_HASH:0:2} # get first 2 characters of the hash
last_38=${OBJ_HASH:2:38} # get last 38 characters of the hash
object_path=".git/objects/${first_2}/${last_38}" # get full path for git object
mkdir -p .git/objects/${first_2} # check directory exists to download object to
wget "${BASE_URL}/${object_path}" -O $object_path # download object
}
Although this is a janky™ solution, my next stage was downloading all git objects
needed to run git log
successfully:
The output above shows us which git objects are missing, causing the command to fail.
We can download these using our shell function, and run git log
again, repeating
this until we see no errors related to missing objects. Luckily this worked the first time.
Now we can see the commits and commit messages - we want to find what was changed in the commit
with the message Ups forgot to remove the secret lmao
.
To do this we can use git diff
:
git diff f88f31725517...3d760de78c
But… we get more errors about missing objects. So lets download it with our shell function again:
download_gitobj 669f7e5d39133dc090771b88f47b91f4329fc392
git diff f88f31725517...3d760de78c
# repeat above until git diff runs with no errors
After repeating this a few times we get some output:
Looks like we have our secret!
Putting it Together
Now we have the secret we can try to modify the jwt payload again. We can paste the secret over the placeholder with 'your-256-bit-secret' and try with user_id as 1 again:
Now lets set our 'login' cookie to this new token and attempt to reach /login.php
on the site again.
This time we get the flag!
Tools for Downloading Git Repos
If you have more braincells than me you might think this could be done quicker with some kind of tool - and you’d be right. One example is git-dumper - a python script which downloads a repo for you.
This makes the second stage much quicker:
git clone https://github.com/arthaud/git-dumper
cd git-dumper
pip3 install -r requirements.txt
./git-dumper.py http://34.89.80.112:11003/.git /tmp/b2b_repo
cd /tmp/b2b_repo
git log
git diff f88f31725517...3d760de78c
Key Lessons
-
Do not store your git repo in your server’s web root unless you are sure you have restricted access to
.git/*
-
Disabling directory listing on your web server is a good idea but it won’t save you here
-
If you ever accidentally push a password / api key / other sensitive info to a repo (we’ve all been there), changing it in a new commit isn’t good enough - either remove it with a tool such as BFG Repo-Cleaner or delete the repo, and start again from the state where sensitive data has been removed. If possible you should also change the password / revoke the token if this