Class: Watobo::Session

Inherits:
Object
  • Object
show all
Includes:
Constants, Subscriber
Defined in:
lib/watobo/core/session.rb

Overview

:nodoc: all

Constant Summary collapse

@@settings =
Hash.new
@@proxy =
Hash.new
@@session_lock =
Mutex.new
@@login_mutex =
Mutex.new
@@login_cv =
ConditionVariable.new
@@login_in_progress =
false

Constants included from Constants

Constants::AC_GROUP_APACHE, Constants::AC_GROUP_DOMINO, Constants::AC_GROUP_ENUMERATION, Constants::AC_GROUP_FILE_INCLUSION, Constants::AC_GROUP_FLASH, Constants::AC_GROUP_GENERIC, Constants::AC_GROUP_JBOSS, Constants::AC_GROUP_JOOMLA, Constants::AC_GROUP_SAP, Constants::AC_GROUP_SQL, Constants::AC_GROUP_TYPO3, Constants::AC_GROUP_XSS, Constants::AUTH_TYPE_BASIC, Constants::AUTH_TYPE_DIGEST, Constants::AUTH_TYPE_NONE, Constants::AUTH_TYPE_NTLM, Constants::AUTH_TYPE_UNKNOWN, Constants::CHAT_SOURCE_AUTO_SCAN, Constants::CHAT_SOURCE_FUZZER, Constants::CHAT_SOURCE_INTERCEPT, Constants::CHAT_SOURCE_MANUAL, Constants::CHAT_SOURCE_MANUAL_SCAN, Constants::CHAT_SOURCE_PROXY, Constants::CHAT_SOURCE_UNDEF, Constants::DEFAULT_PORT_HTTP, Constants::DEFAULT_PORT_HTTPS, Constants::FINDING_TYPE_HINT, Constants::FINDING_TYPE_INFO, Constants::FINDING_TYPE_UNDEFINED, Constants::FINDING_TYPE_VULN, Constants::FIRST_TIME_FILE, Constants::GUI_REGULAR_FONT_SIZE, Constants::GUI_SMALL_FONT_SIZE, Constants::ICON_PATH, Constants::LOG_DEBUG, Constants::LOG_INFO, Constants::SCAN_CANCELED, Constants::SCAN_FINISHED, Constants::SCAN_PAUSED, Constants::SCAN_STARTED, Constants::TE_CHUNKED, Constants::TE_COMPRESS, Constants::TE_DEFLATE, Constants::TE_GZIP, Constants::TE_IDENTITY, Constants::TE_NONE, Constants::VULN_RATING_CRITICAL, Constants::VULN_RATING_HIGH, Constants::VULN_RATING_INFO, Constants::VULN_RATING_LOW, Constants::VULN_RATING_MEDIUM, Constants::VULN_RATING_UNDEFINED

Instance Method Summary collapse

Methods included from Subscriber

#clearEvents, #notify, #subscribe

Constructor Details

#initialize(session_id = nil, prefs = {}) ⇒ Session

INITIALIZE

Possible preferences: :proxy => ‘127.0.0.1:port’ :valid_sids => Hash.new, :sid_patterns => [], :logout_signatures => [], :update_valid_sids => false, :update_sids => false, :update_contentlength => true



465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
# File 'lib/watobo/core/session.rb', line 465

def initialize( session_id=nil, prefs={} )

  @event_dispatcher_listeners = Hash.new
  #     @session = {}

  session = nil

  session = ( session_id.is_a? Fixnum ) ? session_id : session_id.object_id
  session = Digest::MD5.hexdigest(Time.now.to_f.to_s) if session_id.nil?

  @sid_cache = Watobo::SIDCache.acquire(session)

  unless @@settings.has_key? session
    @@settings[session] = {
      :logout_signatures => [],
      :logout_content_types => Hash.new,
      :update_valid_sids => false,
      :update_sids => false,
      :update_otts => false,           
      :update_session => true,
      :update_contentlength => true,
      :login_chats => [],
      :www_auth => Hash.new,
      :client_certificates => {},
      :proxy_auth => Hash.new
    }
  end
  @session = @@settings[session] # shortcut to settings
  @session.update prefs

  #  @valid_csrf_tokens = Hash.new

  addProxy( prefs[:proxy] ) if prefs.is_a? Hash and prefs[:proxy]

  @socket = nil

  @ctx = OpenSSL::SSL::SSLContext.new()
  @ctx.key = nil
  @ctx.cert = nil

  # TODO: Implement switches for URL-Encoding (http://www.blooberry.com/indexdot/html/topics/urlencoding.htm)
  # TODO: Implement switches for Following Redirects
  # TODO: Implement switches for Logging, Debugging, ...
