Bithug
Problem
Code management software is way too bloated. Try our new lightweight solution, BitHug. Source: distribution.tgz
Solution
This web application is using Express.js for the backend and React for the frontend. The package manager is Yarn and Webpack is used to bundle assets. NPM scripts are used to run the various build and serve processes. The entire project can easily be run in a docker container thanks to the included
Dockerfile
. Essentially, this challenge makes use of the standard set of tools used to build a full-stack application. It is likely a hard challenge due to the number of programs that one must understand to complete it. The goal of the challenge is to get access to the repository at/_/<username>.git
because the flag is in the readme file of that repo.The actual website itself is a simple Git server with functionality similar to that of GitHub (the challenge name is "GitHub" with the first and last letters switched). Users can create an account, create a git repository, and then push changes. Additionally, webhooks are supported so a user can post data to an external server on every commit. There is also a feature to give other users access to your repository by editing the
access.conf
file on therefs/meta/config
commit of the repo.Looking at the backend code we see following files:
auth-api.ts
: Handles authentication from auser-token
cookie orauthorization
header. Alternatively, if the incoming connection is happening overlocalhost
, the userkind
attribute is automatically set toadmin
.auth.ts
: Basic logic for connecting the application to a database to store users.git-api.ts
: The API to interact with git repositories over HTTP. Many of these function signatures are standard as part of Git.git.ts
: A bridge between the web application and thegit
command.index.ts
: The main entry point.static-api.ts
: Code to enable serving of the main html and js files for React.utils.ts
: Basic utilities. Only contains aformatString
function.web-api.ts
: Contains thelogin
,logout
, create repository,register
, and obtain user information endpoints.webhooks.ts
: Similar toauth.ts
, but for webhooks. Connects the application to a database to store webhooks.
Here are some interesting things to take note of:
The only ways to obtain access to a repository that a user does not own are to be
user.kind === "admin"
or to have the username be in the access config file. Source:git-api.ts
Line 20.There is only one time when
user.kind
is set toadmin
and it is whenreq.socket.remoteAddress
islocalhost
. Source:auth-api.ts
Line 26.The code that creates a webhook checks to make sure that the endpoint of the webhook is not
localhost
and that the port is80
. Source:git-api.ts
Line 64.The only places that data can be posted are the
login
,register
,repo/create
, andlogout
endpoints inweb-api.ts
and the/:user/:repo.git/webhooks
,/:user/:repo.git/git-upload-pack
, and/:user/:repo.git/git-receive-pack
endpoints ingit-api.ts
. The aforementioned endpoints inweb-api.ts
are basic and likely not exploitable.
Initially, I tried figuring our how to spoof the nodejs
req.socket.remoteAddress
function (auth-api.ts
Line 26). However, this web application is behind a reverse proxy that is configured correctly so spoofing this value is not possible. Thus, in order to execute a request as admin, the request needs to literally come fromlocalhost
. We want to execute a request as an admin because an admin can access any repo, including the one with the flag at/_/<username>.git
. Also, when we make a commit to a repository on BitHug, the following requests are shown in the console:Every commit posts data we control to the
/:user/:repo.git/git-receive-pack
endpoint, which is useful.For this tutorial, we will be using the
zwade
user because some of his repositories are proxied tohttp://localhost:1823
in theclient/src/webpack.config.js
devServer
. I'm not sure if it is necessary to use this user.Trying to use any endpoint that starts with
/:user/:repo.git
requiresuser.kind === "admin" || user.user === repoOwner
or for the username to be in the access configuration file. Posting to/:user/:repo.git/git-receive-pack
callsreq.git.receivePackPost
and allows the user to push changes to their git repository. Therefore, we need to create a webhook on a repo that a regular user owns that posts data tolocalhost
. In this case we will use thezwade/quine.git
repo. The webhook will post a git pack to the/_/zwade.git/git-receive-pack
endpoint to add a new commit to the/_/zwade.git
repo. This commit will add thezwade
user to theaccess.conf
file of the/_/zwade.git
repo. When we push a new commit tozwade/quine.git
, the webhook will be called and the new commit will be pushed to the/_/zwade.git
repo, which will addzwade
to theaccess.conf
and thus give us access. The commit will be pushed successfully because the request will come fromlocalhost
and thus the user will be an admin.We first need to obtain a Git packfile containing a commit that adds a file called
access.conf
torefs/meta/config
containing our username,zwade
. The can be completed by creating a new empty repository on BitHug and then cloning it using the provided commands. While capturing packets on thedocker0
network interface usingwireshark
, run the following commands in the cloned repository:In
wireshark
(my capture file: bithug-git.pcapng) we can follow the HTTP stream and see the git packfile being sent with the content generated by thesend-pack
process. To learn more about how the internals ofgit push
work (includingsend-pack
andgit-receive-pack
) then you should read the Chapter 10.6: Git Internals - Transfer Protocols of the Git Book. Inwireshark
, we can change the data view to "C Arrays" and copy-paste the hex bytes into CyberChef to convert them to base64, since the/:user/:repo.git/webhooks
endpoint decodes thebody
as base64. We could try to manually create the header data generated bysend-pack
, but that is difficult andwireshark
makes it easy to get the exactly correct payload.C Array extracted from
wireshark
:Using CyberChef we get the following base64 string:
MDA5NDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAgZTc4NmRlYmE5NjcwNDM0NTQxYzBlNmY2YTQwZTliZDI2ZjE4YTk4YSByZWZzL21ldGEvY29uZmlnACByZXBvcnQtc3RhdHVzIHNpZGUtYmFuZC02NGsgYWdlbnQ9Z2l0LzIuMjUuMTAwMDBQQUNLAAAAAgAAAAOWC3icpYwxDoMwDAD3vMIfKLLT1AGpqtqNbxDbUTrQSBCG/r4IntDpdDdcW8zAOAbLWQhRk4rSja8SfWDBIVkmS77vxQc3ba3UBcbpq/aBsW7rjns59HmiHLGTOj+AmJh9jEOECwZEt9f53Zr9sXAvVfcD37w5JqcCeJwzNDAwMzFRSExOTi0u1kvOz0tj4PvNHNhaKSyc4nlqW2xA36FLt4wLAfZkDpE2eJyrKk9MSeUCAAi5AibuJPcRWkrPWybWvEnYtztHIwKoUA==
. This is the payload for our webhook.Before we can craft our webhook payload we need to get around the check on lines 71-77 of
git-api.ts
. This code makes sure the webhook does not point tolocalhost
and that it connects to port80
. This is a problem for us because the backend of the application runs onlocalhost:1823
. We can bypass this by running a server that redirects all post requests to thelocalhost
domain. I wrote a simple python server to do this in server.py. You can either run this server on your local network (and port forward it on your router) or put it on an actual server using something like Google's AppEngine. The server performs a307
redirect because307
redirects prohibit HTTP method changes upon redirect: "It is therefore recommended to set the 302 code only as a response for GET or HEAD methods and to use 307 Temporary Redirect instead, as the method change is explicitly prohibited in that case." Quote from HTTP Code 302 on MDN.Let's create a new repository called
first-repo
for this webhook. Then, we can post the webhook using cURL:curl http://venus.picoctf.net:52986/zwade/first-repo.git/webhooks -H "Content-Type: application/json" --cookie "user-token=2dcba1e6-2957-475b-adad-54d123d7bc9a" --data '{"url":"<IP_ADDRESS>/_/zwade.git/git-receive-pack","body":"MDA5NDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAgZTc4NmRlYmE5NjcwNDM0NTQxYzBlNmY2YTQwZTliZDI2ZjE4YTk4YSByZWZzL21ldGEvY29uZmlnACByZXBvcnQtc3RhdHVzIHNpZGUtYmFuZC02NGsgYWdlbnQ9Z2l0LzIuMjUuMTAwMDBQQUNLAAAAAgAAAAOWC3icpYwxDoMwDAD3vMIfKLLT1AGpqtqNbxDbUTrQSBCG/r4IntDpdDdcW8zAOAbLWQhRk4rSja8SfWDBIVkmS77vxQc3ba3UBcbpq/aBsW7rjns59HmiHLGTOj+AmJh9jEOECwZEt9f53Zr9sXAvVfcD37w5JqcCeJwzNDAwMzFRSExOTi0u1kvOz0tj4PvNHNhaKSyc4nlqW2xA36FLt4wLAfZkDpE2eJyrKk9MSeUCAAi5AibuJPcRWkrPWybWvEnYtztHIwKoUA==","contentType":"application/x-git-receive-pack-request"}'
. Make sure to replace<IP_ADDRESS>
with the IP address where you are running the server.py script. ThecontentType
for the webhook isapplication/x-git-receive-pack-request
since this is the content type shown in the code for the/:user/:repo.git/git-receive-pack
endpoint ingit-api.ts
on line 159. Make sure to set youruser-token
cookie for thezwade
user by grabbing it from your browser developer tools.Once this webhook is in place, all we need to do is commit a new change to
zwade/first-repo.git
:Running the above commands in the
zwade/first-repo.git
repo will push the new file and thus execute our webhook. Our webhook connects to our server which redirects the request tolocalhost
on the server running BitHug. The payload data (a Git packfile containing a file calledaccess.conf
with the textzwade
in it) is posted tolocalhost:1823/_/zwade.git/git-receive-pack
, and thus a new commit is added to the target repository that gives the userzwade
access. We can visithttp://venus.picoctf.net:52986/_/zwade.git
to get the flag from the target repository's readme file.
Flag
picoCTF{good_job_at_gitting_good}
Last updated