scabox_sdk 1.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.
- checksums.yaml +7 -0
- data/README.md +4 -0
- data/lib/scabox_sdk.rb +10 -0
- data/lib/scabox_sdk/codebase.rb +68 -0
- data/lib/scabox_sdk/container.rb +73 -0
- data/lib/scabox_sdk/printer.rb +75 -0
- data/lib/scabox_sdk/runner.rb +74 -0
- data/lib/scabox_sdk/scanner.rb +171 -0
- metadata +77 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a8c80dcf7f5f6a7347e69ce67fed3e010384b30eae97bf1dc4be4c3c10e799bf
|
4
|
+
data.tar.gz: 506708f0c1f04c91f4d629cfbfc7bdc5cf08581cec840bcd8ef9e32e2067728f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e8452b4ea6ee804868721b994219fc51910359ef9c91c98579ab62737c2278e4d74ac5d09f6d48041c777a0dce8e8696cad65d5695d81e621d17b49b8c1cd085
|
7
|
+
data.tar.gz: 53e2726f1f657d862c417fcc460937854cb3d5dee97483240730ade740a04f2e73c2ed5ac4a6a6ce32ed8226b3e43d467167271204b9f37ae65ebf755183400f
|
data/README.md
ADDED
data/lib/scabox_sdk.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'ostruct'
|
3
|
+
require_relative 'scanner'
|
4
|
+
|
5
|
+
|
6
|
+
module ScaBox
|
7
|
+
class CodebaseScanner < Scanner
|
8
|
+
def initialize(params)
|
9
|
+
super(params)
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
parse_opts(ARGV)
|
14
|
+
check_info_flag
|
15
|
+
|
16
|
+
if @opts.codebase.nil?
|
17
|
+
print_error("No codebase passed")
|
18
|
+
exit 1
|
19
|
+
end
|
20
|
+
check_output_flag
|
21
|
+
start_scan
|
22
|
+
end
|
23
|
+
|
24
|
+
def parse_opts(args)
|
25
|
+
@opts = OpenStruct.new
|
26
|
+
@opts.codebase = nil
|
27
|
+
@opts.output = nil
|
28
|
+
@opts.format = :json
|
29
|
+
@opts.verbose = false
|
30
|
+
@opts.info = false
|
31
|
+
@opts.color = true
|
32
|
+
|
33
|
+
opt_parser = OptionParser.new do |opts|
|
34
|
+
opts.on('-c', '--codebase=CODEBASE', 'Codebase to be scanned') do |codebase|
|
35
|
+
@opts.codebase = codebase
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.on('-f', '--format=FORMAT', 'Format to save result (json or txt)') do |f|
|
39
|
+
@opts.format = f.to_sym
|
40
|
+
end
|
41
|
+
|
42
|
+
opts.on('-o', '--output=OUTPUT', 'File to save result') do |output|
|
43
|
+
@opts.output = output
|
44
|
+
end
|
45
|
+
|
46
|
+
opts.on("-v", '--[no-]verbose', 'Run verbosely') do |v|
|
47
|
+
@opts.verbose = v
|
48
|
+
end
|
49
|
+
|
50
|
+
opts.on("-i", '--info', 'Info about the scanner') do
|
51
|
+
@opts.info = true
|
52
|
+
end
|
53
|
+
|
54
|
+
opts.on('', '--[no-]color', 'Enable/disable coloring') do |v|
|
55
|
+
@opts.color = v
|
56
|
+
enable_color(@opts.color)
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
opt_parser.parse!(args)
|
62
|
+
print_debug(@opts.inspect) if @opts.verbose
|
63
|
+
@opts
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'ostruct'
|
3
|
+
require_relative 'scanner'
|
4
|
+
|
5
|
+
|
6
|
+
module ScaBox
|
7
|
+
class ContainerScanner < Scanner
|
8
|
+
def initialize(params)
|
9
|
+
super(params)
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
parse_opts(ARGV)
|
14
|
+
check_info_flag
|
15
|
+
|
16
|
+
if @opts.image_name.nil? and @opts.image_file.nil?
|
17
|
+
print_error("No image passed")
|
18
|
+
exit 1
|
19
|
+
end
|
20
|
+
|
21
|
+
check_output_flag
|
22
|
+
start_scan
|
23
|
+
end
|
24
|
+
|
25
|
+
def parse_opts(args)
|
26
|
+
@opts = OpenStruct.new
|
27
|
+
@opts.image_name = nil
|
28
|
+
@opts.image_file = nil
|
29
|
+
@opts.output = nil
|
30
|
+
@opts.format = :json
|
31
|
+
@opts.verbose = false
|
32
|
+
@opts.info = false
|
33
|
+
@opts.color = true
|
34
|
+
|
35
|
+
opt_parser = OptionParser.new do |opts|
|
36
|
+
opts.on('-n', '--image-name=IMAGE_NAME', 'Name of image to be scanned') do |image_name|
|
37
|
+
@opts.image_name = image_name
|
38
|
+
end
|
39
|
+
|
40
|
+
opts.on('-p', '--image-file=IMAGE_PATH', 'Path of image to be scanned') do |image_file|
|
41
|
+
@opts.image_file = image_file
|
42
|
+
end
|
43
|
+
|
44
|
+
opts.on('-f', '--format=FORMAT', 'Format to save result (json or txt)') do |f|
|
45
|
+
@opts.format = f.to_sym
|
46
|
+
end
|
47
|
+
|
48
|
+
opts.on('-o', '--output=OUTPUT', 'File to save result') do |output|
|
49
|
+
@opts.output = output
|
50
|
+
end
|
51
|
+
|
52
|
+
opts.on("-v", '--[no-]verbose', 'Run verbosely') do |v|
|
53
|
+
@opts.verbose = v
|
54
|
+
end
|
55
|
+
|
56
|
+
opts.on("-i", '--info', 'Info about the scanner') do
|
57
|
+
@opts.info = true
|
58
|
+
end
|
59
|
+
|
60
|
+
opts.on('', '--[no-]color', 'Enable/disable coloring') do |v|
|
61
|
+
@opts.color = v
|
62
|
+
enable_color(@opts.color)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
opt_parser.parse!(args)
|
67
|
+
print_debug(@opts.inspect) if @opts.verbose
|
68
|
+
@opts
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'colored'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
module ScaBox
|
5
|
+
module Printer
|
6
|
+
|
7
|
+
def coloring(flag = true)
|
8
|
+
@color = flag
|
9
|
+
end
|
10
|
+
|
11
|
+
def print_title(s, level = 0)
|
12
|
+
pad = " " * (level * 4)
|
13
|
+
out_s = "#{pad}[*] #{s}"
|
14
|
+
if @color
|
15
|
+
puts out_s.bold.blue
|
16
|
+
else
|
17
|
+
puts out_s
|
18
|
+
end
|
19
|
+
@logger.info(out_s) if instance_variable_defined?("@logger")
|
20
|
+
end
|
21
|
+
|
22
|
+
def print_normal(s, level = 0)
|
23
|
+
pad = " " * (level * 4)
|
24
|
+
out_s = "#{pad}#{s}"
|
25
|
+
puts out_s
|
26
|
+
@logger.info(out_s) if instance_variable_defined?("@logger")
|
27
|
+
end
|
28
|
+
|
29
|
+
def print_success(s, level = 0)
|
30
|
+
pad = " " * (level * 4)
|
31
|
+
out_s = "#{pad}[SUCCESS] #{s}"
|
32
|
+
if @color
|
33
|
+
puts out_s.bold.green
|
34
|
+
else
|
35
|
+
puts out_s
|
36
|
+
end
|
37
|
+
@logger.info(out_s) if instance_variable_defined?("@logger")
|
38
|
+
end
|
39
|
+
|
40
|
+
def print_error(s, level = 0)
|
41
|
+
pad = " " * (level * 4)
|
42
|
+
out_s = "#{pad}[ ERROR ] #{s}"
|
43
|
+
if @color
|
44
|
+
puts out_s.bold.red
|
45
|
+
else
|
46
|
+
puts out_s
|
47
|
+
end
|
48
|
+
@logger.error(out_s) if instance_variable_defined?("@logger")
|
49
|
+
end
|
50
|
+
|
51
|
+
def print_with_label(s, label, level = 0)
|
52
|
+
pad = " " * (level * 4)
|
53
|
+
out_s = "#{pad}[#{label}] #{s}"
|
54
|
+
puts out_s
|
55
|
+
@logger.info(out_s) if instance_variable_defined?("@logger")
|
56
|
+
end
|
57
|
+
|
58
|
+
def print_debug(s, level = 0)
|
59
|
+
pad = " " * (level * 4)
|
60
|
+
now = DateTime.now.strftime('%d/%m/%Y %H:%M:%S.%3N')
|
61
|
+
out_s = "#{pad}DEBUG|#{now}| #{s}"
|
62
|
+
if @color
|
63
|
+
puts out_s.bold.yellow
|
64
|
+
else
|
65
|
+
puts out_s
|
66
|
+
end
|
67
|
+
@logger.debug(out_s) if instance_variable_defined?("@logger")
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.included(base)
|
71
|
+
#base.instance_variable_set(:@color, true)
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
module ScaBox
|
5
|
+
module Runner
|
6
|
+
|
7
|
+
# TODO: find a better way to do this
|
8
|
+
def command?(name)
|
9
|
+
`which #{name}`
|
10
|
+
$?.success?
|
11
|
+
end
|
12
|
+
|
13
|
+
def run_cmd(cmd)
|
14
|
+
print_debug(cmd) if @opts.verbose
|
15
|
+
if command?(cmd[0])
|
16
|
+
run_cmd_with_timeout(cmd)
|
17
|
+
else
|
18
|
+
print_error("Command not found: #{cmd[0]}")
|
19
|
+
exit 1
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
=begin
|
24
|
+
def run_cmd(cmd)
|
25
|
+
if command?(cmd[0])
|
26
|
+
@framework.print_debug("Command: #{cmd.join(' ')}") if @framework.verbosity > 1
|
27
|
+
#run_cmd_orig(cmd)
|
28
|
+
run_cmd_with_timeout(cmd)
|
29
|
+
else
|
30
|
+
@framework.print_error("Command: '#{cmd[0]}' not found") if @framework.verbosity > 1
|
31
|
+
@errors += 1
|
32
|
+
end
|
33
|
+
end
|
34
|
+
=end
|
35
|
+
|
36
|
+
def run_cmd_with_timeout(cmd)
|
37
|
+
out_reader = ''
|
38
|
+
err_reader = ''
|
39
|
+
Open3.popen3(*cmd) do |stdin, stdout, stderr, wait_thr|
|
40
|
+
# https://stackoverflow.com/questions/8952043/how-to-fix-hanging-popen3-in-ruby
|
41
|
+
stdin.close_write
|
42
|
+
output, pid = [], wait_thr.pid
|
43
|
+
begin
|
44
|
+
Timeout.timeout(@timeout) do
|
45
|
+
begin
|
46
|
+
err_reader = Thread.new { stderr.read }
|
47
|
+
rescue IOError
|
48
|
+
end
|
49
|
+
|
50
|
+
out_reader = stdout.read
|
51
|
+
|
52
|
+
#output = [stdout.read, stderr.read]
|
53
|
+
Process.wait(pid)
|
54
|
+
end
|
55
|
+
rescue Errno::ECHILD
|
56
|
+
rescue Timeout::Error
|
57
|
+
print_error('Timed out - Skipping...')
|
58
|
+
Process.kill('HUP', pid)
|
59
|
+
exit 1
|
60
|
+
end
|
61
|
+
[out_reader, wait_thr.value]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
|
@@ -0,0 +1,171 @@
|
|
1
|
+
require 'digest'
|
2
|
+
require 'json'
|
3
|
+
require 'tmpdir'
|
4
|
+
require 'securerandom'
|
5
|
+
require_relative 'printer'
|
6
|
+
require_relative 'runner'
|
7
|
+
|
8
|
+
|
9
|
+
module ScaBox
|
10
|
+
class Scanner
|
11
|
+
include ScaBox::Printer
|
12
|
+
include ScaBox::Runner
|
13
|
+
|
14
|
+
DEFAULT_SOLUTION = 'Update to the latest stable version or apply patches.'
|
15
|
+
|
16
|
+
attr_reader :name, :description, :support, :issues, :timeout
|
17
|
+
|
18
|
+
def initialize(params)
|
19
|
+
@name = params[:name]
|
20
|
+
@description = params[:description]
|
21
|
+
@support = params[:support]
|
22
|
+
@issues = []
|
23
|
+
@timeout = 200 * 60
|
24
|
+
enable_color(true)
|
25
|
+
end
|
26
|
+
|
27
|
+
def check_info_flag
|
28
|
+
if @opts.info
|
29
|
+
puts info
|
30
|
+
exit 0
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def check_output_flag
|
35
|
+
if @opts.output.nil?
|
36
|
+
print_error("Output -o not passed")
|
37
|
+
exit 1
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def start_scan
|
42
|
+
starting_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
43
|
+
print_title("Running #{name}...")
|
44
|
+
|
45
|
+
cmd = prepare_command
|
46
|
+
@cmd_output, status = run_cmd(cmd)
|
47
|
+
normalize_result
|
48
|
+
save_results
|
49
|
+
|
50
|
+
ending_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
51
|
+
elapsed_time = Time.at(ending_time - starting_time).utc.strftime('%H:%M:%S')
|
52
|
+
print_normal("Time elapsed: #{elapsed_time}")
|
53
|
+
end
|
54
|
+
|
55
|
+
def enable_color(enabled=true)
|
56
|
+
@color = enabled
|
57
|
+
end
|
58
|
+
|
59
|
+
def info(as_json=true)
|
60
|
+
data = {name: @name, description: @description, support: @support}
|
61
|
+
if as_json
|
62
|
+
data = JSON.pretty_generate(data)
|
63
|
+
end
|
64
|
+
data
|
65
|
+
end
|
66
|
+
|
67
|
+
def add_issue(issue)
|
68
|
+
issue[:plugin] = name
|
69
|
+
issue[:cve] = [] if issue[:cve].nil?
|
70
|
+
issue[:cve] = [issue[:cve]] if issue[:cve].kind_of?(String)
|
71
|
+
issue[:references] = [] if issue[:references].nil?
|
72
|
+
issue[:title] = build_title(issue) if issue[:title].nil?
|
73
|
+
issue[:discovery_method] = '' if issue[:discovery_method].nil?
|
74
|
+
issue[:description] = issue[:title] if issue[:description].nil?
|
75
|
+
issue[:severity] = "undefined" if issue[:severity].nil?
|
76
|
+
issue[:references] = [issue[:references]] if issue[:references].kind_of?(String)
|
77
|
+
issue[:solution] = DEFAULT_SOLUTION if issue[:solution].nil?
|
78
|
+
|
79
|
+
hash_data = [
|
80
|
+
issue[:path],
|
81
|
+
issue[:component],
|
82
|
+
issue[:version],
|
83
|
+
issue[:title],
|
84
|
+
issue[:description],
|
85
|
+
issue[:cve].sort.join(":")
|
86
|
+
]
|
87
|
+
|
88
|
+
issue[:hash] = Digest::SHA256.hexdigest(hash_data.join(':'))
|
89
|
+
@issues << issue
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
def build_title(issue)
|
94
|
+
title = "Vulnerability"
|
95
|
+
if issue[:cve].length > 0
|
96
|
+
title = issue[:cve].sort.join(',')
|
97
|
+
end
|
98
|
+
title
|
99
|
+
end
|
100
|
+
|
101
|
+
def save_results
|
102
|
+
print_normal("Issues found: #{@issues.length}")
|
103
|
+
unless @issues.empty?
|
104
|
+
if @opts.format == :json
|
105
|
+
output = JSON.pretty_generate(@issues)
|
106
|
+
else
|
107
|
+
output = txt_output
|
108
|
+
end
|
109
|
+
File.open(@opts.output, 'wb') {|file| file.write(output) }
|
110
|
+
print_normal("Output saved: #{@opts.output}")
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def txt_output
|
115
|
+
sep = "=" * 100
|
116
|
+
output = ''
|
117
|
+
@issues.each do |i|
|
118
|
+
#pp i
|
119
|
+
#puts "\n\n"
|
120
|
+
|
121
|
+
output << "Hash: #{i[:hash]}\n"
|
122
|
+
output << "Plugin: #{i[:plugin]}\n"
|
123
|
+
output << "Path: #{i[:path]}\n"
|
124
|
+
output << "Component: #{i[:component]}\n"
|
125
|
+
output << "Version: #{i[:version]}\n"
|
126
|
+
output << "Discovery Method #{i[:discovery_method]}\n"
|
127
|
+
output << "Title: #{i[:title]}\n"
|
128
|
+
output << "Description: #{i[:description].gsub("\n", "")}\n"
|
129
|
+
output << "Solution: #{i[:solution].gsub("\n", "")}\n"
|
130
|
+
output << "Severity: #{i[:severity]}\n"
|
131
|
+
output << "CVE: #{i[:cve].join(', ')}\n"
|
132
|
+
output << "References:\n#{i[:references].join("\n")}\n"
|
133
|
+
output << "#{sep}\n\n"
|
134
|
+
end
|
135
|
+
output
|
136
|
+
end
|
137
|
+
|
138
|
+
def filename_for_plugin(suffix='.json')
|
139
|
+
time_s = Time.now.strftime("%Y%m%d%H%M%S")
|
140
|
+
filename = "#{name}_#{time_s}#{suffix}"
|
141
|
+
filename
|
142
|
+
end
|
143
|
+
|
144
|
+
def parse_json_from_str(s)
|
145
|
+
content = nil
|
146
|
+
unless s.nil?
|
147
|
+
begin
|
148
|
+
content = JSON.parse(s)
|
149
|
+
rescue JSON::ParserError
|
150
|
+
end
|
151
|
+
end
|
152
|
+
content
|
153
|
+
end
|
154
|
+
|
155
|
+
def parse_json_from_file(filename)
|
156
|
+
content = nil
|
157
|
+
if File.exist?(filename)
|
158
|
+
content = parse_json_from_str(File.read(filename))
|
159
|
+
end
|
160
|
+
content
|
161
|
+
end
|
162
|
+
|
163
|
+
def gen_random_tmp_filename(suffix = '')
|
164
|
+
File.join(Dir.tmpdir, "#{SecureRandom.urlsafe_base64}#{suffix}")
|
165
|
+
end
|
166
|
+
|
167
|
+
def gen_random_tmp_filename_json
|
168
|
+
gen_random_tmp_filename('.json')
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: scabox_sdk
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- rd
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-09-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: colored
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: json
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.2.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 2.2.0
|
41
|
+
description: SDK to construct SCA scanners
|
42
|
+
email: rd@convisoappsec.com
|
43
|
+
executables: []
|
44
|
+
extensions: []
|
45
|
+
extra_rdoc_files: []
|
46
|
+
files:
|
47
|
+
- README.md
|
48
|
+
- lib/scabox_sdk.rb
|
49
|
+
- lib/scabox_sdk/codebase.rb
|
50
|
+
- lib/scabox_sdk/container.rb
|
51
|
+
- lib/scabox_sdk/printer.rb
|
52
|
+
- lib/scabox_sdk/runner.rb
|
53
|
+
- lib/scabox_sdk/scanner.rb
|
54
|
+
homepage: ''
|
55
|
+
licenses:
|
56
|
+
- MIT
|
57
|
+
metadata: {}
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '2.4'
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
requirements: []
|
73
|
+
rubygems_version: 3.2.3
|
74
|
+
signing_key:
|
75
|
+
specification_version: 4
|
76
|
+
summary: SCABox SDK
|
77
|
+
test_files: []
|