owasp-pipeline 0.8.3

Sign up to get free protection for your applications and to get access to all the features.
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
+