end

Instance Method Details

#addProxy(prefs = nil) ⇒ Object



428
429
430
431
432
433
434
435
436
437
438
439
440
441
# File 'lib/watobo/core/session.rb', line 428

def addProxy(prefs=nil)

  proxy = nil
  unless prefs.nil?
    proxy = Proxy.new(prefs)
    #  proxy.setCredentials(prefs[:credentials]) unless prefs[:credentials].nil?
    unless prefs[:site].nil?
      @@proxy[prefs[:site]] = proxy
      return
    end
  end

  @@proxy[:default] = proxy
end

#doRequest(request, opts = {}) ⇒ Object

+++ doRequest(request) +++ + function:



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
422
423
424
425
426
# File 'lib/watobo/core/session.rb', line 309

def doRequest(request, opts={} )
  begin
    ott_cache = Watobo::OTTCache.acquire(request)
    @session.update opts
    #  puts "[doRequest] #{@session.to_yaml}"
    # puts "#[#{self.class}]" + @session[:csrf_requests].first.object_id.to_s
    # unless @session[:csrf_requests].empty? or @session[:csrf_patterns].empty?
    unless Watobo::OTTCache.requests(request).empty? or @session[:update_otts] == false
      Watobo::OTTCache.requests(request).each do |req|

        copy = Watobo::Request.new YAML.load(YAML.dump(req))

        #updateCSRFToken(csrf_cache, copy)
        ott_cache.update_request(copy)
        socket, csrf_request, csrf_response = sendHTTPRequest(copy, opts)
        next if socket.nil?
        #  puts "= Response Headers:"
        #  puts csrf_response
        #  puts "==="
        #update_sids(csrf_request.host, csrf_response.headers)
        @sid_cache.update_sids(csrf_request.site, csrf_response.headers) if @session[:update_sids] == true
        next if socket.nil?
        #  p "*"
        #    csrf_response = readHTTPHeader(socket)
        readHTTPBody(socket, csrf_response, csrf_request, opts)

        # response = Response.new(csrf_response)


        next unless csrf_response.has_body?

        csrf_response.unchunk!
        csrf_response.unzip! 

        @sid_cache.update_sids(csrf_request.site, [csrf_response.body]) if @session[:update_sids] == true

        # updateCSRFCache(csrf_cache, csrf_request, [csrf_response.body]) if csrf_response.content_type =~ /text\//
        ott_cache.update_tokens( [csrf_response.body]) if csrf_response.content_type =~ /text\//

        # socket.close
        closeSocket(socket)
      end
      #p @session[:csrf_requests].length
      #updateCSRFToken(csrf_cache, request)
      ott_cache.update_request(request)
    end

    socket, request, response = sendHTTPRequest(request, opts)

    if socket.nil?
      return request, response
      #return request, nil
    end

    @sid_cache.update_sids(request.site, response.headers) if @session[:update_sids] == true

    if @session[:follow_redirect]
      # puts response.status
      if response.status =~ /^30(1|2|8)/
        #response.extend Watobo::Mixin::Parser::Web10
        #request.extend Watobo::Mixin::Shaper::Web10

        loc_header = response.headers("Location:").first
        new_location = loc_header.gsub(/^[^:]*:/,'').strip
        unless new_location =~ /^http/
          if new_location =~ /^\//
            new_location = request.proto + "://" + request.site  + new_location      
          else
            new_location = request.proto + "://" + request.site + "/" + request.dir + "/" + new_location.sub(/^[\.\/]*/,'')
          end
        end

        notify(:follow_redirect, new_location)
        nr = Watobo::Request.new YAML.load(YAML.dump(request))

        # create GET request for new location
        nr.replaceMethod("GET")
        nr.removeHeader("Content-Length")
        nr.removeBody()
        nr.replaceURL(new_location)


        socket, request, response = sendHTTPRequest(nr, opts)

        if socket.nil?
          #return nil, request
          return request, response
        end
      end
    end

    readHTTPBody(socket, response, request, opts)

    unless response.body.nil?
      @sid_cache.update_sids(request.site, [response.body]) if @session[:update_sids] == true and response.content_type =~ /text\//
    end

    #socket.close
    closeSocket(socket)

  rescue  => bang
    #  puts "! Error in doRequest"
    puts "! Module #{Module.nesting[0].name}"
    puts bang
    puts bang.backtrace if $DEBUG
    @lasterror = bang
    # raise
    # ensure
  end

  #response.extend Watobo::Mixin::Parser::Web10
  # resp = Watobo::Response.new(response)

  response.unchunk!
  response.unzip!

  return Request.new(request), response
