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
data/CONFIG.rdoc
CHANGED
@@ -91,7 +91,12 @@ to 'true', the detailed zipped report will be attached. You'll ned
|
|
91
91
|
It's important not to exceed the maximum file number on your
|
92
92
|
client, otherwise it might not work. It's also important to
|
93
93
|
set up a correct timeout (sleep_time) that should not be
|
94
|
-
bigger than server's read timeout.
|
94
|
+
bigger than server's read or write timeout. The timeouts are different
|
95
|
+
for different servers and attack types: for slow reads the
|
96
|
+
timeout is usally bigger (100-200-.. secs), while for
|
97
|
+
slow writes it worked well for 5 - 10 sec interval. I general,
|
98
|
+
I found that slow writes are far more dangerous than slow reads
|
99
|
+
and that's why I set the default for 'delay_on_write' to 'true'.
|
95
100
|
|
96
101
|
* bscan.slowloris.hostport=<host>:<port>
|
97
102
|
no defaults, must provide both
|
@@ -100,23 +105,29 @@ bigger than server's read timeout.
|
|
100
105
|
* bscan.slowloris.method=<http method>
|
101
106
|
POST or GET, default - GET
|
102
107
|
* bscan.slowloris.threads=n
|
103
|
-
Thread number, default - 20
|
108
|
+
Thread number, default - 20 for reads, 500 for writes
|
104
109
|
* bscan.slowloris.con_nbr_per_thread=n
|
105
|
-
Number of connections per thread
|
110
|
+
Number of connections per thread.
|
111
|
+
Default - 50 for reads, 1 for writes
|
106
112
|
* bscan.slowloris.pack_per_con=n
|
107
113
|
Max number of data packets to be sent in each connection
|
108
|
-
|
114
|
+
Default - 5 for reads, 50 - for writes
|
109
115
|
* bscan.slowloris.response_time_factor=n
|
110
116
|
Normal response time will be multipled by this number to determine
|
111
117
|
when report an issue, e.g. if normal reposne time is 2 sec then if
|
112
118
|
a response time under attack is bigger than 2*n, it will
|
113
119
|
be logged as an issue. Default - 5
|
114
120
|
* bscan.slowloris.sleep_time=n
|
115
|
-
number of seconds to sleep
|
116
|
-
|
121
|
+
number of seconds to sleep after beginning of a request has been send
|
122
|
+
for reads or a delay to read message body for writes.
|
123
|
+
Default: 100 - for reads, 5 - for writes
|
117
124
|
* bscan.slowloris.static_request=true
|
118
125
|
Must be always set to 'true' for this module
|
119
|
-
|
126
|
+
* delay_on_write=<true|false>
|
127
|
+
Make delays on server's writes if true. Default: true
|
128
|
+
* health_check_int=n
|
129
|
+
Health check interval in seconds. Default: 2
|
130
|
+
|
120
131
|
== kill_apache.rb Module Parameters
|
121
132
|
Similar to slowloris a monitoring thread will be checking
|
122
133
|
a response time and log an issue if a threshold is reached
|
data/README.rdoc
CHANGED
@@ -28,14 +28,13 @@ Burp Suite from PortSwigger and Buby from Eric Monti and Timur Duehr
|
|
28
28
|
== REQUIREMENTS:
|
29
29
|
* JRuby - http://jruby.org
|
30
30
|
* Burp pro if you want to use default Burp's scanners
|
31
|
-
*
|
32
|
-
* Buby 1.3.1 (see http://emonti.github.com/buby/)
|
31
|
+
* Buby 1.3.1 (see http://emonti.github.com/buby/) (not neccessary for modules_only mode)
|
33
32
|
|
34
33
|
== BUILD/INSTALL:
|
35
34
|
|
36
35
|
=== Gem
|
37
36
|
|
38
|
-
sudo jruby -S gem install buby -d --source=http://gemcutter.org
|
37
|
+
sudo jruby -S gem install buby -d --source=http://gemcutter.org (not necessary for modules_only)
|
39
38
|
sudo jruby -S gem install bscan --source=http://gemcutter.org
|
40
39
|
|
41
40
|
After Buby and BScan are installed, you'll need to link BScan to Burp's JAR (see below)
|
@@ -51,6 +50,8 @@ Burp Suite from PortSwigger and Buby from Eric Monti and Timur Duehr
|
|
51
50
|
|
52
51
|
==== Linking BScan and Buby to Burp's JAR.
|
53
52
|
|
53
|
+
* This step is not neccessary for modules_only mode
|
54
|
+
|
54
55
|
After Buby and BScan are installed (either manually or by gem)
|
55
56
|
you'll need to link them to a Burp's JAR.
|
56
57
|
|
@@ -81,6 +82,8 @@ To get help for config layout run:
|
|
81
82
|
== RUNNING, REGISTERING Burp Pro:
|
82
83
|
You need to register you burp.jar before you can run it headless
|
83
84
|
|
85
|
+
* Not neccessary for modules_only mode
|
86
|
+
|
84
87
|
* Run your Burp as usual with GUI and provide you license.
|
85
88
|
After this is done, you can run it headless using 'bscan'
|
86
89
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
2.0.0
|
data/bin/bscan
CHANGED
@@ -3,10 +3,9 @@
|
|
3
3
|
#require File.expand_path(File.join(File.dirname(__FILE__), %w[.. lib bscan]))
|
4
4
|
$: << File.expand_path(File.join(File.dirname(__FILE__), %w[.. lib]))
|
5
5
|
|
6
|
-
require 'buby'
|
7
6
|
require 'getoptlong'
|
8
|
-
require 'bscan'
|
9
7
|
require 'bscan/utils/bscan_helper'
|
8
|
+
require 'json'
|
10
9
|
|
11
10
|
include BscanHelper
|
12
11
|
|
@@ -29,6 +28,35 @@ USAGE: jruby [-J-Xmx<nnn>M] [-J-Djava.awt.headless=true] -S bscan --config path_
|
|
29
28
|
exit(1)
|
30
29
|
end
|
31
30
|
|
31
|
+
def read_config file
|
32
|
+
|
33
|
+
burp_config = {}
|
34
|
+
bscan_config = {}
|
35
|
+
|
36
|
+
begin
|
37
|
+
open_in_path(file).each_line do |line|
|
38
|
+
line.chomp!
|
39
|
+
line.strip!
|
40
|
+
next if (line =~ /^#/ or line.length < 1)
|
41
|
+
data = line.split(/=/)
|
42
|
+
val = nil
|
43
|
+
val = data[1..-1].join('=') if data.size > 1
|
44
|
+
if data[0] =~ /^bscan./
|
45
|
+
add_multi(bscan_config,data[0],val)
|
46
|
+
else
|
47
|
+
burp_config[data[0]] = val
|
48
|
+
end
|
49
|
+
end
|
50
|
+
rescue Exception => e
|
51
|
+
$stderr.puts("BScan.read_config Error: can't read config file '#{file}', exception: #{e.message}")
|
52
|
+
$stderr.puts(e.backtrace.join("\n"))
|
53
|
+
Process.exit(3)
|
54
|
+
end
|
55
|
+
[burp_config, bscan_config]
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
|
32
60
|
def get_cmd_params
|
33
61
|
params = {}
|
34
62
|
|
@@ -74,6 +102,16 @@ def get_cmd_params
|
|
74
102
|
params
|
75
103
|
end
|
76
104
|
|
105
|
+
params = get_cmd_params
|
106
|
+
|
107
|
+
init_internals params
|
108
|
+
run_modules self
|
109
|
+
exit 0 if @modules_only
|
110
|
+
|
111
|
+
require 'buby'
|
112
|
+
require 'burp'
|
113
|
+
require 'bscan'
|
114
|
+
|
77
115
|
$burp = Buby.new()
|
78
116
|
$burp.extend(BScan)
|
79
|
-
$burp.start_burp([
|
117
|
+
$burp.start_burp([params.to_json])
|
data/bscan.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "bscan"
|
8
|
-
s.version = "
|
8
|
+
s.version = "2.0.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Oleg Gryb (ogryb)"]
|
12
|
-
s.date = "2012-08-
|
12
|
+
s.date = "2012-08-22"
|
13
13
|
s.description = "BScan is a configurable and extendable web application security scanner that can be run from a command line headless (without UI). It's built on top of arguably the most popular commercial security testing tool Burp Suite from PortSwigger and Buby from Eric Monti and Timur Duehr"
|
14
14
|
s.email = "oleg@gryb.info"
|
15
15
|
s.executables = ["bscan"]
|
data/lib/bscan.rb
CHANGED
@@ -1,75 +1,20 @@
|
|
1
1
|
#!/usr/bin/env jruby
|
2
2
|
|
3
|
-
require 'pathname'
|
4
3
|
require 'buby'
|
5
4
|
require 'getoptlong'
|
6
|
-
require 'json'
|
7
5
|
require 'bscan/utils/bscan_helper'
|
8
6
|
require 'bscan/utils/mailer'
|
9
7
|
require 'java'
|
10
|
-
|
11
|
-
class String
|
12
|
-
def camelize
|
13
|
-
self.split(/[^a-z0-9]/i).map{|w| w.capitalize}.join
|
14
|
-
end
|
15
|
-
def camelize!
|
16
|
-
self.replace(self.split(/[^a-z0-9]/i).map{|w| w.capitalize}.join)
|
17
|
-
end
|
18
|
-
end
|
8
|
+
require 'json'
|
19
9
|
|
20
10
|
module BScan
|
21
11
|
|
22
12
|
include BscanHelper
|
23
13
|
include Mailer
|
24
14
|
|
25
|
-
attr_accessor :activity
|
26
|
-
attr_reader :modules_only
|
27
|
-
attr_reader :bscan_config
|
28
|
-
attr_accessor :stat
|
29
15
|
|
30
|
-
def Log (mtype, *msgs)
|
31
|
-
pr = "Unknown:"
|
32
|
-
case mtype
|
33
|
-
when 0
|
34
|
-
pr = "ERROR:"
|
35
|
-
when 1
|
36
|
-
pr = "WARN:"
|
37
|
-
when 2
|
38
|
-
pr = "INFO:"
|
39
|
-
when 3
|
40
|
-
pr = "DEBUG:"
|
41
|
-
end
|
42
|
-
msgs.each do |msg|
|
43
|
-
if (@ll >= mtype)
|
44
|
-
dt = Time.now.strftime("%y%m%d %H%M%S")
|
45
|
-
@log.println "#{dt} #{pr} #{msg}"
|
46
|
-
end
|
47
|
-
end
|
48
|
-
@log.flush
|
49
|
-
end
|
50
|
-
|
51
16
|
def evt_commandline_args args
|
52
|
-
|
53
|
-
@cmd_params ||= JSON.parse args[0]
|
54
|
-
@ll = @cmd_params['loglevel'].to_i
|
55
|
-
|
56
|
-
lfile = @cmd_params['logfile']
|
57
|
-
if lfile != nil
|
58
|
-
begin
|
59
|
-
@log = java.io.PrintStream.new(lfile)
|
60
|
-
rescue Exception => e
|
61
|
-
$stderr.puts("BScan.evt_register_callbacks Error: can't open log file '#{lfile}', exception: #{e.message}")
|
62
|
-
$stderr.puts(e.backtrace.join("\n"))
|
63
|
-
Process.exit!(2)
|
64
|
-
end
|
65
|
-
else
|
66
|
-
@log = $stdout
|
67
|
-
end
|
68
|
-
|
69
|
-
Log 2, "BScan.evt_commandline_args CMD_PARAMS: #{@cmd_params}"
|
70
|
-
@cmd_params.each_pair do |k,v|
|
71
|
-
Log 2,"BScan.evt_commandline_args #{k}:#{v}"
|
72
|
-
end
|
17
|
+
init_internals JSON.parse(args[0])
|
73
18
|
end
|
74
19
|
|
75
20
|
|
@@ -95,50 +40,13 @@ module BScan
|
|
95
40
|
Log 2, "BScan.evt_http_message Actively scanning: #{message_info.url.to_string}"
|
96
41
|
isqi = do_active_scan(message_info.getHost(), message_info.getPort(), https, message_info.getRequest(), [])
|
97
42
|
@queue.push(isqi)
|
98
|
-
run_modules message_info
|
43
|
+
run_modules self, message_info
|
99
44
|
end
|
100
45
|
end
|
101
46
|
end
|
102
47
|
end
|
103
48
|
|
104
|
-
|
105
|
-
pref = 'bscan.' + n + '.'
|
106
|
-
pref += p + '.' if p and p.length > 0
|
107
|
-
@bscan_config[pref + 'static_request'] == 'true'
|
108
|
-
end
|
109
|
-
|
110
|
-
|
111
|
-
def run_modules msg=nil
|
112
|
-
if @modules
|
113
|
-
@modules.each do |m|
|
114
|
-
begin
|
115
|
-
mod,prop=m.split(':',2)
|
116
|
-
prop ||=''
|
117
|
-
mn = File.basename(mod,".rb")
|
118
|
-
is_static = is_module_static(mn,prop)
|
119
|
-
Log 2, "BScan.run_modules executing module #{mod}:#{prop} #{is_static}"
|
120
|
-
mn.camelize!
|
121
|
-
if (is_static && !msg) || (!is_static && msg)
|
122
|
-
eval("
|
123
|
-
# puts '=====================MODULE PATH: ' + $:.join(':')
|
124
|
-
require '#{mod}'
|
125
|
-
require 'bscan/utils/bscan_helper.rb'
|
126
|
-
|
127
|
-
class #{mn}#{prop}Class
|
128
|
-
include #{mn}
|
129
|
-
include BscanHelper
|
130
|
-
end
|
131
|
-
#{mn}#{prop}Class.new.run(self, msg, prop)
|
132
|
-
")
|
133
|
-
end
|
134
|
-
rescue Exception => e
|
135
|
-
Log 1, "BScan.run_modules Can't exceute module #{mod}, Exception: #{e.message}"
|
136
|
-
Log 1, e.backtrace.join("\n")
|
137
|
-
end
|
138
|
-
end
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
49
|
+
|
142
50
|
def evt_scan_issue issue
|
143
51
|
super(issue)
|
144
52
|
# Buby::HttpRequestResponseHelper.implant(issue.http_messages)
|
@@ -152,32 +60,7 @@ module BScan
|
|
152
60
|
}
|
153
61
|
end
|
154
62
|
|
155
|
-
|
156
|
-
# Log 2,"INSPECT: #{issue.http_messages[0].methods} #{issue.http_messages[0].inspect} #{issue.http_messages[0].to_s} "
|
157
|
-
|
158
|
-
@stat['high'] += 1 if issue.severity =~ /High/i
|
159
|
-
@stat['med'] += 1 if issue.severity =~ /Med/i
|
160
|
-
@stat['low'] += 1 if issue.severity =~ /Low/i
|
161
|
-
@stat['urls'] += " #{issue.url}\n"
|
162
|
-
|
163
|
-
Log 2,"BScan.write_issue_state #{not @istream} #{issue.http_messages[0].methods} #{issue.http_messages[0].to_s} "
|
164
|
-
@istream or return
|
165
|
-
begin
|
166
|
-
@istream.println '#'*70
|
167
|
-
@istream.println "#{issue.issue_name} : #{issue.url}"
|
168
|
-
@istream.println "Severity: #{issue.severity}(#{issue.confidence})"
|
169
|
-
@istream.println "Background: #{issue.issue_background}"
|
170
|
-
@istream.println "Details: #{issue.issue_detail}"
|
171
|
-
@istream.println "Remediation: #{issue.remediation_background}"
|
172
|
-
@istream.println "Request: #{issue.http_messages[0].req_str}"
|
173
|
-
@istream.println "Response: #{issue.http_messages[0].rsp_str}"
|
174
|
-
# sync_save_state issue throws exceptions
|
175
|
-
@istream.flush
|
176
|
-
rescue Exception => e
|
177
|
-
Log 0, "BScan.write_issue_state Can't write issue #{issue.issue_name}, Exception: #{e.message}"
|
178
|
-
Log 0, e.backtrace.join("\n")
|
179
|
-
end
|
180
|
-
end
|
63
|
+
|
181
64
|
def evt_application_closing
|
182
65
|
begin
|
183
66
|
Log 2,"BScan.evt_application_closing #{@bscan_config['bscan.smtp.server']} #{@bscan_config['bscan.smtp.to']}"
|
@@ -193,32 +76,12 @@ module BScan
|
|
193
76
|
|
194
77
|
def evt_register_callbacks cb
|
195
78
|
super(cb)
|
79
|
+
@burp ||= self
|
196
80
|
begin
|
197
|
-
|
198
81
|
Log 2, "="*30, "BScan.evt_register_callbacks registring, log = #{@cmd_params['logfile']} ", "="*30
|
199
|
-
|
200
|
-
@burp_config = @cmd_params['burp_config']
|
201
|
-
@issues = @bscan_config['bscan.issues']
|
202
|
-
@modules_only = (@bscan_config['bscan.modules_only'] and @bscan_config['bscan.modules_only'] == 'true')
|
203
|
-
|
204
|
-
Log 1, "BScan.evt_register_callbacks No issues dir provided. Issues will not be logged." if not @issues
|
205
|
-
if (@issues)
|
206
|
-
begin
|
207
|
-
dt = Time.now.strftime("%y%m%d_%H%M%S")
|
208
|
-
File.directory? @issues or %x{mkdir -p "#{@issues}"}
|
209
|
-
@sstream = "#{@issues}/session.#{dt}.zip"
|
210
|
-
ifile = "#{@issues}/issues.#{dt}.txt"
|
211
|
-
@istream = java.io.PrintStream.new(ifile)
|
212
|
-
@stat['issues'] = ifile
|
213
|
-
rescue Exception => e
|
214
|
-
Log 0, "BScan.evt_register_callbacks Can't create issues or session files, Exception: #{e.message}"
|
215
|
-
Log 0, e.backtrace.join("\n")
|
216
|
-
exit_suite
|
217
|
-
end
|
218
|
-
end
|
82
|
+
|
219
83
|
|
220
84
|
@queue ||= []
|
221
|
-
@activity = [false]
|
222
85
|
@inactivity_to = @bscan_config['bscan.inactivity_to']
|
223
86
|
@inactivity_to ||= '30'
|
224
87
|
@inactivity_to = @inactivity_to.to_i
|
@@ -255,22 +118,11 @@ module BScan
|
|
255
118
|
load_config params
|
256
119
|
params.each_pair {|k,v| Log 2,"#{k}:#{v}"} # if k =~ /^(scanner|spider)/}
|
257
120
|
|
258
|
-
|
259
|
-
|
260
|
-
@modules = [@modules] if not @modules.kind_of?(Array)
|
261
|
-
|
262
|
-
mods = []
|
263
|
-
@modules.each do |m|
|
264
|
-
mods << m.split(',')
|
265
|
-
end
|
121
|
+
# run_modules
|
122
|
+
# exit_suite
|
266
123
|
|
267
|
-
|
268
|
-
|
269
|
-
urls = @bscan_config['bscan.url']
|
270
|
-
Log 2, "BScan.evt_register_callbacks urls: #{@modules_only} #{urls}"
|
271
|
-
|
272
|
-
run_modules # run_modules without 'msg' will do static reqs only
|
273
|
-
return if @modules_only
|
124
|
+
urls = @bscan_config['bscan.url']
|
125
|
+
Log 2, "BScan.evt_register_callbacks urls: #{@modules_only} #{urls}"
|
274
126
|
|
275
127
|
if not urls
|
276
128
|
Log 0, "BScan.evt_register_callbacks No URL's provided in config. Use bscan.url param. Multiple entries are OK"
|
@@ -297,58 +149,4 @@ module BScan
|
|
297
149
|
end
|
298
150
|
|
299
151
|
|
300
|
-
def log ll, stream
|
301
|
-
stream.puts if true
|
302
|
-
end
|
303
|
-
|
304
|
-
def add_multi map,k,v
|
305
|
-
if (map[k])
|
306
|
-
ov = map[k];
|
307
|
-
if (ov.kind_of?(Array))
|
308
|
-
map[k] << v
|
309
|
-
else
|
310
|
-
map[k] = [ov,v]
|
311
|
-
end
|
312
|
-
else
|
313
|
-
map[k] = v
|
314
|
-
end
|
315
|
-
end
|
316
|
-
|
317
|
-
def init_stat
|
318
|
-
@stat ||= {}
|
319
|
-
@stat['start_time'] = Time.now.strftime("%Y-%m-%d %H:%M:%S")
|
320
|
-
@stat['end_time'] = ''
|
321
|
-
@stat['high'] = 0
|
322
|
-
@stat['med'] = 0
|
323
|
-
@stat['low'] = 0
|
324
|
-
@stat['issues'] = ''
|
325
|
-
@stat['urls'] = ''
|
326
|
-
end
|
327
|
-
|
328
|
-
def read_config file
|
329
|
-
|
330
|
-
burp_config = {}
|
331
|
-
bscan_config = {}
|
332
|
-
|
333
|
-
begin
|
334
|
-
open_in_path(file).each_line do |line|
|
335
|
-
line.chomp!
|
336
|
-
line.strip!
|
337
|
-
next if (line =~ /^#/ or line.length < 1)
|
338
|
-
data = line.split(/=/)
|
339
|
-
val = nil
|
340
|
-
val = data[1..-1].join('=') if data.size > 1
|
341
|
-
if data[0] =~ /^bscan./
|
342
|
-
add_multi(bscan_config,data[0],val)
|
343
|
-
else
|
344
|
-
burp_config[data[0]] = val
|
345
|
-
end
|
346
|
-
end
|
347
|
-
rescue Exception => e
|
348
|
-
$stderr.puts("BScan.read_config Error: can't read config file '#{file}', exception: #{e.message}")
|
349
|
-
$stderr.puts(e.backtrace.join("\n"))
|
350
|
-
Process.exit(3)
|
351
|
-
end
|
352
|
-
[burp_config, bscan_config]
|
353
|
-
end
|
354
152
|
|