Class: Watobo::Interceptor::Proxy

Inherits:
Object
  • Object
show all
Includes:
Constants
Defined in:
lib/watobo/interceptor/proxy.rb

Constant Summary

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 Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(settings = nil) ⇒ Proxy

Returns a new instance of Proxy.



424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
# File 'lib/watobo/interceptor/proxy.rb', line 424

def initialize(settings=nil)
  @event_dispatcher_listeners = Hash.new
  @pass_through_hosts = ['safebrowsing.*google\.com', 'download.cdn.mozilla.net', 'shavar.services.mozilla.com']
  begin

    puts
    puts "=== Initialize Interceptor/Proxy ==="

    #Watobo::Interceptor.proxy_mode = INTERCEPT_NONE

    init_instance_vars

    @srv_path = File.join(File.dirname(__FILE__), 'html')

    @awaiting_requests = 0
    @awaiting_responses = 0

    @request_filter_settings = {
        :site_in_scope => false,
        :method_filter => '(get|post|put)',
        :negate_method_filter => false,
        :negate_url_filter => false,
        :url_filter => '',
        :file_type_filter => '(jpg|gif|png|jpeg|bmp)',
        :negate_file_type_filter => true,

        :parms_filter => '',
        :negate_parms_filter => false
        #:regex_location => 0, # TODO: HEADER_LOCATION, BODY_LOCATION, ALL

    }

    @response_filter_settings = {
        :content_type_filter => '(text|script)',
        :negate_content_type_filter => false,
        :response_code_filter => '2\d{2}',
        :negate_response_code_filter => false,
        :request_intercepted => false,
        :content_printable => true,
        :enable_printable_check => false
    }

    @preview = Hash.new
    @preview['ProxyTest'] = ["HTTP/1.0 200 OK\r\nServer: Watobo-Interceptor\r\nConnection: close\r\nContent-Type: text/html; charset=iso-8859-1\r\n\r\n<html><body>PROXY_OK</body></html>"]

    @dh_key = Watobo::CA.dh_key

  rescue => bang
    puts "!!!could not read certificate files:"
    puts bang
    puts bang.backtrace if $DEBUG
  end

end

Instance Attribute Details

#client_certificatesObject

attr :www_auth



17
18
19
# File 'lib/watobo/interceptor/proxy.rb', line 17

def client_certificates
  @client_certificates
end

#portObject (readonly)

Returns the value of attribute port.



9
10
11
# File 'lib/watobo/interceptor/proxy.rb', line 9

def port
  @port
end

#proxy_modeObject

Returns the value of attribute proxy_mode.



11
12
13
# File 'lib/watobo/interceptor/proxy.rb', line 11

def proxy_mode
  @proxy_mode
end

#targetObject

attr_accessor :contentLength attr_accessor :contentTypes



15
16
17
# File 'lib/watobo/interceptor/proxy.rb', line 15

def target
  @target
end

Class Method Details

.start(settings = {}) ⇒ Object

R U N



129
130
131
132
133
# File 'lib/watobo/interceptor/proxy.rb', line 129

def self.start(settings = {})
  proxy = Proxy.new(settings)
  proxy.start
  proxy
end

.transparent?Boolean

Returns:

  • (Boolean)


19
20
21
22
# File 'lib/watobo/interceptor/proxy.rb', line 19

def self.transparent?
  return true if (Watobo::Conf::Interceptor.proxy_mode & Watobo::Interceptor::MODE_TRANSPARENT) > 0
  return false
end

Instance Method Details

#addPreview(response) ⇒ Object



105
106
107
108
109
# File 'lib/watobo/interceptor/proxy.rb', line 105

def addPreview(response)
  preview_id = Digest::MD5.hexdigest(response.join)
  @preview[preview_id] = response
  return preview_id
end

#cert_responseObject



57
58
59
60
61
62
63
64
65
# File 'lib/watobo/interceptor/proxy.rb', line 57