end

#get_settingsObject



443
444
445
# File 'lib/watobo/core/session.rb', line 443

def get_settings
  @@settings
end

#getProxy(site = nil) ⇒ Object



447
448
449
450
451
452
# File 'lib/watobo/core/session.rb', line 447

def getProxy(site=nil)
  unless site.nil?
    return @@proxy[site] unless @@proxy[site].nil?
  end
  return @@proxy[:default]
end

#readHTTPBody(socket, response, request, prefs = {}) ⇒ Object



510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
# File 'lib/watobo/core/session.rb', line 510

def readHTTPBody(socket, response, request, prefs={})
  clen = response.content_length
  data = ""

  begin
    if response.is_chunked?
      Watobo::HTTPSocket.readChunkedBody(socket) { |c|
        data += c
      }
    elsif  clen > 0
      #  puts "* read #{clen} bytes for body"
      Watobo::HTTPSocket.read_body(socket, :max_bytes => clen) { |c|

        data += c
        break if data.length == clen
      }
    elsif clen < 0
      # puts "* no content-length information ... mmmmmpf"
      # eofcount = 0
      Watobo::HTTPSocket.read_body(socket) do |c|
        data += c
      end

    end

    response.push data unless data.empty?
    unless prefs[:ignore_logout]==true  or @session[:logout_signatures].empty?
      notify(:logout, self) if loggedOut?(response)
    end

    @sid_cache.update_sids(request.site, response) if prefs[:update_sids] == true
    return true

  rescue => e
    puts "! Could not read response"
    puts e
    # puts e.backtrace
  end

  return false
end

#runLogin(chat_list, prefs = {}) ⇒ Object



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/watobo/core/session.rb', line 19

def runLogin(chat_list, prefs={})
  #puts @session.object_id
  @@login_mutex.synchronize do
    begin
      @@login_in_progress = true
       = Hash.new
      .update prefs
      dummy = {:ignore_logout => true, :update_sids => true, :update_session => true, :update_contentlength => true}
      .update dummy
      puts "! Start Login ..." if $DEBUG
      unless chat_list.empty?
        #  puts login_prefs.to_yaml
        chat_list.each do |chat|
          puts "! LoginRequest: #{chat.id}" if $DEBUG
          test_req = chat.copyRequest
          request, response = doRequest(test_req, )
        end
      else
        puts "! no login script configured !"
      end
    rescue => bang
      puts "!ERROR in runLogin"
      puts bang.backtrace if $DEBUG
    ensure
      @@login_in_progress = false
      @@login_cv.signal

    end
  end
end

#sendHTTPRequest(request, prefs = {}) ⇒ Object

sendHTTPRequest returns Socket, ResponseHeader



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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
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
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
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
# File 'lib/watobo/core/session.rb', line 56

