Skip to content

Commit a946bdb

Browse files
committed
Add cookie management to HttpClient
1 parent 6e64d74 commit a946bdb

1 file changed

Lines changed: 60 additions & 42 deletions

File tree

lib/msf/core/exploit/http/client.rb

Lines changed: 60 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
require 'uri'
44
require 'digest'
5+
56
module Msf
67

78
###
@@ -11,6 +12,7 @@ module Msf
1112
#
1213
###
1314
module Exploit::Remote::HttpClient
15+
1416
include Msf::Auxiliary::Report
1517

1618
#
@@ -48,6 +50,7 @@ def initialize(info = {})
4850
OptString.new('DOMAIN', [ true, 'The domain to use for Windows authentication', 'WORKSTATION']),
4951
OptFloat.new('HttpClientTimeout', [false, 'HTTP connection and receive timeout']),
5052
OptBool.new('HttpPartialResponses', [false, 'Return partial HTTP responses despite timeouts', false]),
53+
OptBool.new('HttpKeepCookies', [false, 'Keep cookies from HTTP responses for reuse in HTTP requests', false]),
5154
OptBool.new('HttpTrace', [false, 'Show the raw HTTP requests and responses', false]),
5255
OptBool.new('HttpTraceHeadersOnly', [false, 'Show HTTP headers only in HttpTrace', false]),
5356
OptString.new('HttpTraceColors', [false, 'HTTP request and response colors for HttpTrace (unset to disable)', 'red/blu'])
@@ -88,6 +91,9 @@ def initialize(info = {})
8891
)
8992
register_autofilter_ports([ 80, 8080, 443, 8000, 8888, 8880, 8008, 3000, 8443 ])
9093
register_autofilter_services(%W{ http https })
94+
95+
# Initialize an empty cookie jar to keep cookies
96+
self.cookie_jar = Set.new
9197
end
9298

9399
def deregister_http_client_options
@@ -314,69 +320,81 @@ def cleanup
314320
#
315321
# Passes +opts+ through directly to Rex::Proto::Http::Client#request_raw.
316322
#
317-
def send_request_raw(opts={}, timeout = 20, disconnect = false)
323+
def send_request_raw(opts = {}, timeout = 20, disconnect = false)
318324
if datastore['HttpClientTimeout'] && datastore['HttpClientTimeout'] > 0
319325
actual_timeout = datastore['HttpClientTimeout']
320326
else
321-
actual_timeout = opts[:timeout] || timeout
327+
actual_timeout = opts[:timeout] || timeout
322328
end
323329

324-
begin
325-
c = connect(opts)
326-
r = opts[:cgi] ? c.request_cgi(opts) : c.request_raw(opts)
330+
c = connect(opts)
331+
r = opts[:cgi] ? c.request_cgi(opts) : c.request_raw(opts)
327332

328-
if datastore['HttpTrace']
329-
request_color, response_color =
330-
(datastore['HttpTraceColors'] || '').split('/').map { |color| "%bld%#{color}" }
333+
if datastore['HttpTrace']
334+
request_color, response_color =
335+
(datastore['HttpTraceColors'] || '').split('/').map { |color| "%bld%#{color}" }
331336

332-
request = r.to_s(headers_only: datastore['HttpTraceHeaders'])
337+
request = r.to_s(headers_only: datastore['HttpTraceHeaders'])
333338

334-
print_line('#' * 20)
335-
print_line('# Request:')
336-
print_line('#' * 20)
337-
print_line("%clr#{request_color}#{request}%clr")
338-
end
339+
print_line('#' * 20)
340+
print_line('# Request:')
341+
print_line('#' * 20)
342+
print_line("%clr#{request_color}#{request}%clr")
343+
end
339344

340-
res = c.send_recv(r, actual_timeout)
345+
res = c.send_recv(r, actual_timeout)
341346

342-
if datastore['HttpTrace']
343-
print_line('#' * 20)
344-
print_line('# Response:')
345-
print_line('#' * 20)
347+
if datastore['HttpTrace']
348+
print_line('#' * 20)
349+
print_line('# Response:')
350+
print_line('#' * 20)
346351

347-
if res
348-
response = res.to_terminal_output(headers_only: datastore['HttpTraceHeadersOnly'])
352+
if res
353+
response = res.to_terminal_output(headers_only: datastore['HttpTraceHeadersOnly'])
349354

350-
print_line("%clr#{response_color}#{response}%clr")
351-
else
352-
print_line('No response received')
353-
end
355+
print_line("%clr#{response_color}#{response}%clr")
356+
else
357+
print_line('No response received')
354358
end
359+
end
355360

356-
disconnect(c) if disconnect
361+
disconnect(c) if disconnect
357362

358-
res
359-
rescue ::Errno::EPIPE, ::Timeout::Error => e
360-
print_line(e.message) if datastore['HttpTrace']
361-
nil
362-
rescue Rex::ConnectionError => e
363-
vprint_error(e.to_s)
364-
nil
365-
rescue ::Exception => e
366-
print_line(e.message) if datastore['HttpTrace']
367-
raise e
368-
end
363+
res
364+
rescue ::Errno::EPIPE, ::Timeout::Error => e
365+
print_line(e.message) if datastore['HttpTrace']
366+
nil
367+
rescue Rex::ConnectionError => e
368+
vprint_error(e.to_s)
369+
nil
370+
rescue ::Exception => e
371+
print_line(e.message) if datastore['HttpTrace']
372+
raise e
369373
end
370374

371-
372375
# Connects to the server, creates a request, sends the request,
373376
# reads the response
374377
#
375378
# Passes `opts` through directly to {Rex::Proto::Http::Client#request_cgi}.
379+
# Set `opts['keep_cookies']` to keep cookies from responses for reuse in requests.
376380
#
377381
# @return (see Rex::Proto::Http::Client#send_recv))
378-
def send_request_cgi(opts={}, timeout = 20, disconnect = true)
379-
send_request_raw(opts.merge(cgi: true), timeout, disconnect)
382+
def send_request_cgi(opts = {}, timeout = 20, disconnect = true)
383+
if cookie_jar.any?
384+
opts = { 'cookie' => cookie_jar.to_a.join(' ') }.merge(opts)
385+
end
386+
387+
res = send_request_raw(opts.merge(cgi: true), timeout, disconnect)
388+
389+
return unless res
390+
return res unless res.headers['Set-Cookie'].present?
391+
392+
if opts['keep_cookies'] || datastore['HttpKeepCookies']
393+
# XXX: CGI::Cookie (get_cookies_parsed) is hella broken
394+
cookie_jar.merge(res.get_cookies.split(' '))
395+
end
396+
397+
res
380398
end
381399

382400
# Connects to the server, creates a request, sends the request, reads the
@@ -871,10 +889,10 @@ def service_details
871889
}
872890
end
873891

874-
protected
892+
protected
875893

876894
attr_accessor :client
895+
attr_accessor :cookie_jar
877896

878897
end
879-
880898
end

0 commit comments

Comments
 (0)