bscan 1.4.5 → 2.0.0
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.
- data/CONFIG.rdoc +18 -7
- data/README.rdoc +6 -3
- data/VERSION +1 -1
- data/bin/bscan +41 -3
- data/bscan.gemspec +2 -2
- data/lib/bscan.rb +11 -213
- data/lib/bscan/modules/injector.rb +23 -24
- data/lib/bscan/modules/kill_apache.rb +40 -36
- data/lib/bscan/modules/many_threads.rb +7 -8
- data/lib/bscan/modules/slowloris.rb +115 -100
- data/lib/bscan/utils/bscan_helper.rb +304 -11
- data/release_notes.txt +5 -0
- data/test.sh +1 -1
- metadata +2 -2
@@ -1,4 +1,28 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'java'
|
3
|
+
require "socket"
|
4
|
+
require "openssl"
|
5
|
+
require "uri"
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
class String
|
10
|
+
def camelize
|
11
|
+
self.split(/[^a-z0-9]/i).map{|w| w.capitalize}.join
|
12
|
+
end
|
13
|
+
def camelize!
|
14
|
+
self.replace(self.split(/[^a-z0-9]/i).map{|w| w.capitalize}.join)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
1
18
|
module BscanHelper
|
19
|
+
|
20
|
+
attr_reader :modules_only
|
21
|
+
attr_reader :bscan_config
|
22
|
+
attr_accessor :stat
|
23
|
+
attr_accessor :activity
|
24
|
+
|
25
|
+
|
2
26
|
class Issue
|
3
27
|
attr_accessor :issue_name
|
4
28
|
attr_accessor :url
|
@@ -23,11 +47,26 @@ module BscanHelper
|
|
23
47
|
@req_str,@rsp_str = req,rsp
|
24
48
|
end
|
25
49
|
end
|
50
|
+
|
51
|
+
def copy_vars from
|
52
|
+
from.instance_variables.each do |nm|
|
53
|
+
self.instance_variable_set(nm, from.instance_variable_get(nm))
|
54
|
+
# puts "#{nm} => #{from.instance_variable_get(nm)}"
|
55
|
+
end
|
56
|
+
end
|
26
57
|
|
27
58
|
def prop nm
|
28
59
|
@prop_pref + nm
|
29
60
|
end
|
30
61
|
|
62
|
+
def get_par k,defv
|
63
|
+
p = @bscan_config[prop(k)]
|
64
|
+
p = p.to_i if p and p.to_i.to_s == p
|
65
|
+
p = true if p == 'true' or p == 'yes'
|
66
|
+
p = false if p == 'false' or p == 'no'
|
67
|
+
p ? p:defv
|
68
|
+
end
|
69
|
+
|
31
70
|
def search_path
|
32
71
|
path = []
|
33
72
|
path << File.expand_path('.') << File.expand_path(File.join('.','lib')) << File.expand_path(File.join('~','.bscan')) << File.expand_path(File.join('etc','bscan')) << $:
|
@@ -58,8 +97,8 @@ module BscanHelper
|
|
58
97
|
|
59
98
|
|
60
99
|
def do_scan msg, trg, inj
|
61
|
-
@
|
62
|
-
|
100
|
+
@activity[0]=true
|
101
|
+
Log 2, "#{@mid}do_scan Scanning: #{trg}"
|
63
102
|
# msg.url = trg
|
64
103
|
path = $1 if trg =~ /\/\/[^\/]+(\/.*)/
|
65
104
|
path = '/' if (not path) or (path.length < 1)
|
@@ -84,24 +123,101 @@ module BscanHelper
|
|
84
123
|
trg,host,port = get_url_host_port req,proto
|
85
124
|
https = proto == "https" ? true : false
|
86
125
|
start = Time.now
|
87
|
-
|
88
|
-
rsp = @bscan.make_request(host, port, https, req)
|
126
|
+
Log 2, "#{@mid}send_req make_req: '#{trg}' '#{host}' '#{port}'\n#{req}"
|
127
|
+
# rsp = @bscan.make_request(host, port, https, req)
|
128
|
+
rsp = make_request_socket host, port, https, req
|
89
129
|
rt = Time.now - start
|
90
130
|
return [rsp,rt,trg,host,port]
|
91
131
|
rescue Exception => e
|
92
|
-
|
93
|
-
|
132
|
+
Log 0, "#{@mid}send_req Exception: #{e.message}"
|
133
|
+
Log 0, e.backtrace.join("\n")
|
94
134
|
end
|
95
135
|
end
|
96
136
|
|
137
|
+
def make_request_socket host, port, https, req
|
138
|
+
begin
|
139
|
+
rsp = ''
|
140
|
+
s = TCPSocket.new(host, port)
|
141
|
+
Log 2, "#{@mid}make_request_socket new socket #{host} #{port} #{https}"
|
142
|
+
if (https)
|
143
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
144
|
+
ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
145
|
+
s = OpenSSL::SSL::SSLSocket.new(s, ctx)
|
146
|
+
s.connect
|
147
|
+
end
|
148
|
+
s.syswrite(req)
|
149
|
+
Log 2, "#{@mid}make_request_socket write succeeded for #{req}"
|
150
|
+
rsp = read_response_socket s
|
151
|
+
while ((u = redirect? rsp))
|
152
|
+
rsp = redirect u,req
|
153
|
+
end
|
154
|
+
Log 2, "#{@mid}make_request_socket read succeeded #{rsp}"
|
155
|
+
rescue Exception => e
|
156
|
+
Log 1, "#{@mid}make_request_socket failed: #{e.message}"
|
157
|
+
Log 1, e.backtrace.join("\n")
|
158
|
+
end
|
159
|
+
begin
|
160
|
+
s.close
|
161
|
+
rescue
|
162
|
+
end
|
163
|
+
rsp
|
164
|
+
end
|
97
165
|
|
166
|
+
def redirect? req
|
167
|
+
if req =~ /^HTTP\/[^\s]+\s+3\d\d/i
|
168
|
+
return $1 if req =~ /\nLocation\s*:\s*([^\r\n]+)\r?\n/i
|
169
|
+
end
|
170
|
+
nil
|
171
|
+
end
|
172
|
+
|
173
|
+
def redirect u, req
|
174
|
+
uri = URI.parse(u)
|
175
|
+
proto = uri.scheme
|
176
|
+
host = uri.host
|
177
|
+
port = uri.port
|
178
|
+
path = uri.path
|
179
|
+
path += '?' + uri.query if uri.query
|
180
|
+
make_request_socket host, port, ('https'==proto),
|
181
|
+
req.sub(/^(POST|GET)\s+\/[^\s]+/, "\\1 #{path}")
|
182
|
+
end
|
183
|
+
|
184
|
+
|
185
|
+
def msg_end rsp
|
186
|
+
rsp[-4..-1] == "\r\n\r\n" || rsp[-2..-1] == "\n\n"
|
187
|
+
end
|
188
|
+
|
189
|
+
def read_response_socket s
|
190
|
+
rsp=''
|
191
|
+
begin
|
192
|
+
while (ch=s.sysread(1))
|
193
|
+
rsp += ch
|
194
|
+
break if msg_end rsp
|
195
|
+
if rsp =~ /Content-Length\s*:\s*(\d+)\r?\n$/i
|
196
|
+
len = $1.to_i
|
197
|
+
while (c=s.sysread(1))
|
198
|
+
rsp += c
|
199
|
+
if msg_end rsp
|
200
|
+
return rsp if len <= 0
|
201
|
+
rsp += s.sysread(len)
|
202
|
+
return rsp;
|
203
|
+
end
|
204
|
+
end
|
205
|
+
break
|
206
|
+
end
|
207
|
+
end
|
208
|
+
rescue Exception => e
|
209
|
+
Log 1, "#{@mid}read_response_socket failed: #{e.message}\nResponse:\n#{rsp}"
|
210
|
+
Log 1, e.backtrace.join("\n")
|
211
|
+
end
|
212
|
+
rsp
|
213
|
+
end
|
98
214
|
|
99
215
|
def send_req req, proto, inj
|
100
216
|
rsp,rt,trg,host,port = send_only req, proto, inj
|
101
217
|
https = proto == "https" ? true : false
|
102
|
-
if not @
|
103
|
-
|
104
|
-
@
|
218
|
+
if not @modules_only
|
219
|
+
Log 2, "#{@mid}send_req do_passive: '#{trg}' '#{host}' '#{port}'\n#{req}\n#{rsp}"
|
220
|
+
@burp.do_passive_scan(host, port, https, req, rsp)
|
105
221
|
end
|
106
222
|
verify_response trg, req, rsp, inj, rt
|
107
223
|
end
|
@@ -112,7 +228,7 @@ module BscanHelper
|
|
112
228
|
|
113
229
|
def verify_response u, req, rsp, inj, time
|
114
230
|
|
115
|
-
|
231
|
+
Log 2, "#{@mid}verify_response: #{u} #{inj} #{time} #{req} #{rsp}"
|
116
232
|
|
117
233
|
st = $1 if rsp =~ /^\s*HTTP.*\s+(\d+)\s+/
|
118
234
|
st ||= '0'
|
@@ -130,7 +246,184 @@ module BscanHelper
|
|
130
246
|
issue = Issue.new "#{@mid.chop}: Possible XSS", u, "High", "Retest", req, rsp, "The following input has been replayed in a response #{inj}"
|
131
247
|
end
|
132
248
|
|
133
|
-
|
249
|
+
write_issue_state issue if issue
|
134
250
|
end
|
135
251
|
|
252
|
+
def Log (mtype, *msgs)
|
253
|
+
pr = "Unknown:"
|
254
|
+
case mtype
|
255
|
+
when 0
|
256
|
+
pr = "ERROR:"
|
257
|
+
when 1
|
258
|
+
pr = "WARN:"
|
259
|
+
when 2
|
260
|
+
pr = "INFO:"
|
261
|
+
when 3
|
262
|
+
pr = "DEBUG:"
|
263
|
+
end
|
264
|
+
msgs.each do |msg|
|
265
|
+
if (@ll >= mtype)
|
266
|
+
dt = Time.now.strftime("%y%m%d %H%M%S")
|
267
|
+
@log.println "#{dt} #{pr} #{msg}"
|
268
|
+
end
|
269
|
+
end
|
270
|
+
@log.flush
|
271
|
+
end
|
272
|
+
|
273
|
+
def run_modules base, msg=nil
|
274
|
+
if @modules
|
275
|
+
@modules.each do |m|
|
276
|
+
begin
|
277
|
+
mod,prop=m.split(':',2)
|
278
|
+
prop ||=''
|
279
|
+
mn = File.basename(mod,".rb")
|
280
|
+
is_static = is_module_static(mn,prop)
|
281
|
+
Log 2, "BscanHelper.run_modules executing module #{mod}:#{prop} #{is_static}"
|
282
|
+
mn.camelize!
|
283
|
+
if (is_static && !msg) || (!is_static && msg)
|
284
|
+
eval("
|
285
|
+
# puts '=====================MODULE PATH: ' + $:.join(':')
|
286
|
+
require '#{mod}'
|
287
|
+
require 'bscan/utils/bscan_helper.rb'
|
288
|
+
|
289
|
+
class #{mn}#{prop}Class
|
290
|
+
include #{mn}
|
291
|
+
include BscanHelper
|
292
|
+
end
|
293
|
+
modins = #{mn}#{prop}Class.new
|
294
|
+
modins.copy_vars base if base
|
295
|
+
modins.run(self, msg, prop)
|
296
|
+
")
|
297
|
+
end
|
298
|
+
rescue Exception => e
|
299
|
+
Log 1, "BscanHelper.run_modules Can't exceute module #{mod}, Exception: #{e.message}"
|
300
|
+
Log 1, e.backtrace.join("\n")
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
def write_issue_state issue
|
307
|
+
# Log 2,"INSPECT: #{issue.http_messages[0].methods} #{issue.http_messages[0].inspect} #{issue.http_messages[0].to_s} "
|
308
|
+
|
309
|
+
@stat['high'] += 1 if issue.severity =~ /High/i
|
310
|
+
@stat['med'] += 1 if issue.severity =~ /Med/i
|
311
|
+
@stat['low'] += 1 if issue.severity =~ /Low/i
|
312
|
+
@stat['urls'] += " #{issue.url}\n"
|
313
|
+
|
314
|
+
Log 2,"BscanHelper.write_issue_state #{not @istream} #{issue.http_messages[0].methods} #{issue.http_messages[0].to_s} "
|
315
|
+
@istream or return
|
316
|
+
begin
|
317
|
+
@istream.println '#'*70
|
318
|
+
@istream.println "#{issue.issue_name} : #{issue.url}"
|
319
|
+
@istream.println "Severity: #{issue.severity}(#{issue.confidence})"
|
320
|
+
@istream.println "Background: #{issue.issue_background}"
|
321
|
+
@istream.println "Details: #{issue.issue_detail}"
|
322
|
+
@istream.println "Remediation: #{issue.remediation_background}"
|
323
|
+
@istream.println "Request: #{issue.http_messages[0].req_str}"
|
324
|
+
@istream.println "Response: #{issue.http_messages[0].rsp_str}"
|
325
|
+
# sync_save_state issue throws exceptions
|
326
|
+
@istream.flush
|
327
|
+
rescue Exception => e
|
328
|
+
Log 0, "BscanHelper.write_issue_state Can't write issue #{issue.issue_name}, Exception: #{e.message}"
|
329
|
+
Log 0, e.backtrace.join("\n")
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
def log ll, stream
|
334
|
+
stream.puts if true
|
335
|
+
end
|
336
|
+
|
337
|
+
def add_multi map,k,v
|
338
|
+
if (map[k])
|
339
|
+
ov = map[k];
|
340
|
+
if (ov.kind_of?(Array))
|
341
|
+
map[k] << v
|
342
|
+
else
|
343
|
+
map[k] = [ov,v]
|
344
|
+
end
|
345
|
+
else
|
346
|
+
map[k] = v
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
def init_stat
|
351
|
+
@stat ||= {}
|
352
|
+
@stat['start_time'] = Time.now.strftime("%Y-%m-%d %H:%M:%S")
|
353
|
+
@stat['end_time'] = ''
|
354
|
+
@stat['high'] = 0
|
355
|
+
@stat['med'] = 0
|
356
|
+
@stat['low'] = 0
|
357
|
+
@stat['issues'] = ''
|
358
|
+
@stat['urls'] = ''
|
359
|
+
end
|
360
|
+
|
361
|
+
def init_internals cmd_params
|
362
|
+
|
363
|
+
init_stat
|
364
|
+
@activity ||= [false]
|
365
|
+
|
366
|
+
# @cmd_params ||= JSON.parse args[0]
|
367
|
+
@cmd_params ||= cmd_params
|
368
|
+
@ll = @cmd_params['loglevel'].to_i
|
369
|
+
|
370
|
+
lfile = @cmd_params['logfile']
|
371
|
+
if lfile != nil
|
372
|
+
begin
|
373
|
+
@log = java.io.PrintStream.new(lfile)
|
374
|
+
rescue Exception => e
|
375
|
+
$stderr.puts("BscanHelper.init_internals Error: can't open log file '#{lfile}', exception: #{e.message}")
|
376
|
+
$stderr.puts(e.backtrace.join("\n"))
|
377
|
+
Process.exit!(2)
|
378
|
+
end
|
379
|
+
else
|
380
|
+
@log = $stdout
|
381
|
+
end
|
382
|
+
|
383
|
+
Log 2, "BscanHelper.init_internals CMD_PARAMS: #{@cmd_params}"
|
384
|
+
@cmd_params.each_pair do |k,v|
|
385
|
+
Log 2,"BscanHelper.init_internals #{k}:#{v}"
|
386
|
+
end
|
387
|
+
|
388
|
+
@bscan_config = @cmd_params['bscan_config']
|
389
|
+
@burp_config = @cmd_params['burp_config']
|
390
|
+
@issues = @bscan_config['bscan.issues']
|
391
|
+
@modules_only = (@bscan_config['bscan.modules_only'] and @bscan_config['bscan.modules_only'] == 'true')
|
392
|
+
@modules ||= @bscan_config['bscan.modules']
|
393
|
+
@modules ||= [] if not @modules
|
394
|
+
@modules = [@modules] if not @modules.kind_of?(Array)
|
395
|
+
mods = []
|
396
|
+
@modules.each do |m|
|
397
|
+
mods << m.split(',')
|
398
|
+
end
|
399
|
+
@modules = mods.flatten
|
400
|
+
|
401
|
+
|
402
|
+
Log 1, "BscanHelper.init_internals No issues dir provided. Issues will not be logged." if not @issues
|
403
|
+
if (@issues)
|
404
|
+
begin
|
405
|
+
dt = Time.now.strftime("%y%m%d_%H%M%S")
|
406
|
+
File.directory? @issues or %x{mkdir -p "#{@issues}"}
|
407
|
+
@sstream = "#{@issues}/session.#{dt}.zip"
|
408
|
+
ifile = "#{@issues}/issues.#{dt}.txt"
|
409
|
+
@istream = java.io.PrintStream.new(ifile)
|
410
|
+
@stat['issues'] = ifile
|
411
|
+
rescue Exception => e
|
412
|
+
Log 0, "BscanHelper.init_internals Can't create issues or session files, Exception: #{e.message}"
|
413
|
+
Log 0, e.backtrace.join("\n")
|
414
|
+
exit_suite
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
end
|
419
|
+
|
420
|
+
def is_module_static n,p
|
421
|
+
pref = 'bscan.' + n + '.'
|
422
|
+
pref += p + '.' if p and p.length > 0
|
423
|
+
@bscan_config[pref + 'static_request'] == 'true'
|
424
|
+
end
|
425
|
+
|
426
|
+
|
427
|
+
|
428
|
+
|
136
429
|
end
|
data/release_notes.txt
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
== 2.0.0
|
2
|
+
Two major improvements:
|
3
|
+
* Modules with static requests do not require Burp or Buby anymore
|
4
|
+
* Slow server writes have been added to slowloris (see delay_on_write)
|
5
|
+
|
1
6
|
== 1.4.5
|
2
7
|
* Mailer added to send scan status and detailed reports over SMTP
|
3
8
|
* Issues are logged with Java's PrintStream
|
data/test.sh
CHANGED
metadata
CHANGED
@@ -2,14 +2,14 @@
|
|
2
2
|
name: bscan
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version:
|
5
|
+
version: 2.0.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Oleg Gryb (ogryb)
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-08-
|
12
|
+
date: 2012-08-22 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: buby
|