owasp-glue 0.9.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/CHANGES +27 -0
- data/FEATURES +19 -0
- data/README.md +117 -0
- data/bin/glue +67 -0
- data/lib/glue.rb +317 -0
- data/lib/glue/event.rb +14 -0
- data/lib/glue/filters.rb +41 -0
- data/lib/glue/filters/base_filter.rb +19 -0
- data/lib/glue/filters/jira_one_time_filter.rb +57 -0
- data/lib/glue/filters/remove_all_filter.rb +16 -0
- data/lib/glue/filters/zap_consdensing_filter.rb +76 -0
- data/lib/glue/finding.rb +52 -0
- data/lib/glue/mounters.rb +55 -0
- data/lib/glue/mounters/base_mounter.rb +31 -0
- data/lib/glue/mounters/docker_mounter.rb +44 -0
- data/lib/glue/mounters/filesystem_mounter.rb +20 -0
- data/lib/glue/mounters/git_mounter.rb +52 -0
- data/lib/glue/mounters/iso_mounter.rb +42 -0
- data/lib/glue/mounters/url_mounter.rb +28 -0
- data/lib/glue/options.rb +269 -0
- data/lib/glue/reporters.rb +50 -0
- data/lib/glue/reporters/base_reporter.rb +21 -0
- data/lib/glue/reporters/csv_reporter.rb +19 -0
- data/lib/glue/reporters/jira_reporter.rb +59 -0
- data/lib/glue/reporters/json_reporter.rb +20 -0
- data/lib/glue/reporters/text_reporter.rb +19 -0
- data/lib/glue/scanner.rb +28 -0
- data/lib/glue/tasks.rb +124 -0
- data/lib/glue/tasks/av.rb +42 -0
- data/lib/glue/tasks/base_task.rb +80 -0
- data/lib/glue/tasks/brakeman.rb +58 -0
- data/lib/glue/tasks/bundle-audit.rb +95 -0
- data/lib/glue/tasks/checkmarx.rb +60 -0
- data/lib/glue/tasks/dawnscanner.rb +55 -0
- data/lib/glue/tasks/eslint.rb +69 -0
- data/lib/glue/tasks/fim.rb +60 -0
- data/lib/glue/tasks/findsecbugs.rb +90 -0
- data/lib/glue/tasks/npm.rb +58 -0
- data/lib/glue/tasks/nsp.rb +65 -0
- data/lib/glue/tasks/owasp-dep-check.rb +117 -0
- data/lib/glue/tasks/patterns.json +394 -0
- data/lib/glue/tasks/pmd.rb +63 -0
- data/lib/glue/tasks/retirejs.rb +107 -0
- data/lib/glue/tasks/scanjs-eslintrc +106 -0
- data/lib/glue/tasks/scanjs.rb +31 -0
- data/lib/glue/tasks/sfl.rb +67 -0
- data/lib/glue/tasks/snyk.rb +81 -0
- data/lib/glue/tasks/test.rb +47 -0
- data/lib/glue/tasks/zap.rb +99 -0
- data/lib/glue/tracker.rb +47 -0
- data/lib/glue/util.rb +36 -0
- data/lib/glue/version.rb +3 -0
- metadata +294 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'glue/finding'
|
2
|
+
require 'glue/reporters/base_reporter'
|
3
|
+
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
class Glue::JSONReporter < Glue::BaseReporter
|
7
|
+
|
8
|
+
Glue::Reporters.add self
|
9
|
+
|
10
|
+
attr_accessor :name, :format
|
11
|
+
|
12
|
+
def initialize()
|
13
|
+
@name = "JSONReporter"
|
14
|
+
@format = :to_json
|
15
|
+
end
|
16
|
+
|
17
|
+
def out(finding)
|
18
|
+
finding.to_json
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'glue/finding'
|
2
|
+
require 'glue/reporters/base_reporter'
|
3
|
+
|
4
|
+
class Glue::TextReporter < Glue::BaseReporter
|
5
|
+
|
6
|
+
Glue::Reporters.add self
|
7
|
+
|
8
|
+
attr_accessor :name, :format
|
9
|
+
|
10
|
+
def initialize()
|
11
|
+
@name = "TextReporter"
|
12
|
+
@format = :to_s
|
13
|
+
end
|
14
|
+
|
15
|
+
def out(finding)
|
16
|
+
finding.to_string << "\n"
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
data/lib/glue/scanner.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'glue/event'
|
2
|
+
require 'glue/tracker'
|
3
|
+
require 'glue/tasks'
|
4
|
+
|
5
|
+
class Glue::Scanner
|
6
|
+
attr_reader :tracker
|
7
|
+
attr_reader :mounter
|
8
|
+
|
9
|
+
#Pass in path to the root of the Rails application
|
10
|
+
def initialize
|
11
|
+
@stage = :wait
|
12
|
+
@stages = [ :wait, :mount, :file, :code, :live, :done]
|
13
|
+
end
|
14
|
+
|
15
|
+
#Process everything in the Rails application
|
16
|
+
def process target, tracker
|
17
|
+
@stages.each do |stage|
|
18
|
+
Glue.notify "Running tasks in stage: #{stage}"
|
19
|
+
@stage = stage
|
20
|
+
begin
|
21
|
+
Glue::Tasks.run_tasks(target, stage, tracker)
|
22
|
+
rescue Exception => e
|
23
|
+
Glue.warn e.message
|
24
|
+
raise e
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/glue/tasks.rb
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
#Collects up results from running different tasks.
|
4
|
+
#
|
5
|
+
#tasks can be added with +task.add(task_class)+
|
6
|
+
#
|
7
|
+
#All .rb files in tasks/ will be loaded.
|
8
|
+
class Glue::Tasks
|
9
|
+
@tasks = []
|
10
|
+
@optional_tasks = []
|
11
|
+
|
12
|
+
attr_reader :tasks_run
|
13
|
+
|
14
|
+
#Add a task. This will call +_klass_.new+ when running tests
|
15
|
+
def self.add klass
|
16
|
+
@tasks << klass unless @tasks.include? klass
|
17
|
+
end
|
18
|
+
|
19
|
+
#Add an optional task
|
20
|
+
def self.add_optional klass
|
21
|
+
@optional_tasks << klass unless @tasks.include? klass
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.tasks
|
25
|
+
@tasks + @optional_tasks
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.optional_tasks
|
29
|
+
@optional_tasks
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.initialize_tasks task_directory = ""
|
33
|
+
#Load all files in task_directory
|
34
|
+
Dir.glob(File.join(task_directory, "*.rb")).sort.each do |f|
|
35
|
+
require f
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
#No need to use this directly.
|
40
|
+
def initialize options = { }
|
41
|
+
@warnings = []
|
42
|
+
@tasks_run = []
|
43
|
+
end
|
44
|
+
|
45
|
+
#Add Warning to list of warnings to report.
|
46
|
+
def add_warning warning
|
47
|
+
@warnings << warning
|
48
|
+
end
|
49
|
+
|
50
|
+
#Run all the tasks on the given Tracker.
|
51
|
+
#Returns a new instance of tasks with the results.
|
52
|
+
def self.run_tasks(target, stage, tracker)
|
53
|
+
task_runner = self.new
|
54
|
+
|
55
|
+
trigger = Glue::Event.new(tracker.options[:appname])
|
56
|
+
trigger.path = target
|
57
|
+
|
58
|
+
self.tasks_to_run(tracker).each do |c|
|
59
|
+
task_name = get_task_name c
|
60
|
+
|
61
|
+
#Run or don't run task based on options
|
62
|
+
#Now case-insensitive specifiers: nodesecurityproject = Glue::NodeSecurityProject
|
63
|
+
|
64
|
+
if tracker.options[:skip_tasks]
|
65
|
+
skip_tasks = tracker.options[:skip_tasks].map {|task| task.downcase}
|
66
|
+
end
|
67
|
+
if (tracker.options[:run_tasks])
|
68
|
+
run_tasks = tracker.options[:run_tasks].map {|task| task.downcase}
|
69
|
+
end
|
70
|
+
|
71
|
+
unless skip_tasks.include? task_name.downcase or
|
72
|
+
(run_tasks and not run_tasks.include? task_name.downcase)
|
73
|
+
|
74
|
+
task = c.new(trigger, tracker)
|
75
|
+
begin
|
76
|
+
if task.supported? and task.stage == stage
|
77
|
+
if task.labels.intersect? tracker.options[:labels] or # Only run tasks with labels
|
78
|
+
( run_tasks and run_tasks.include? task_name.downcase ) # or that are explicitly requested.
|
79
|
+
Glue.notify "#{stage} - #{task_name} - #{task.labels}"
|
80
|
+
task.run
|
81
|
+
task.analyze
|
82
|
+
task.findings.each do | finding |
|
83
|
+
tracker.report finding
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
rescue => e
|
88
|
+
Glue.notify e.message
|
89
|
+
tracker.error e
|
90
|
+
end
|
91
|
+
|
92
|
+
task.warnings.each do |w|
|
93
|
+
task_runner.add_warning w
|
94
|
+
end
|
95
|
+
|
96
|
+
#Maintain list of which tasks were run
|
97
|
+
#mainly for reporting purposes
|
98
|
+
task_runner.tasks_run << task_name[5..-1]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
task_runner
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def self.get_task_name task_class
|
109
|
+
task_class.to_s.split("::").last
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.tasks_to_run tracker
|
113
|
+
if tracker.options[:run_all_tasks] or tracker.options[:run_tasks]
|
114
|
+
@tasks + @optional_tasks
|
115
|
+
else
|
116
|
+
@tasks
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
#Load all files in tasks/ directory
|
122
|
+
Dir.glob("#{File.expand_path(File.dirname(__FILE__))}/tasks/*.rb").sort.each do |f|
|
123
|
+
require f.match(/(glue\/tasks\/.*)\.rb$/)[0]
|
124
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# https://gist.github.com/paulspringett/8802240
|
2
|
+
|
3
|
+
require 'glue/tasks/base_task'
|
4
|
+
|
5
|
+
class Glue::AV < Glue::BaseTask
|
6
|
+
|
7
|
+
Glue::Tasks.add self
|
8
|
+
|
9
|
+
def initialize(trigger, tracker)
|
10
|
+
super(trigger,tracker)
|
11
|
+
@name = "AV"
|
12
|
+
@description = "Test for virus/malware"
|
13
|
+
@stage = :file
|
14
|
+
@labels << "filesystem"
|
15
|
+
end
|
16
|
+
|
17
|
+
def run
|
18
|
+
# Update AV
|
19
|
+
`freshclam`
|
20
|
+
# Run AV
|
21
|
+
# TODO: Circle back and use runsystem.
|
22
|
+
Glue.notify "Malware/Virus Check"
|
23
|
+
rootpath = @trigger.path
|
24
|
+
@result=`clamscan --no-summary -i -r "#{rootpath}"`
|
25
|
+
end
|
26
|
+
|
27
|
+
def analyze
|
28
|
+
list = @result.split(/\n/)
|
29
|
+
list.each do |v|
|
30
|
+
# v.slice! installdir
|
31
|
+
Glue.notify v
|
32
|
+
report "Malicious file identified.", v, @name, :medium
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def supported?
|
37
|
+
# TODO verify.
|
38
|
+
# In future, verify tool is available.
|
39
|
+
return true
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'glue/finding'
|
2
|
+
require 'set'
|
3
|
+
require 'digest'
|
4
|
+
|
5
|
+
class Glue::BaseTask
|
6
|
+
attr_reader :findings, :warnings, :trigger, :labels
|
7
|
+
attr_accessor :name
|
8
|
+
attr_accessor :description
|
9
|
+
attr_accessor :stage
|
10
|
+
attr_accessor :appname
|
11
|
+
|
12
|
+
def initialize(trigger, tracker)
|
13
|
+
@findings = []
|
14
|
+
@warnings = []
|
15
|
+
@labels = Set.new
|
16
|
+
@trigger = trigger
|
17
|
+
@tracker = tracker
|
18
|
+
@severity_filter = {
|
19
|
+
:low => ['low','weak'],
|
20
|
+
:medium => ['medium','med','average'],
|
21
|
+
:high => ['high','severe','critical']
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
def report description, detail, source, severity, fingerprint
|
26
|
+
finding = Glue::Finding.new( @trigger.appname, description, detail, source, severity, fingerprint )
|
27
|
+
@findings << finding
|
28
|
+
end
|
29
|
+
|
30
|
+
def warn warning
|
31
|
+
@warnings << warning
|
32
|
+
end
|
33
|
+
|
34
|
+
def name
|
35
|
+
@name
|
36
|
+
end
|
37
|
+
|
38
|
+
def description
|
39
|
+
@description
|
40
|
+
end
|
41
|
+
|
42
|
+
def stage
|
43
|
+
@stage
|
44
|
+
end
|
45
|
+
|
46
|
+
def directories_with? file, exclude_dirs = []
|
47
|
+
exclude_dirs = @tracker.options[:exclude_dirs] if exclude_dirs == [] and @tracker.options[:exclude_dirs]
|
48
|
+
results = []
|
49
|
+
|
50
|
+
Find.find(@trigger.path) do |path|
|
51
|
+
if FileTest.directory? path
|
52
|
+
Find.prune if exclude_dirs.include? File.basename(path) or exclude_dirs.include? File.basename(path) + '/'
|
53
|
+
next
|
54
|
+
end
|
55
|
+
|
56
|
+
Find.prune unless File.basename(path) == file
|
57
|
+
|
58
|
+
results << File.dirname(path)
|
59
|
+
end
|
60
|
+
return results
|
61
|
+
end
|
62
|
+
|
63
|
+
def run
|
64
|
+
end
|
65
|
+
|
66
|
+
def analyze
|
67
|
+
end
|
68
|
+
|
69
|
+
def supported?
|
70
|
+
end
|
71
|
+
|
72
|
+
def severity sev
|
73
|
+
sev = '' if sev.nil?
|
74
|
+
return 1 if @severity_filter[:low].include?(sev.strip.chomp.downcase)
|
75
|
+
return 2 if @severity_filter[:medium].include?(sev.strip.chomp.downcase)
|
76
|
+
return 3 if @severity_filter[:high].include?(sev.strip.chomp.downcase)
|
77
|
+
return 0
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'glue/tasks/base_task'
|
2
|
+
require 'json'
|
3
|
+
require 'glue/util'
|
4
|
+
require 'pathname'
|
5
|
+
|
6
|
+
class Glue::Brakeman < Glue::BaseTask
|
7
|
+
|
8
|
+
Glue::Tasks.add self
|
9
|
+
include Glue::Util
|
10
|
+
|
11
|
+
def initialize(trigger, tracker)
|
12
|
+
super(trigger, tracker)
|
13
|
+
@name = "Brakeman"
|
14
|
+
@description = "Source analysis for Ruby"
|
15
|
+
@stage = :code
|
16
|
+
@labels << "code" << "ruby" << "rails"
|
17
|
+
end
|
18
|
+
|
19
|
+
def run
|
20
|
+
rootpath = @trigger.path
|
21
|
+
@result=runsystem(true, "brakeman", "-A", "-q", "-f", "json", "#{rootpath}")
|
22
|
+
end
|
23
|
+
|
24
|
+
def analyze
|
25
|
+
# puts @result
|
26
|
+
begin
|
27
|
+
parsed = JSON.parse(@result)
|
28
|
+
parsed["warnings"].each do |warning|
|
29
|
+
file = relative_path(warning['file'], @trigger.path)
|
30
|
+
|
31
|
+
detail = "#{warning['message']}\n#{warning['link']}"
|
32
|
+
if ! warning['line']
|
33
|
+
warning['line'] = "0"
|
34
|
+
end
|
35
|
+
if ! warning['code']
|
36
|
+
warning['code'] = ""
|
37
|
+
end
|
38
|
+
source = { :scanner => @name, :file => file, :line => warning['line'], :code => warning['code'].lstrip }
|
39
|
+
|
40
|
+
report warning["warning_type"], detail, source, severity(warning["confidence"]), fingerprint("#{warning['message']}#{warning['link']}#{severity(warning["confidence"])}#{source}")
|
41
|
+
end
|
42
|
+
rescue Exception => e
|
43
|
+
Glue.warn e.message
|
44
|
+
Glue.warn e.backtrace
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def supported?
|
49
|
+
supported=runsystem(true, "brakeman", "-v")
|
50
|
+
if supported =~ /command not found/
|
51
|
+
Glue.notify "Run: gem install brakeman"
|
52
|
+
return false
|
53
|
+
else
|
54
|
+
return true
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'glue/tasks/base_task'
|
2
|
+
require 'json'
|
3
|
+
require 'glue/util'
|
4
|
+
require 'digest'
|
5
|
+
|
6
|
+
class Glue::BundleAudit < Glue::BaseTask
|
7
|
+
Glue::Tasks.add self
|
8
|
+
include Glue::Util
|
9
|
+
|
10
|
+
def initialize(trigger, tracker)
|
11
|
+
super(trigger, tracker)
|
12
|
+
@name = "BundleAudit"
|
13
|
+
@description = "Dependency Checker analysis for Ruby"
|
14
|
+
@stage = :code
|
15
|
+
@labels << "code" << "ruby"
|
16
|
+
@results = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def run
|
20
|
+
directories_with?('Gemfile.lock').each do |dir|
|
21
|
+
Glue.notify "#{@name} scanning: #{dir}"
|
22
|
+
Dir.chdir(dir) do
|
23
|
+
@results[dir] = runsystem(true, "bundle-audit", "check")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def analyze
|
29
|
+
# puts @result
|
30
|
+
begin
|
31
|
+
get_warnings
|
32
|
+
rescue Exception => e
|
33
|
+
Glue.warn e.message
|
34
|
+
Glue.notify "Appears not to be a project with Gemfile.lock or there was another problem ... bundle-audit skipped."
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def supported?
|
39
|
+
supported=runsystem(false, "bundle-audit", "update")
|
40
|
+
if supported =~ /command not found/
|
41
|
+
Glue.notify "Run: gem install bundler-audit"
|
42
|
+
return false
|
43
|
+
else
|
44
|
+
return true
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def get_warnings
|
50
|
+
@results.each do |dir, result|
|
51
|
+
detail, jem, source, sev, hash = '','',{},'',''
|
52
|
+
result.each_line do | line |
|
53
|
+
|
54
|
+
if /\S/ !~ line
|
55
|
+
# Signal section is over. Reset variables and report.
|
56
|
+
if detail != ''
|
57
|
+
report "Gem #{jem} has known security issues.", detail, source, sev, fingerprint(hash)
|
58
|
+
end
|
59
|
+
|
60
|
+
detail, jem, source, sev, hash = '','', {},'',''
|
61
|
+
end
|
62
|
+
|
63
|
+
name, value = line.chomp.split(':')
|
64
|
+
case name
|
65
|
+
when 'Name'
|
66
|
+
jem << value
|
67
|
+
hash << value
|
68
|
+
when 'Version'
|
69
|
+
jem << value
|
70
|
+
hash << value
|
71
|
+
when 'Advisory'
|
72
|
+
source = { :scanner => @name, :file => "#{relative_path(dir, @trigger.path)}/Gemfile.lock", :line => nil, :code => nil }
|
73
|
+
hash << value
|
74
|
+
when 'Criticality'
|
75
|
+
sev = severity(value)
|
76
|
+
hash << sev
|
77
|
+
when 'URL'
|
78
|
+
detail += line.chomp.split('URL:').last
|
79
|
+
when 'Title'
|
80
|
+
detail += ",#{value}"
|
81
|
+
when 'Solution'
|
82
|
+
detail += ": #{value}"
|
83
|
+
when 'Insecure Source URI found'
|
84
|
+
report "Insecure GEM Source", "#{line.chomp} - use git or https", {:scanner => @name, :file => 'Gemfile.lock', :line => nil, :code => nil}, severity('high'), fingerprint("bundlerauditgemsource#{line.chomp}")
|
85
|
+
else
|
86
|
+
if line =~ /\S/ and line !~ /Unpatched versions found/
|
87
|
+
Glue.debug "Not sure how to handle line: #{line}"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
end
|