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 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
@@ -0,0 +1,4 @@
1
+ ## SCABox SDK
2
+
3
+ SDK to construct SCA scanners
4
+
data/lib/scabox_sdk.rb ADDED
@@ -0,0 +1,10 @@
1
+ require_relative 'scabox_sdk/printer'
2
+ require_relative 'scabox_sdk/runner'
3
+ require_relative 'scabox_sdk/scanner'
4
+ require_relative 'scabox_sdk/codebase'
5
+ require_relative 'scabox_sdk/container'
6
+
7
+ module ScaBox
8
+ VERSION = '1.0.0'
9
+ end
10
+
@@ -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: []