Bithug
Last updated
Was this helpful?
Last updated
Was this helpful?
Code management software is way too bloated. Try our new lightweight solution, BitHug. Source: distribution.tgz
This web application is using for the backend and for the frontend. The package manager is and is used to bundle assets. scripts are used to run the various build and serve processes. The entire project can easily be run in a 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 server with functionality similar to that of (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 the refs/meta/config
commit of the repo.
Looking at the backend code we see following files:
auth-api.ts
: Handles authentication from a user-token
cookie or authorization
header. Alternatively, if the incoming connection is happening over localhost
, the user kind
attribute is automatically set to admin
.
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 the git
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 a formatString
function.
web-api.ts
: Contains the login
, logout
, create repository, register
, and obtain user information endpoints.
webhooks.ts
: Similar to auth.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 to admin
and it is when req.socket.remoteAddress
is localhost
. 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 is 80
. Source: git-api.ts
Line 64.
The only places that data can be posted are the login
, register
, repo/create
, and logout
endpoints in web-api.ts
and the /:user/:repo.git/webhooks
, /:user/:repo.git/git-upload-pack
, and /:user/:repo.git/git-receive-pack
endpoints in git-api.ts
. The aforementioned endpoints in web-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 from localhost
. 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 to http://localhost:1823
in the client/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
requires user.kind === "admin" || user.user === repoOwner
or for the username to be in the access configuration file. Posting to /:user/:repo.git/git-receive-pack
calls req.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 to localhost
. In this case we will use the zwade/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 the zwade
user to the access.conf
file of the /_/zwade.git
repo. When we push a new commit to zwade/quine.git
, the webhook will be called and the new commit will be pushed to the /_/zwade.git
repo, which will add zwade
to the access.conf
and thus give us access. The commit will be pushed successfully because the request will come from localhost
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
to refs/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 the docker0
network interface using wireshark
, run the following commands in the cloned repository:
In wireshark
(my capture file: ) we can follow the HTTP stream and see the git packfile being sent with the content generated by the send-pack
process. To learn more about how the internals of git push
work (including send-pack
and git-receive-pack
) then you should read the of the Git Book. In wireshark
, 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 the body
as base64. We could try to manually create the header data generated by send-pack
, but that is difficult and wireshark
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 to localhost
and that it connects to port 80
. This is a problem for us because the backend of the application runs on localhost:1823
. We can bypass this by running a server that redirects all post requests to the localhost
domain. I wrote a simple python server to do this in . 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 . The server performs a 307
redirect because 307
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 .
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 script. The contentType
for the webhook is application/x-git-receive-pack-request
since this is the content type shown in the code for the /:user/:repo.git/git-receive-pack
endpoint in git-api.ts
on line 159. Make sure to set your user-token
cookie for the zwade
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 to localhost
on the server running BitHug. The payload data (a Git packfile containing a file called access.conf
with the text zwade
in it) is posted to localhost:1823/_/zwade.git/git-receive-pack
, and thus a new commit is added to the target repository that gives the user zwade
access. We can visit http://venus.picoctf.net:52986/_/zwade.git
to get the flag from the target repository's readme file.
picoCTF{good_job_at_gitting_good}