Welcome! We are finally here: this is the first of my Honest Write-ups to be blinded by the light of day. I rooted my first few Hack The Box machines back in October/November last year, when I started the CPTS path, but then focused on studying. It was actually during the exam that the idea for these came to mind. An honest report would be very different. “The tester made a stupid typo and assumed the target machine was down”, or “at this point, the tester was stuck for days”. Now that CPTS is out of the way, I’m back to solving these machines, and will write honestly about them.
LinkVortex, and another easy-rated machine that I won’t name yet (still active), made me worry. I had stumbled my way to root on a few easy boxes at the start of my CPTS path. I rooted a few more after CPTS, graduating to medium machines and feeling almost at ease. Then I tried that other easy one, and I got nothing. Staring at a web application’s login page, all the enumeration steps I knew of done, all the very few options exhausted. Stunned, I set it aside and moved on to LinkVortex, one week before its retirement. Spoiler alert:
Enumerate web server
Discovered `dev` subdomain by virtual host bruteforce, and `.git` directory by content bruteforce
Analyse git repository
Recovered credentials in git repository history
Exploit file read vulnerability
Extracted `bob`'s credentials via CVE-2023-40028
Foothold
Logged into SSH as user `bob`
Enumerate permissions
Discovered that `bob` can run a cleanup script as `root` via sudo
Privilege escalation
Exploited insecure script and sudo rights to gain root shell
Up-beat retro synthwave music on
It always starts the same way - a quick nmap
shows SSH & HTTP only, and the full TCP scan confirms it later on:
Browsing to the web application finds an instance of Ghost, yet another open-source Content Management System. There isn’t much to note for now, aside from the login page we find with the help of the documentation, at http://linkvortex.htb/ghost/#/signin
. After trying some common credentials for the admin@linkvortex.htb
account, the author of the posts on the site, we move on to see what else we can find.
Looking for vulnerabilities on this version of Ghost (5.58) finds a few, including an arbitrary file read that will surely be useful as soon as we manage to authenticate, but nothing too useful for now. There is a way to bypass the site’s login brute-force protection, so I used that to try 200 common passwords in case that was the intended path. It usually isn’t though, and it wasn’t here either.
Fuzzing subdirectories doesn’t find much, but doing the same for virtual hosts does:
Browsing to that finds a simple “launching soon” page. Its source doesn’t tell us anything, and fuzzing subdirectories with my usual list finds absolutely nothing:
The good thing about web enumeration is that there’s not a lot to try, that I know of at least. If we face a custom application, sure, there’s plenty of things to try. But if the only apps we find are commercial or open-source, these challenges don’t really expect us to find vulnerabilities that are not already public. I had to remind myself of that while messing with the app’s API in Burp. But I had fuzzed directories, v-hosts, v-hosts of that v-host, parameters, checked robots.txt
, sitemap.xml
, pretty much everything from my notes… and I got nothing.
I probably shouldn’t, but I usually only run tools like nikto
last. Maybe it’s because those manual enumeration steps usually do the trick, and I tend to think about them first. Anyway, nikto
found a .git
directory on the dev
v-host:
This annoyed me, but it was a good lesson learned for the future. It turns out that directory-list-2.3-medium.txt
does not include some pretty common directories. So note to self: also use common.txt
…
This was exciting for just a bit, because by browsing the files in the .git
directory I thought it was just a clone of the Ghost GitHub repository, as indicated by the .git/logs/HEAD
file. Nothing stood out either while painstakingly checking strings in the .git/index
file. I fuzzed the cgi-bin
directory for, well, CGI bins, with a few educated guesses but with no results.
Darkest just before dawn
Here I was again. Staring at a web application’s login page, all the enumeration steps I knew of done, all the very few options exhausted. Double stunned, thinking that my web enumeration skills were clearly lacking even for Easy HTB boxes, and also worried, because LinkVortex is mentioned in Tj Null’s famous list of machines to prepare for the OSCP exam, which I was then working on.
I started listing what I had found in this box, and which of those things I might not know how to thoroughly enumerate. Arrogantly, in retrospect, the only item on that list was the cgi-bin
directory. Maybe CPTS didn’t teach me everything there was to know about these, so I turned to google and ended up, as usual, on HackTricks’ page on CGI. It didn’t actually tell me all that much, but a few items below it, on the menu, was their entry on Git.
I’ve been using Git daily and professionally, as a Software Engineer, for about 10 years now. So what I’m about to tell you is more than mildly embarrassing.
The first thing that HackTricks says is “To dump a .git
folder from a URL use git-dumper”. I did think “so what, I can just browse the directory”, but I had a look. I did say “yeah, I checked that” a few more times while reading the “How does it work” section, until the last sentence hit me: “Run git checkout .
to recover the current working tree.”
What! You can do that?! Well, how many times in those 10 years did I restore changes? What’s the difference between getting one deleted file back to the tracked state, and getting all deleted files back? Of course you can do that. So I used my new best friend to do that:
This was cool for a second. Most of those files were exactly what we could already find on the public repository for this version, and the one new file Dockerfile.ghost
didn’t provide anything actionable for now. I was trying a few grep
s that came to mind, until I realised that if the checkout
had recovered a new file, there might have been additional changes in the working tree. And indeed there were:
We snap throw that password at the Ghost login page, and authenticate successfully. It feels like it was ages ago that we found that arbitrary file read vulnerability, but it’s finally time to use one of the several proof-of-concept scripts available. I went with CVE-2023-40028 PoC, which is awkwardly interactive but does the job:
The other file in the Git repository, Dockerfile.ghost
, points us towards a few files that could be interesting to check. config.production.json
is the one with the reward:
Fi-na-lly, [hacker voice] we’re in…
The rest
The rest was fairly simple. The second of the Compulsory Couple of Commands we always run after landing on a Linux box (id
and sudo -l
) shows us that we’ll be trying to abuse this script:
In English: take a .png
file, and check if it is a symbolic link. If it is, then
- if it targets a file that has
etc
orroot
anywhere in the path, remove it; - otherwise, move it to another folder, and possibly print its content.
The limitation on etc
and root
looked solid, so I started by checking other potentially interesting files that I couldn’t read, like the docker-compose.yml
file:
I thought this was it, root is probably reusing that password. But throwing those credentials over SSH did not work. I spent some time trying to figure out if having access to that database could be the way, but it looked more and more like a rabbit-hole.
I eventually wondered if the script would work properly if I created a link-to-a-link. In other words, a .png
file linking to a .txt
file, which in turn linked to a target file we do not have access to, like /etc/shadow
. Examining the script, it looked like this could work, but the first attempt was unsuccessful:
At first I thought that the empty content was due to stderr
being redirected to /dev/null
, so I was not seeing a "Permission denied"
kind of message. But that didn’t make any sense given what the script is programmed to do. Then I noticed the link after it got moved to /var/quarantine
:
It was still pointing to a dude.txt
file, which was still on my current working directory… so that couldn’t possibly work. Let’s try with a full path instead:
Roll the credits
I hope you’ve enjoyed this. “Administrator” is retiring next week, so I’ll be back with more soon. I’ll finish these write-ups with a summary of lessons learned, a good old tl;dr except it’s at the end:
- I solemnly swear to always fuzz with
common.txt
as well from now on; - 10 years later, I suddenly understand an everyday tool much better…
- for reading this far, you’re a champ.