owasp-pipeline 0.8.3

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.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES +23 -0
  3. data/FEATURES +19 -0
  4. data/README.md +101 -0
  5. data/bin/pipeline +67 -0
  6. data/lib/pipeline.rb +301 -0
  7. data/lib/pipeline/event.rb +14 -0
  8. data/lib/pipeline/filters.rb +41 -0
  9. data/lib/pipeline/filters/base_filter.rb +19 -0
  10. data/lib/pipeline/filters/jira_one_time_filter.rb +57 -0
  11. data/lib/pipeline/filters/remove_all_filter.rb +16 -0
  12. data/lib/pipeline/finding.rb +52 -0
  13. data/lib/pipeline/mounters.rb +55 -0
  14. data/lib/pipeline/mounters/base_mounter.rb +31 -0
  15. data/lib/pipeline/mounters/docker_mounter.rb +44 -0
  16. data/lib/pipeline/mounters/filesystem_mounter.rb +25 -0
  17. data/lib/pipeline/mounters/git_mounter.rb +52 -0
  18. data/lib/pipeline/mounters/iso_mounter.rb +42 -0
  19. data/lib/pipeline/mounters/url_mounter.rb +28 -0
  20. data/lib/pipeline/options.rb +240 -0
  21. data/lib/pipeline/reporters.rb +50 -0
  22. data/lib/pipeline/reporters/base_reporter.rb +21 -0
  23. data/lib/pipeline/reporters/csv_reporter.rb +19 -0
  24. data/lib/pipeline/reporters/jira_reporter.rb +61 -0
  25. data/lib/pipeline/reporters/json_reporter.rb +20 -0
  26. data/lib/pipeline/reporters/text_reporter.rb +19 -0
  27. data/lib/pipeline/scanner.rb +28 -0
  28. data/lib/pipeline/tasks.rb +124 -0
  29. data/lib/pipeline/tasks/av.rb +43 -0
  30. data/lib/pipeline/tasks/base_task.rb +64 -0
  31. data/lib/pipeline/tasks/brakeman.rb +60 -0
  32. data/lib/pipeline/tasks/bundle-audit.rb +93 -0
  33. data/lib/pipeline/tasks/checkmarx.rb +62 -0
  34. data/lib/pipeline/tasks/eslint.rb +71 -0
  35. data/lib/pipeline/tasks/fim.rb +61 -0
  36. data/lib/pipeline/tasks/nsp.rb +59 -0
  37. data/lib/pipeline/tasks/owasp-dep-check.rb +120 -0
  38. data/lib/pipeline/tasks/patterns.json +394 -0
  39. data/lib/pipeline/tasks/retirejs.rb +106 -0
  40. data/lib/pipeline/tasks/scanjs-eslintrc +106 -0
  41. data/lib/pipeline/tasks/scanjs.rb +32 -0
  42. data/lib/pipeline/tasks/sfl.rb +67 -0
  43. data/lib/pipeline/tasks/test.rb +47 -0
  44. data/lib/pipeline/tasks/zap.rb +84 -0
  45. data/lib/pipeline/tracker.rb +47 -0
  46. data/lib/pipeline/util.rb +39 -0
  47. data/lib/pipeline/version.rb +3 -0
  48. data/lib/zapjson.json +0 -0
  49. metadata +205 -0