def cert_response
  crt_file = File.join(Watobo.working_directory, "CA", "cacert.pem")
  headers = ["HTTP/1.0 200 OK", "Server: Watobo-Interceptor", "Connection: close", "Content-Type: application/x-pem-file"]
  content = File.read(crt_file)
  headers << "Content-Length: #{content.length}"
  r = headers.join("\r\n")
  r << "\r\n\r\n"
  r << content
end

#clear_request_carversObject



96
97
98
99
# File 'lib/watobo/interceptor/proxy.rb', line 96

def clear_request_carvers
  @request_carvers.clear unless @request_carvers.nil?

end

#clear_response_carversObject



101
102
103
# File 'lib/watobo/interceptor/proxy.rb', line 101

def clear_response_carvers
  @response_carvers.clear unless @response_carvers.nil?
end

#clearEvents(event) ⇒ Object



75
76
77
# File 'lib/watobo/interceptor/proxy.rb', line 75

def clearEvents(event)
  @event_dispatcher_listener[event].clear
end

#getRequestFilterObject



83
84
85
# File 'lib/watobo/interceptor/proxy.rb', line 83

def getRequestFilter()
  YAML.load(YAML.dump(@request_filter_settings))
end

#getResponseFilterObject



79
80
81
# File 'lib/watobo/interceptor/proxy.rb', line 79

def getResponseFilter()
  YAML.load(YAML.dump(@response_filter_settings))
end

#refresh_www_authObject



420
421
422
# File 'lib/watobo/interceptor/proxy.rb', line 420

def refresh_www_auth
  @www_auth = Watobo::Conf::Scanner.www_auth
end

#serverObject



67
68
69
# File 'lib/watobo/interceptor/proxy.rb', line 67

def server
  @bind_addr
end

#setRequestFilter(new_settings) ⇒ Object



91
92
93
94
# File 'lib/watobo/interceptor/proxy.rb', line 91

def setRequestFilter(new_settings)
  @request_filter_settings.update new_settings unless new_settings.nil?
  # puts @request_filter_settings.to_yaml
end

#setResponseFilter(new_settings) ⇒ Object



87
88
89
# File 'lib/watobo/interceptor/proxy.rb', line 87

def setResponseFilter(new_settings)
  @response_filter_settings.update new_settings unless new_settings.nil?
end

#startObject



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
296
297
298
299
300
301
302
303
304
305
306
307
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
# File 'lib/watobo/interceptor/proxy.rb', line 135

