Files
metasploit-gs/lib/msf/core/exploit/ndmp_socket.rb
T
Matthew Daley 52363aec13 Add module for CVE-2017-8895, UAF in Backup Exec Windows agent
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.
2017-05-24 00:18:20 +12:00

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