@@ -0,0 +1,19 @@
1
+ require 'pipeline/finding'
2
+ require 'pipeline/reporters/base_reporter'
3
+
4
+ class Pipeline::TextReporter < Pipeline::BaseReporter
5
+
6
+ Pipeline::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
@@ -0,0 +1,28 @@
1
+ require 'pipeline/event'
2
+ require 'pipeline/tracker'
3
+ require 'pipeline/tasks'
4
+
5
+ class Pipeline::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
+ Pipeline.notify "Running tasks in stage: #{stage}"
19
+ @stage = stage
20
+ begin
21
+ Pipeline::Tasks.run_tasks(target, stage, tracker)
22
+ rescue Exception => e
23
+ Pipeline.warn e.message
24
+ raise e
25
+ end
26
+ end
27
+ end
28
+ end
@@ -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 Pipeline::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 = Pipeline::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 = Pipeline::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
+ Pipeline.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
+ Pipeline.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(/(pipeline\/tasks\/.*)\.rb$/)[0]
124
+ end
@@ -0,0 +1,43 @@
1
+ # https://gist.github.com/paulspringett/8802240
2
+
3
+ require 'pipeline/tasks/base_task'
4
+
5
+ class Pipeline::AV < Pipeline::BaseTask
6
+
7
+ Pipeline::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
+ Pipeline.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
+ Pipeline.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
43
+
@@ -0,0 +1,64 @@
1
+ require 'pipeline/finding'
2
+ require 'set'
3
+ require 'digest'
4
+
5
+ class Pipeline::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 = Pipeline::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
+
47
+ def run
48
+ end
49
+
50
+ def analyze
51
+ end
52
+
53
+ def supported?
54
+ end
55
+
56
+ def severity sev
57
+ sev = '' if sev.nil?
58
+ return 1 if @severity_filter[:low].include?(sev.strip.chomp.downcase)
59
+ return 2 if @severity_filter[:medium].include?(sev.strip.chomp.downcase)
60
+ return 3 if @severity_filter[:high].include?(sev.strip.chomp.downcase)
61
+ return 0
62
+ end
63
+
64
+ end
@@ -0,0 +1,60 @@
1
+ require 'pipeline/tasks/base_task'
2
+ require 'json'
3
+ require 'pipeline/util'
4
+ require 'pathname'
5
+
6
+ class Pipeline::Brakeman < Pipeline::BaseTask
7
+
8
+ Pipeline::Tasks.add self
9
+ include Pipeline::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
+ # Pipeline.notify "#{@name}"
21
+ rootpath = @trigger.path
22
+ @result=runsystem(true, "brakeman", "-A", "-q", "-f", "json", "#{rootpath}")
23
+ end
24
+
25
+ def analyze
26
+ # puts @result
27
+ begin
28
+ parsed = JSON.parse(@result)
29
+ parsed["warnings"].each do |warning|
30
+ file = relative_path(warning['file'], @trigger.path)
31
+
32
+ detail = "#{warning['message']}\n#{warning['link']}"
33
+ if ! warning['line']
34
+ warning['line'] = "0"
35
+ end
36
+ if ! warning['code']
37
+ warning['code'] = ""
38
+ end
39
+ source = { :scanner => @name, :file => file, :line => warning['line'], :code => warning['code'].lstrip }
40
+
41
+ report warning["warning_type"], detail, source, severity(warning["confidence"]), fingerprint("#{warning['message']}#{warning['link']}#{severity(warning["confidence"])}#{source}")
42
+ end
43
+ rescue Exception => e
44
+ Pipeline.warn e.message
45
+ Pipeline.warn e.backtrace
46
+ end
47
+ end
48
+
49
+ def supported?
50
+ supported=runsystem(true, "brakeman", "-v")
51
+ if supported =~ /command not found/
52
+ Pipeline.notify "Run: gem install brakeman"
53
+ return false
54
+ else
55
+ return true
56
+ end
57
+ end
58
+
59
+ end
60
+
@@ -0,0 +1,93 @@
1
+ require 'pipeline/tasks/base_task'
2
+ require 'json'
3
+ require 'pipeline/util'
4
+ require 'digest'
5
+
6
+ class Pipeline::BundleAudit < Pipeline::BaseTask
7
+ Pipeline::Tasks.add self
8
+ include Pipeline::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
+ end
17
+
18
+ def run
19
+ # Pipeline.notify "#{@name}"
20
+ rootpath = @trigger.path
21
+ Pipeline.debug "Rootpath: #{rootpath}"
22
+ Dir.chdir("#{rootpath}") do
23
+ @result= runsystem(true, "bundle-audit", "check")
24
+ end
25
+ end
26
+
27
+ def analyze
28
+ # puts @result
29
+ begin
30
+ get_warnings
31
+ rescue Exception => e
32
+ Pipeline.warn e.message
33
+ Pipeline.notify "Appears not to be a project with Gemfile.lock or there was another problem ... bundle-audit skipped."
34
+ end
35
+ end
36
+
37
+ def supported?
38
+ supported=runsystem(true, "bundle-audit", "update")
39
+ if supported =~ /command not found/
40
+ Pipeline.notify "Run: gem install bundler-audit"
41
+ return false
42
+ else
43
+ return true
44
+ end
45
+ end
46
+
47
+ private
48
+ def get_warnings
49
+ detail, jem, source, sev, hash = '','',{},'',''
50
+ @result.each_line do | line |
51
+
52
+ if /\S/ !~ line
53
+ # Signal section is over. Reset variables and report.
54
+ if detail != ''
55
+ report "Gem #{jem} has known security issues.", detail, source, sev, fingerprint(hash)
56
+ end
57
+
58
+ detail, jem, source, sev, hash = '','', {},'',''
59
+ end
60
+
61
+ name, value = line.chomp.split(':')
62
+ case name
63
+ when 'Name'
64
+ jem << value
65
+ hash << value
66
+ when 'Version'
67
+ jem << value
68
+ hash << value
69
+ when 'Advisory'
70
+ source = { :scanner => @name, :file => 'Gemfile.lock', :line => nil, :code => nil }
71
+ hash << value
72
+ when 'Criticality'
73
+ sev = severity(value)
74
+ hash << sev
75
+ when 'URL'
76
+ detail += line.chomp.split('URL:').last
77
+ when 'Title'
78
+ detail += ",#{value}"
79
+ when 'Solution'
80
+ detail += ": #{value}"
81
+ when 'Insecure Source URI found'
82
+ 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}")
83
+ else
84
+ if line =~ /\S/ and line !~ /Unpatched versions found/
85
+ Pipeline.debug "Not sure how to handle line: #{line}"
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+
92
+ end
93
+
@@ -0,0 +1,62 @@
1
+ require 'pipeline/tasks/base_task'
2
+ require 'pipeline/util'
3
+ # require 'nokogiri'
4
+
5
+ class Pipeline::Checkmarx < Pipeline::BaseTask
6
+
7
+ # Pipeline::Tasks.add self
8
+ include Pipeline::Util
9
+
10
+ def initialize(trigger, tracker)
11
+ super(trigger, tracker)
12
+ @name = "Checkmarx"
13
+ @description = "CxSAST"
14
+ @stage = :code
15
+ @labels << "code"
16
+ end
17
+
18
+ def run
19
+ Pipeline.notify "#{@name}"
20
+ rootpath = @trigger.path
21
+ runsystem(true, "runCxConsole.sh", "scan", "-v",
22
+ "-CxUser", "#{@tracker.options[:checkmarx_user]}",
23
+ "-CxPassword", "#{@tracker.options[:checkmarx_password]}",
24
+ "-CxServer", "#{@tracker.options[:checkmarx_server]}",
25
+ "-LocationType", "folder",
26
+ "-LocationPath", "#{rootpath}",
27
+ "-ProjectName", "#{@tracker.options[:checkmarx_project]}",
28
+ "-ReportXML", "#{rootpath}checkmarx_results.xml",
29
+ "-Log", "#{@tracker.options[:checkmarx_log]}"
30
+ )
31
+ # @results = Nokogiri::XML(File.read("#{rootpath}checkmarx_results.xml")).xpath '//Result'
32
+ end
33
+
34
+ def analyze
35
+ begin
36
+ @results.each do |result|
37
+ description = result.parent.attributes['name'].value.gsub('_', ' ')
38
+ detail = result.attributes['DeepLink'].value
39
+ source = { :scanner => @name, :file => result.attributes['FileName'].value, :line => result.attributes['Line'].value.to_i, :code => result.at_xpath('Path/PathNode/Snippet/Line/Code').text }
40
+ sev = severity(result.parent.attributes['Severity'].value)
41
+ print = fingerprint("#{description}#{source}#{sev}")
42
+
43
+ report description, detail, source, sev, print
44
+ end
45
+ rescue Exception => e
46
+ Pipeline.warn e.message
47
+ Pipeline.warn e.backtrace
48
+ end
49
+ end
50
+
51
+ def supported?
52
+ supported=runsystem(true, "runCxConsole.sh", "--help")
53
+ if supported =~ /command not found/
54
+ Pipeline.notify "Install CxConsolePlugin"
55
+ return false
56
+ else
57
+ return true
58
+ end
59
+ end
60
+
61
+ end
62
+