def sendHTTPRequest(request, prefs={})
  begin
    @lasterror = nil
    response_header = nil

    site = request.site
    #   proxy = getProxy(site)
    proxy = Watobo::ForwardingProxy.get(site)

    unless proxy.nil?
      host = proxy.host
      port = proxy.port
    else
      host = request.host
      port = request.port
    end
    # check if hostname is valid and can be resolved
    hostip = IPSocket.getaddress(host)
    # update current preferences, prefs given here are stronger then global settings!
    current_prefs = Hash.new
    [:update_session, :update_sids, :update_contentlength, :ssl_cipher, :www_auth, :client_certificates, :egress_handler ].each do |k|
      current_prefs[k] = prefs[k].nil? ? @session[k] : prefs[k]
    end

    @sid_cache.update_request(request) if current_prefs[:update_session] == true

    #---------------------------------------
    # request.removeHeader("^Proxy-Connection") #if not use_proxy
    # request.removeHeader("^Connection") #if not use_proxy

    # !!!
    # remove Accept-Encoding header
    # otherwise we won't get the content-length information for pass-through feature
    request.removeHeader("^Accept-Encoding")
    # If-Modified-Since: Tue, 28 Oct 2008 11:06:43 GMT
    # If-None-Match: W/"3975-1225192003000"
    # request.removeHeader("^If-")
    #  puts
    #  request.each do |line|
    #  puts line.unpack("H*")
    #end
    #puts
    if current_prefs[:update_contentlength] == true and request.has_body? then
      #puts request.body.unpack("H*")[0]
      #puts (request.body.unpack("H*")[0].length / 2).to_s
      request.fix_content_length()
      #puts "New: #{request.content_length}"
      #puts request.body.encoding
      #puts "--"
    end

    #
    # Engress Handler
    unless current_prefs[:egress_handler].nil?
      unless current_prefs[:egress_handler].empty?
        h = Watobo::EgressHandlers.create current_prefs[:egress_handler]
        unless h.nil?
          h.execute request
        end
      end
    end


    #request.add_header("Via", "Watobo") if use_proxy
    #puts request
    # puts "=============="
  rescue SocketError
    puts "!!! unknown hostname #{host}"
    puts request.first
    return nil, "WATOBO: Could not resolve hostname #{host}", nil
  rescue => bang
    puts bang
    puts bang.backtrace if $DEBUG
  end

  begin
    unless proxy.nil?
      # connection requires proxy
      # puts "* use proxy #{proxy.name}"

      # check for regular proxy authentication
      if request.is_ssl?
        socket, response_header = sslProxyConnect(request, proxy, current_prefs)
        return socket, response_header, error_response("Could Not Connect To Proxy: #{proxy.name} (#{proxy.host}:#{proxy.port})\n", "#{response_header}") if socket.nil?

        if current_prefs[:www_auth].has_key?(site)
          case current_prefs[:www_auth][site][:type]
          when AUTH_TYPE_NTLM
            #  puts "* found NTLM credentials for site #{site}"
            socket, response_header = wwwAuthNTLM(socket, request, current_prefs[:www_auth][site])

          else
            puts "* Unknown Authentication Type: #{current_prefs[:www_auth][site][:type]}"
          end
        else

          data = request.join + "\r\n"
          unless socket.nil?
            socket.print data
            response_header = readHTTPHeader(socket, current_prefs)
          end
        end
        return socket, Request.new(request), Response.new(response_header)
      end
      #  puts "* doProxyRequest"
      socket, response_header = doProxyRequest(request, proxy, current_prefs)
      #   puts socket.class
      return socket, response_header, error_response("Could Not Connect To Proxy: #{proxy.name} (#{proxy.host}:#{proxy.port})\n", "#{response_header}") if socket.nil?

      return socket, Request.new(request), Response.new(response_header)
    else
      # direct connection to host
      tcp_socket = nil
      #  timeout(6) do
      #puts "* no proxy - direct connection"
      tcp_socket = TCPSocket.new( host, port )
      #optval = [1, 5000].pack("I_2")
      #tcp_socket.setsockopt Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, optval
      #tcp_socket.setsockopt Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, optval    
      tcp_socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)    
      #tcp_socket.setsockopt Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, 1
      tcp_socket.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)

      tcp_socket.sync = true

      socket =  tcp_socket
      if request.is_ssl?
        ssl_prefs = {}
        ssl_prefs[:ssl_cipher] = current_prefs[:ssl_cipher] if current_prefs.has_key? :ssl_cipher
        #if current_prefs.has_key? :client_certificates
        #  if current_prefs[:client_certificates].has_key? request.site
        #    puts "* use ssl client certificate for site #{request.site}" if $DEBUG
        #    ssl_prefs[:ssl_client_cert] = current_prefs[:client_certificates][request.site][:ssl_client_cert] 
        #    ssl_prefs[:ssl_client_key] = current_prefs[:client_certificates][request.site][:ssl_client_key]
        #  end              
        #end
        unless Watobo::ClientCertStore.get(site).nil?
          # puts "* using client cert for site #{site}"
          client_cert = Watobo::ClientCertStore.get(site)
          ssl_prefs[:client_certificate] = client_cert
        end
        # need hostname for SNI (Server Name Indication)
        # http://en.wikipedia.org/wiki/Server_Name_Indication
        ssl_prefs[:hostname] = host
        socket = sslConnect(tcp_socket, ssl_prefs)
        # puts "SSLSocket " + (socket.nil? ? "NO" : "OK")
        return nil, request, [] if socket.nil?
      end
      #puts socket.class
      # remove URI before sending request but cache it for restoring request
      uri_cache = nil
      uri_cache = request.removeURI #if proxy.nil?
      # request.addHeader("Proxy-Connection", "Close") unless proxy.nil?
      # request.set_header("Accept-Encoding", "gzip;q=0;identity; q=0.5, *;q=0") #don't want encoding

      # request.set_header("Connection", "close") unless request.has_header?("Upgrade") 

      if current_prefs[:www_auth].has_key?(site)
        case current_prefs[:www_auth][site][:type]
        when AUTH_TYPE_NTLM
          # puts "* found NTLM credentials for site #{site}"
          socket, response_header = wwwAuthNTLM(socket, request, current_prefs[:www_auth][site])
          request.restoreURI(uri_cache)

        else
          puts "* Unknown Authentication Type: #{current_prefs[:www_auth][site][:type]}"
        end
      else
        # puts "========== Add Headers"

        request.set_header("Connection", "close") #if not use_proxy

        data = request.join
        unless request.has_body? 
          data << "\r\n" unless data =~ /\r\n\r\n$/ 
        end

        #  puts "\n*** SENDING ..."
        #  puts data
        #  if request.has_body?
        #    puts request.body.length
        #    bhex = request.body.unpack("H*")[0]
        #    puts bhex
        #    puts bhex.length
        #  end



        #puts "= SESSION ="
        #puts data
        #puts data.unpack("H*")[0]#.gsub(/0d0a/,"0d0a\n")
        # puts "---"
        unless socket.nil?                
          socket.print data
          socket.flush
          response_header = readHTTPHeader(socket, current_prefs)
        end
        # RESTORE URI FOR HISTORY/LOG
        request.restoreURI(uri_cache)

      end
      return socket, Watobo::Request.new(request), Watobo::Response.new(response_header)
    end

  rescue Errno::ECONNREFUSED
    response = error_response "connection refused (#{host}:#{port})"
    puts response
    socket = nil
  rescue Errno::ECONNRESET
    response = error_response "connection reset (#{host}:#{port})"
    socket = nil
  rescue Errno::ECONNABORTED
    response = error_response "connection aborted (#{host}:#{port})"
    socket = nil
  rescue Errno::EHOSTUNREACH
    response = error_response "host unreachable (#{host}:#{port})"
    socket = nil
  rescue Timeout::Error
    #request = "WATOBO: TimeOut (#{host}:#{port})\n"
    response = error_response "TimeOut (#{host}:#{port})"
    socket = nil
  rescue Errno::ETIMEDOUT
    puts "TimeOut (#{host}:#{port})"
    response = error_response "TimeOut (#{host}:#{port})"
    socket = nil
  rescue Errno::ENOTCONN
    puts "!!!ENOTCONN"
  rescue OpenSSL::SSL::SSLError
    response = error_response "SSL-Error", $!.to_s + "<br>" + $!.backtrace.join("<br>")
    socket = nil
  rescue => bang
    response = error_response "ERROR:", "#{bang}\n#{bang.backtrace}"
    socket = nil

    puts bang
    puts bang.backtrace if $DEBUG
  end
  #puts response
  return socket, request, response
end

#sessionSettingsObject



50
51
52
# File 'lib/watobo/core/session.rb', line 50

def sessionSettings()
  @@settings
end

#setSIDCache(new_cache = {}) ⇒ Object



302
303
304
# File 'lib/watobo/core/session.rb', line 302

def setSIDCache(new_cache = {} )
  @session[:valid_sids] = new_cache if new_cache.is_a? Hash
end

#sidCacheObject



297
298
299
300
# File 'lib/watobo/core/session.rb', line 297

def sidCache()
  #puts @project
  @session[:valid_sids]
end