def start()
  @wait_queue = Queue.new

  if transparent?
    Watobo::Interceptor::Transparent.start
  end

  begin
    @intercept_srv = TCPServer.new(@bind_addr, @port)
    @intercept_srv.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, 1)

  rescue => bang
    puts "\n!!!Could not start InterceptProxy"
    puts bang
    return nil
  end
  puts "\n* Intercepor started on #{@bind_addr}:#{@port}"
  session_list = []
  puts "!!! TRANSPARENT MODE ENABLED !!!" if transparent?

  @t_server = Thread.new(@intercept_srv) { |server|
    while (new_session = server.accept)
      #  new_session.sync = true
      new_sender = Watobo::Session.new(@target)
      Thread.new(new_sender, new_session) { |sender, session|
        #puts "* got new request from client"
        c_sock = Watobo::HTTPSocket::ClientSocket.connect(session)

        #puts "ClientSocket: #{c_sock}"
        Thread.exit if c_sock.nil?

        #
        # loop for reusing client connections

        max_loop = 0
        loop do
          flags = []
          begin

            # puts "#{c_sock} - read request"
            request = c_sock.request

            #if request.is_multipart?
            #  puts request
            #  puts request.body.to_s.length
            #  puts request.body.to_s.unpack("H*")[0]
            #end

            if request.nil? or request.empty? then
              print "c/"
              c_sock.close
              Thread.exit
            end

            url = (request.url.to_s.length > 65) ? request.url.to_s.slice(0, 65) + "..." : request.url.to_s
            puts "\n[I] #{url}"

          rescue => bang
            puts "!!! Error reading client request "
            puts bang
            puts bang.backtrace
            # puts request.class
            # puts request
            c_sock.close
            Thread.exit
            #break
          end

          #if request.host =~ /safebrowsing.*google\.com/
          #  c_sock.close
          #  Thread.exit
          #end

          # check if preview is requested
          if request.host =='watobo.localhost' or request.first =~ /WATOBOPreview/ then
            if request.first =~ /WATOBOPreview=([0-9a-zA-Z]*)/ then
              hashid = $1
              response = @preview[hashid]

              if response then
                c_sock.write response.join
                c_sock.close
              end
            end
            #next
            Thread.exit
          end

          # check for watobo info page
          if request.host =~ /^watobo$/
            if request.path =~ /watobo\.pem/
              response = cert_response
            else
              response = watobo_srv_get(request.path)
            end

            c_sock.write response
            c_sock.close
            Thread.exit
          end

          request_intercepted = false
          # no preview, check if interception request is turned on
          if Watobo::Interceptor.rewrite_requests? then
            Interceptor::RequestCarver.shape(request, flags)
            puts "FLAGS >>"
            puts flags
          end

          if @target and Watobo::Interceptor.intercept_requests? then
            if matchRequestFilter(request)
              @awaiting_requests += 1
              request_intercepted = true

              if @target.respond_to? :addRequest
                Watobo.print_debug "send request to target"
                @target.addRequest(request, Thread.current)
                Thread.stop
              else
                p "! no target for editing request"
              end
              @awaiting_requests -= 1
            end
          end

          begin
            s_sock, req, resp = sender.sendHTTPRequest(request, :update_sids => true,
                                                       :update_session => false,
                                                       :update_contentlength => true,
                                                       :www_auth => @www_auth
            # :client_certificates => @client_certificates
            )
            if s_sock.nil? then
              puts "s_sock is nil! bye, bye, ..."
              puts request if $DEBUG
              c_sock.write resp.join unless resp.nil?
              c_sock.close
              Thread.exit
            end

          rescue => bang
            puts bang
            puts bang.backtrace if $DEBUG
            c_sock.close
            Thread.exit
          end

          # check if response should be passed through
          #Thread.current.exit if isPassThrough?(req, resp, s_sock, c_sock)
          if isPassThrough?(req, resp, s_sock, c_sock)
            #puts "[Interceptor] PassThrough >> #{req.url}"
            Watobo::HTTPSocket.close s_sock
            c_sock.close
            Thread.exit
          end

          begin
            missing_credentials = false
            rs = resp.status
            auth_type = AUTH_TYPE_NONE
            if rs =~ /^(401|407)/ then

              missing_credentials = true

              resp.each do |rl|
                if rl =~ /^(Proxy|WWW)-Authenticate: Basic/i
                  auth_type = AUTH_TYPE_BASIC
                  break
                elsif rl =~ /^(Proxy|WWW)-Authenticate: NTLM/i
                  auth_type = AUTH_TYPE_NTLM
                  break
                end
              end
              # when auth type not basic assume it's ntlm -> ntlm credentials must be set in watobo
              unless auth_type == AUTH_TYPE_NONE
                if auth_type == AUTH_TYPE_NTLM
                  if rs =~ /^401/ then
                    resp.push "WATOBO: Server requires (NTLM) authorization, please set WWW_Auth Credentials!"
                    resp.shift
                    resp.unshift "HTTP/1.1 200 OK\r\n"
                  else
                    resp.push "WATOBO: Proxy requires (NTLM) authorization, please set Proxy Credentials!"
                    resp.shift
                    resp.unshift "HTTP/1.1 200 OK\r\n"
                  end
                end
              end
            end

            # don't try to read body if request method is HEAD
            unless auth_type == AUTH_TYPE_UNKNOWN or req.method =~ /^head/i
              sender.readHTTPBody(s_sock, resp, req, :update_sids => true)
              Watobo::HTTPSocket.close s_sock
            end

          rescue => bang
            puts "!!! could not send request !!!"
            puts bang
            puts bang.backtrace if $DEBUG
            #  puts "* Error sending request"
          end

          begin
            # Watobo::Response.create resp
            #resp = Watobo::Response.new resp
            # puts "* unchunk response ..."
            resp.unchunk!
            # puts "* unzip response ..."
            resp.unzip!

            if Watobo::Interceptor.rewrite_responses? then
              Interceptor::ResponseCarver.shape(resp, flags)
            end

            if @target and Watobo::Interceptor.intercept_responses? then
              if matchResponseFilter(resp)
                #  if resp.content_type =~ /text/ or resp.content_type =~ /application\/javascript/ then
                if @target.respond_to? :modifyResponse
                  @target.modifyResponse(resp, Thread.current)
                  Thread.stop
                else
                  p "! no target for editing response"
                end
              end
            end

            # puts ">> SEND TO CLIENT"
            # puts ">>C<< - Close: #{request.connection_close?}"
            # request.headers("Connection"){ |h| puts h }

            if missing_credentials
              resp.set_header("Connection", "close")
            elsif request.connection_close? or resp.content_length < 0 or max_loop > 4
              # resp.set_header("Proxy-Connection","close")
              resp.set_header("Connection", "close")
            else
              resp.set_header("Connection", "keep-alive")
              resp.set_header("Keep-Alive", "max=4, timeout=120")
            end

            resp_data = resp.join
            c_sock.write resp_data

            chat = Chat.new(request.copy, resp.copy, :source => CHAT_SOURCE_INTERCEPT)
            Watobo::Chats.add chat

          rescue Errno::ECONNRESET
            print "x"
            #  puts "!!! ERROR (Reset): reading body"
            #  puts "* last data seen on socket: #{buf}"
            #return
            c_sock.close
            Thread.exit
          rescue Errno::ECONNABORTED
            print "x"
            #return
            c_sock.close
            Thread.exit
          rescue => bang
            puts "!!! Error (???) in Client Communication:"
            puts bang
            puts bang.class
            puts bang.backtrace #if $DEBUG
            #return
            c_sock.close
            Thread.exit
          end


          # TODO: place check into ClientSocket, because headers must be checked and changed too
          # e.g. if c_sock.open?
          if missing_credentials or request.connection_close? or resp.content_length < 0 or max_loop > 4
            c_sock.close
            Thread.exit
          end

          max_loop += 1

        end
      }

    end
  }
