bscan 1.4.4
Sign up to get free protection for your applications and to get access to all the features.
- data/CONFIG.rdoc +131 -0
- data/README.rdoc +140 -0
- data/Rakefile +54 -0
- data/VERSION +1 -0
- data/bin/bscan +79 -0
- data/bscan.gemspec +63 -0
- data/lib/bscan.rb +324 -0
- data/lib/bscan/modules/injector.rb +142 -0
- data/lib/bscan/modules/kill_apache.rb +201 -0
- data/lib/bscan/modules/many_threads.rb +52 -0
- data/lib/bscan/modules/slowloris.rb +263 -0
- data/lib/bscan/utils/bscan_helper.rb +133 -0
- data/release_notes.txt +25 -0
- data/samples/config/big_request.txt +12 -0
- data/samples/config/conf +58 -0
- data/samples/config/injector.txt +514 -0
- data/samples/config/request.txt +12 -0
- data/samples/headless-bscan.sh +3 -0
- data/test.sh +3 -0
- data/test/bscan_test.rb +4 -0
- metadata +91 -0
data/lib/bscan.rb
ADDED
@@ -0,0 +1,324 @@
|
|
1
|
+
#!/usr/bin/env jruby
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
require 'buby'
|
5
|
+
require 'getoptlong'
|
6
|
+
require 'json'
|
7
|
+
require 'bscan/utils/bscan_helper'
|
8
|
+
require 'java'
|
9
|
+
|
10
|
+
class String
|
11
|
+
def camelize
|
12
|
+
self.split(/[^a-z0-9]/i).map{|w| w.capitalize}.join
|
13
|
+
end
|
14
|
+
def camelize!
|
15
|
+
self.replace(self.split(/[^a-z0-9]/i).map{|w| w.capitalize}.join)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module BScan
|
20
|
+
|
21
|
+
include BscanHelper
|
22
|
+
|
23
|
+
attr_accessor :activity
|
24
|
+
attr_reader :modules_only
|
25
|
+
attr_reader :bscan_config
|
26
|
+
|
27
|
+
def Log (mtype, *msgs)
|
28
|
+
pr = "Unknown:"
|
29
|
+
case mtype
|
30
|
+
when 0
|
31
|
+
pr = "ERROR:"
|
32
|
+
when 1
|
33
|
+
pr = "WARN:"
|
34
|
+
when 2
|
35
|
+
pr = "INFO:"
|
36
|
+
when 3
|
37
|
+
pr = "DEBUG:"
|
38
|
+
end
|
39
|
+
msgs.each do |msg|
|
40
|
+
if (@ll >= mtype)
|
41
|
+
dt = Time.now.strftime("%y%m%d %H%M%S")
|
42
|
+
@log.println "#{dt} #{pr} #{msg}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
@log.flush
|
46
|
+
end
|
47
|
+
|
48
|
+
def evt_commandline_args args
|
49
|
+
@cmd_params ||= JSON.parse args[0]
|
50
|
+
@ll = @cmd_params['loglevel'].to_i
|
51
|
+
|
52
|
+
lfile = @cmd_params['logfile']
|
53
|
+
if lfile != nil
|
54
|
+
begin
|
55
|
+
@log = java.io.PrintStream.new(lfile)
|
56
|
+
rescue Exception => e
|
57
|
+
$stderr.puts("BScan.evt_register_callbacks Error: can't open log file '#{lfile}', exception: #{e.message}")
|
58
|
+
$stderr.puts(e.backtrace.join("\n"))
|
59
|
+
Process.exit!(2)
|
60
|
+
end
|
61
|
+
else
|
62
|
+
@log = $stdout
|
63
|
+
end
|
64
|
+
|
65
|
+
Log 2, "BScan.evt_commandline_args CMD_PARAMS: #{@cmd_params}"
|
66
|
+
@cmd_params.each_pair do |k,v|
|
67
|
+
Log 2,"BScan.evt_commandline_args #{k}:#{v}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
|
73
|
+
def evt_http_message(tool_name, is_request, message_info)
|
74
|
+
super(tool_name, is_request, message_info)
|
75
|
+
if tool_name == 'Scanner'
|
76
|
+
if is_request
|
77
|
+
Log 2, "#"*70, "# BScan.evt_http_message REQUEST: #{message_info.url.toString}, tool: #{tool_name}", "#"*70
|
78
|
+
Log 3, "BScan.evt_http_message #{message_info.req_str}", ""
|
79
|
+
else
|
80
|
+
Log 2, "# BScan.evt_http_message RESPONSE CODE: #{message_info.statusCode}", ""
|
81
|
+
Log 3, "BScan.evt_http_message #{message_info.rsp_str}", ""
|
82
|
+
end
|
83
|
+
end
|
84
|
+
if tool_name == 'Spider'
|
85
|
+
if not is_request
|
86
|
+
@activity[0] = true
|
87
|
+
https = message_info.getProtocol() == "https" ? true : false
|
88
|
+
Log 2, "BScan.evt_http_message Passively scanning: #{message_info.url.to_string}"
|
89
|
+
do_passive_scan(message_info.getHost(), message_info.getPort(), https, message_info.getRequest(), message_info.getResponse())
|
90
|
+
if (is_in_scope(message_info.url))
|
91
|
+
Log 2, "BScan.evt_http_message Actively scanning: #{message_info.url.to_string}"
|
92
|
+
isqi = do_active_scan(message_info.getHost(), message_info.getPort(), https, message_info.getRequest(), [])
|
93
|
+
@queue.push(isqi)
|
94
|
+
run_modules message_info
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def is_module_static n,p
|
101
|
+
pref = 'bscan.' + n + '.'
|
102
|
+
pref += p + '.' if p and p.length > 0
|
103
|
+
@bscan_config[pref + 'static_request'] == 'true'
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
def run_modules msg=nil
|
108
|
+
if @modules
|
109
|
+
@modules.each do |m|
|
110
|
+
begin
|
111
|
+
mod,prop=m.split(':',2)
|
112
|
+
prop ||=''
|
113
|
+
mn = File.basename(mod,".rb")
|
114
|
+
is_static = is_module_static(mn,prop)
|
115
|
+
Log 2, "BScan.run_modules executing module #{mod}:#{prop} #{is_static}"
|
116
|
+
mn.camelize!
|
117
|
+
if (is_static && !msg) || (!is_static && msg)
|
118
|
+
eval("
|
119
|
+
# puts '=====================MODULE PATH: ' + $:.join(':')
|
120
|
+
require '#{mod}'
|
121
|
+
require 'bscan/utils/bscan_helper.rb'
|
122
|
+
|
123
|
+
class #{mn}#{prop}Class
|
124
|
+
include #{mn}
|
125
|
+
include BscanHelper
|
126
|
+
end
|
127
|
+
#{mn}#{prop}Class.new.run(self, msg, prop)
|
128
|
+
")
|
129
|
+
end
|
130
|
+
rescue Exception => e
|
131
|
+
Log 1, "BScan.run_modules Can't exceute module #{mod}, Exception: #{e.message}"
|
132
|
+
Log 1, e.backtrace.join("\n")
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def evt_scan_issue issue
|
139
|
+
super(issue)
|
140
|
+
# Buby::HttpRequestResponseHelper.implant(issue.http_messages)
|
141
|
+
write_issue_state issue
|
142
|
+
end
|
143
|
+
|
144
|
+
def sync_save_state issue
|
145
|
+
@sync_state_mutex ||= Mutex.new
|
146
|
+
@sync_state_mutex.synchronize {
|
147
|
+
save_state(@sstream) if @sstream and issue.severity =~ /(High)/
|
148
|
+
}
|
149
|
+
end
|
150
|
+
|
151
|
+
def write_issue_state issue
|
152
|
+
# Log 2,"INSPECT: #{issue.http_messages[0].methods} #{issue.http_messages[0].inspect} #{issue.http_messages[0].to_s} "
|
153
|
+
|
154
|
+
Log 2,"BScan.write_issue_state #{not @istream} #{issue.http_messages[0].methods} #{issue.http_messages[0].to_s} "
|
155
|
+
@istream or return
|
156
|
+
begin
|
157
|
+
@istream.puts '#'*70,"#{issue.issue_name} : #{issue.url}",
|
158
|
+
"Severity: #{issue.severity}(#{issue.confidence})",
|
159
|
+
"Background: #{issue.issue_background}",
|
160
|
+
"Details: #{issue.issue_detail}",
|
161
|
+
"Remediation: #{issue.remediation_background}",
|
162
|
+
"Request: #{issue.http_messages[0].req_str}",
|
163
|
+
"Response: #{issue.http_messages[0].rsp_str}"
|
164
|
+
# sync_save_state issue throws exceptions
|
165
|
+
@istream.flush
|
166
|
+
rescue Exception => e
|
167
|
+
Log 0, "BScan.write_issue_state Can't write issue #{issue.issue_name}, Exception: #{e.message}"
|
168
|
+
Log 0, e.backtrace.join("\n")
|
169
|
+
end
|
170
|
+
end
|
171
|
+
def evt_application_closing
|
172
|
+
Log 2,"BScan.evt_application_closing"
|
173
|
+
@istream.close if @istream
|
174
|
+
@log.close if @log
|
175
|
+
end
|
176
|
+
|
177
|
+
def evt_register_callbacks cb
|
178
|
+
super(cb)
|
179
|
+
begin
|
180
|
+
|
181
|
+
Log 2, "="*30, "BScan.evt_register_callbacks registring, log = #{@cmd_params['logfile']} ", "="*30
|
182
|
+
@bscan_config = @cmd_params['bscan_config']
|
183
|
+
@burp_config = @cmd_params['burp_config']
|
184
|
+
@issues = @bscan_config['bscan.issues']
|
185
|
+
@modules_only = (@bscan_config['bscan.modules_only'] and @bscan_config['bscan.modules_only'] == 'true')
|
186
|
+
|
187
|
+
Log 1, "BScan.evt_register_callbacks No issues dir provided. Issues will not be logged." if not @issues
|
188
|
+
if (@issues)
|
189
|
+
begin
|
190
|
+
dt = Time.now.strftime("%y%m%d_%H%M%S")
|
191
|
+
File.directory? @issues or %x{mkdir -p "#{@issues}"}
|
192
|
+
@sstream = "#{@issues}/session.#{dt}.zip"
|
193
|
+
@istream = File.open("#{@issues}/issues.#{dt}.txt","w")
|
194
|
+
rescue Exception => e
|
195
|
+
Log 0, "BScan.evt_register_callbacks Can't create issues or session files, Exception: #{e.message}"
|
196
|
+
Log 0, e.backtrace.join("\n")
|
197
|
+
|
198
|
+
exit_suite
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
@queue ||= []
|
203
|
+
@activity = [false]
|
204
|
+
@inactivity_to = @bscan_config['bscan.inactivity_to']
|
205
|
+
@inactivity_to ||= '30'
|
206
|
+
@inactivity_to = @inactivity_to.to_i
|
207
|
+
# Will exit if @activity = 0 and @queue is empty two times
|
208
|
+
if @monitor == nil
|
209
|
+
Log 2, "="*30, "BScan.evt_register_callbacks Starting Monitor", "="*30
|
210
|
+
@monitor = Thread.new(@queue, @activity) {|q,a|
|
211
|
+
cnt=0;
|
212
|
+
while (true)
|
213
|
+
sleep(@inactivity_to/2)
|
214
|
+
q.delete_if {|e| e.getPercentageComplete() == 100}
|
215
|
+
if q.length == 0 and not a[0]
|
216
|
+
cnt += 1
|
217
|
+
else
|
218
|
+
cnt = 0
|
219
|
+
# Log 2, "BScan.evt_register_callbacks QUEUE: #{q.length}, Activity: #{a[0]}"
|
220
|
+
end
|
221
|
+
if cnt > 1
|
222
|
+
# Log 2, "BScan.evt_register_callbacks Scanning complete"
|
223
|
+
exit_suite
|
224
|
+
break
|
225
|
+
end
|
226
|
+
a[0] = false
|
227
|
+
end
|
228
|
+
}
|
229
|
+
end
|
230
|
+
|
231
|
+
Log 2, '='*30, 'BScan.evt_register_callbacks Params', '='*30
|
232
|
+
params = save_config
|
233
|
+
params['target.scopeinclude0']='**empty**' # burp can store the previous scope somehow, thus need to clean
|
234
|
+
@burp_config.each_pair do |k,v|
|
235
|
+
params[k] = v
|
236
|
+
end
|
237
|
+
load_config params
|
238
|
+
params.each_pair {|k,v| Log 2,"#{k}:#{v}"} # if k =~ /^(scanner|spider)/}
|
239
|
+
|
240
|
+
@modules ||= @bscan_config['bscan.modules']
|
241
|
+
@modules = [@modules] if not @modules.kind_of?(Array)
|
242
|
+
|
243
|
+
mods = []
|
244
|
+
@modules.each do |m|
|
245
|
+
mods << m.split(',')
|
246
|
+
end
|
247
|
+
|
248
|
+
@modules = mods.flatten
|
249
|
+
|
250
|
+
urls = @bscan_config['bscan.url']
|
251
|
+
Log 2, "BScan.evt_register_callbacks urls: #{@modules_only} #{urls.join('|')}"
|
252
|
+
|
253
|
+
run_modules # run_modules without 'msg' will do static reqs only
|
254
|
+
return if @modules_only
|
255
|
+
|
256
|
+
if not urls
|
257
|
+
Log 0, "BScan.evt_register_callbacks No URL's provided in config. Use bscan.url param. Multiple entries are OK"
|
258
|
+
exit_suite
|
259
|
+
end
|
260
|
+
|
261
|
+
urls = [urls] if not urls.kind_of?(Array)
|
262
|
+
|
263
|
+
urls.each do |u|
|
264
|
+
Log 2, "BScan.evt_register_callbacks checking url: #{u}"
|
265
|
+
if not is_in_scope(u)
|
266
|
+
Log 2, "BScan.evt_register_callbacks including url: #{u}"
|
267
|
+
include_in_scope(u)
|
268
|
+
end
|
269
|
+
Log 2, "BScan.evt_register_callbacks Sending to spider: #{u}"
|
270
|
+
send_to_spider(u)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
rescue Exception => e
|
274
|
+
Log 0, "BScan.evt_register_callbacks Exception: #{e.message}"
|
275
|
+
Log 0, e.backtrace.join("\n")
|
276
|
+
exit_suite
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
|
281
|
+
def log ll, stream
|
282
|
+
stream.puts if true
|
283
|
+
end
|
284
|
+
|
285
|
+
def add_multi map,k,v
|
286
|
+
if (map[k])
|
287
|
+
ov = map[k];
|
288
|
+
if (ov.kind_of?(Array))
|
289
|
+
map[k] << v
|
290
|
+
else
|
291
|
+
map[k] = [ov,v]
|
292
|
+
end
|
293
|
+
else
|
294
|
+
map[k] = v
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
def read_config file
|
299
|
+
|
300
|
+
burp_config = {}
|
301
|
+
bscan_config = {}
|
302
|
+
|
303
|
+
begin
|
304
|
+
open_in_path(file).each_line do |line|
|
305
|
+
line.chomp!
|
306
|
+
line.strip!
|
307
|
+
next if (line =~ /^#/ or line.length < 1)
|
308
|
+
data = line.split(/=/)
|
309
|
+
val = nil
|
310
|
+
val = data[1..-1].join('=') if data.size > 1
|
311
|
+
if data[0] =~ /^bscan./
|
312
|
+
add_multi(bscan_config,data[0],val)
|
313
|
+
else
|
314
|
+
burp_config[data[0]] = val
|
315
|
+
end
|
316
|
+
end
|
317
|
+
rescue Exception => e
|
318
|
+
$stderr.puts("BScan.read_config Error: can't read config file '#{file}', exception: #{e.message}")
|
319
|
+
$stderr.puts(e.backtrace.join("\n"))
|
320
|
+
Process.exit(3)
|
321
|
+
end
|
322
|
+
[burp_config, bscan_config]
|
323
|
+
end
|
324
|
+
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'bscan/utils/bscan_helper.rb'
|
2
|
+
|
3
|
+
module Injector
|
4
|
+
|
5
|
+
COMMENT_START='# '
|
6
|
+
|
7
|
+
def run *args
|
8
|
+
|
9
|
+
@bscan = args[0]
|
10
|
+
@config ||= @bscan.instance_variable_get("@bscan_config")
|
11
|
+
@bscan.activity[0]=true
|
12
|
+
|
13
|
+
@prop_pref = 'bscan.injector.'
|
14
|
+
@prop_pref += args[2] + '.' if args[2] && args[2].length > 0
|
15
|
+
@mid = args[2]?"Injector.#{args[2]}.":'Injector.'
|
16
|
+
msg = args[1]
|
17
|
+
|
18
|
+
if not msg
|
19
|
+
inject_to_pattern
|
20
|
+
return
|
21
|
+
end
|
22
|
+
|
23
|
+
url = msg.url.dup.to_s
|
24
|
+
@bscan.Log 2, "#{@mid}run for #{url}"
|
25
|
+
begin
|
26
|
+
if (url =~ /([^?]+)\?(.+)/)
|
27
|
+
beg = "#{$1}?"
|
28
|
+
params = $2
|
29
|
+
@bscan.Log 2, "#{@mid}run BEG: #{beg} PARAMS: #{params} FILE: #{@config[prop('file')]}"
|
30
|
+
injs = open_in_path(@config[prop('file')])
|
31
|
+
injs.each_line do |l|
|
32
|
+
l.chomp!
|
33
|
+
next if (l =~ /^#{COMMENT_START}/ or l.length < 1)
|
34
|
+
@bscan.Log 2, "#{@mid}run injecting: #{l}"
|
35
|
+
|
36
|
+
@bscan.activity[0]=true
|
37
|
+
|
38
|
+
do_scan(msg, beg + l, l) # in parameter name
|
39
|
+
do_scan(msg, beg.chop + l, l) # in URL
|
40
|
+
|
41
|
+
pos=0
|
42
|
+
while (m=params.match(/([^&]+)=([^&]+)/,pos))
|
43
|
+
trg = beg + params[0..m.begin(2)-1] + l + params[m.end(2)..-1]
|
44
|
+
@bscan.activity[0]=true
|
45
|
+
do_scan(msg, trg, l)
|
46
|
+
pos=m.end(1)+1
|
47
|
+
end
|
48
|
+
end
|
49
|
+
injs.close
|
50
|
+
end
|
51
|
+
|
52
|
+
inject_to_body msg if @config['bscan.injector.one.inject_to_body'] == 'true'
|
53
|
+
|
54
|
+
rescue Exception => e
|
55
|
+
@bscan.Log 0, "#{@mid}run Exception: #{e.message}"
|
56
|
+
@bscan.Log 0, e.backtrace.join("\n")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def inject_to_pattern
|
61
|
+
param = @config[prop('inject_instead_of')]
|
62
|
+
a=[]
|
63
|
+
if (not param or (a=param.split(':',3)).size < 3)
|
64
|
+
@bscan.Log 0, "#{@mid}inject_to_pattern: 'inject_instead_of' parameter is not valid #{param}"
|
65
|
+
return
|
66
|
+
end
|
67
|
+
@bscan.Log 2, "#{@mid}inject_to_pattern input: #{a.join('|')}"
|
68
|
+
begin
|
69
|
+
p,f,proto = Regexp.escape(a[0]), a[1], a[2]
|
70
|
+
file = open_in_path(f)
|
71
|
+
req = file.read
|
72
|
+
req.gsub!(/\^M\n/,"\r\n")
|
73
|
+
file.close
|
74
|
+
|
75
|
+
injs = open_in_path(@config[prop('file')])
|
76
|
+
injs.each_line do |l|
|
77
|
+
l.chomp!
|
78
|
+
next if (l =~ /^#{COMMENT_START}/ or l.length < 1)
|
79
|
+
@bscan.Log 2, "#{@mid}inject_to_pattern injecting: #{l}"
|
80
|
+
|
81
|
+
pos = 0
|
82
|
+
while (m=req.match(/(#{p}).*?(#{p})/,pos))
|
83
|
+
r = (req[0..m.begin(1)-1] + l + req[m.end(2)..-1]).gsub /#{p}(.*?)#{p}/,'\1'
|
84
|
+
|
85
|
+
@bscan.Log 2, "#{@mid}inject_to_pattern new req:\n#{r}"
|
86
|
+
set_len r
|
87
|
+
@bscan.activity[0]=true
|
88
|
+
send_req r,proto,l
|
89
|
+
pos=m.end(1)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
injs.close
|
93
|
+
|
94
|
+
rescue Exception => e
|
95
|
+
@bscan.Log 0, "#{@mid}inject_to_pattern Exception: #{e.message}"
|
96
|
+
@bscan.Log 0, e.backtrace.join("\n")
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
def inject_to_body msg
|
102
|
+
scanf = false
|
103
|
+
@bscan.Log 2, "#{@mid}inject_to_body req: #{msg.req_str}"
|
104
|
+
msg.request_headers.each do |a|
|
105
|
+
@bscan.Log 2, "#{@mid}inject_to_body hdr: #{a[0]} #{a[1]}"
|
106
|
+
if a.size > 1 and a[0] =~ /content-type/i and a[1] =~ /application\/x-www-form-urlencoded/i
|
107
|
+
scanf = true
|
108
|
+
break
|
109
|
+
end
|
110
|
+
end
|
111
|
+
return if not scanf
|
112
|
+
m=msg.req_str.match(/\r?\n\r?\n/)
|
113
|
+
return if m.size < 1
|
114
|
+
start_pos = m.end(0)
|
115
|
+
|
116
|
+
begin
|
117
|
+
injs = open_in_path(@config[prop('file')])
|
118
|
+
injs.each_line do |l|
|
119
|
+
l.chomp!
|
120
|
+
next if (l =~ /^#{COMMENT_START}/ or l.length < 1)
|
121
|
+
@bscan.Log 2, "#{@mid}inject_to_body injecting: #{l}"
|
122
|
+
pos=start_pos
|
123
|
+
while (m=msg.req_str.match(/([^=]+)=([^=]+)/,pos))
|
124
|
+
req = msg.req_str[0..m.begin(2)-1] + l + msg.req_str[m.end(2)..-1]
|
125
|
+
req.sub!(/content-length\s*:\s*\d+/i, "Content-Length: "+(req.length-start_pos).to_s)
|
126
|
+
@bscan.Log 2, "#{@mid}inject_to_body #{pos} #{req}"
|
127
|
+
@bscan.activity[0]=true
|
128
|
+
send_req req, msg.getProtocol, l
|
129
|
+
pos=m.end(1)+1
|
130
|
+
end
|
131
|
+
end
|
132
|
+
injs.close
|
133
|
+
rescue Exception => e
|
134
|
+
@bscan.Log 0, "#{@mid}inject_to_body Exception: #{e.message}"
|
135
|
+
@bscan.Log 0, e.backtrace.join("\n")
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
|
140
|
+
|
141
|
+
|
142
|
+
end
|