end

#stopObject



111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/watobo/interceptor/proxy.rb', line 111

def stop()
  begin
    puts "[#{self.class}] stop"
    if @t_server.respond_to? :status
      puts @t_server.status
      Thread.kill @t_server
      @intercept_srv.close
    end
  rescue IOError => bang
    puts bang
    puts bang.backtrace if $DEBUG
  end
end

#subscribe(event, &callback) ⇒ Object



71
72
73
# File 'lib/watobo/interceptor/proxy.rb', line 71

def subscribe(event, &callback)
  (@event_dispatcher_listeners[event] ||= []) << callback
end

#watobo_srv_get(file) ⇒ Object



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/watobo/interceptor/proxy.rb', line 25

def watobo_srv_get(file)
  srv_file = file.empty? ? File.join(@srv_path, 'index.html') : File.join(@srv_path, file)
  if File.exist? srv_file
    ct = case srv_file
           when /\.ico/
             "image/vnd.microsoft.icon"
           when /\.htm/
             'text/html; charset=iso-8859-1'
           else
             'text/plain'
         end
    headers = ["HTTP/1.0 200 OK", "Server: Watobo-Interceptor", "Connection: close", "Content-Type: #{ct}"]
    content = File.open(srv_file, "rb").read
    content.gsub!('WATOBO_VERSION', Watobo::VERSION)
    content.gsub!('WATOBO_HOME', Watobo.working_directory)
    headers << "Content-Length: #{content.length}"
    r = headers.join("\r\n")
    r << "\r\n\r\n"
    r << content
    return r
  end

  headers = ["HTTP/1.0 404 Not Found", "Server: Watobo-Interceptor", "Connection: close", "Content-Type: text/plain; charset=iso-8859-1"]
  content = "The requested file (#{file}) does not exist in the interceptor web folder."
  headers << "Content-Length: #{content.length}"
  r = headers.join("\r\n")
  r << "\r\n\r\n"
  r << content
  return r

end