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.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES +27 -0
  3. data/FEATURES +19 -0
  4. data/README.md +117 -0
  5. data/bin/glue +67 -0
  6. data/lib/glue.rb +317 -0
  7. data/lib/glue/event.rb +14 -0
  8. data/lib/glue/filters.rb +41 -0
  9. data/lib/glue/filters/base_filter.rb +19 -0
  10. data/lib/glue/filters/jira_one_time_filter.rb +57 -0
  11. data/lib/glue/filters/remove_all_filter.rb +16 -0
  12. data/lib/glue/filters/zap_consdensing_filter.rb +76 -0
  13. data/lib/glue/finding.rb +52 -0
  14. data/lib/glue/mounters.rb +55 -0
  15. data/lib/glue/mounters/base_mounter.rb +31 -0
  16. data/lib/glue/mounters/docker_mounter.rb +44 -0
  17. data/lib/glue/mounters/filesystem_mounter.rb +20 -0
  18. data/lib/glue/mounters/git_mounter.rb +52 -0
  19. data/lib/glue/mounters/iso_mounter.rb +42 -0
  20. data/lib/glue/mounters/url_mounter.rb +28 -0
  21. data/lib/glue/options.rb +269 -0
  22. data/lib/glue/reporters.rb +50 -0
  23. data/lib/glue/reporters/base_reporter.rb +21 -0
  24. data/lib/glue/reporters/csv_reporter.rb +19 -0
  25. data/lib/glue/reporters/jira_reporter.rb +59 -0
  26. data/lib/glue/reporters/json_reporter.rb +20 -0
  27. data/lib/glue/reporters/text_reporter.rb +19 -0
  28. data/lib/glue/scanner.rb +28 -0
  29. data/lib/glue/tasks.rb +124 -0
  30. data/lib/glue/tasks/av.rb +42 -0
  31. data/lib/glue/tasks/base_task.rb +80 -0
  32. data/lib/glue/tasks/brakeman.rb +58 -0
  33. data/lib/glue/tasks/bundle-audit.rb +95 -0
  34. data/lib/glue/tasks/checkmarx.rb +60 -0
  35. data/lib/glue/tasks/dawnscanner.rb +55 -0
  36. data/lib/glue/tasks/eslint.rb +69 -0
  37. data/lib/glue/tasks/fim.rb +60 -0
  38. data/lib/glue/tasks/findsecbugs.rb +90 -0
  39. data/lib/glue/tasks/npm.rb +58 -0
  40. data/lib/glue/tasks/nsp.rb +65 -0
  41. data/lib/glue/tasks/owasp-dep-check.rb +117 -0
  42. data/lib/glue/tasks/patterns.json +394 -0
  43. data/lib/glue/tasks/pmd.rb +63 -0
  44. data/lib/glue/tasks/retirejs.rb +107 -0
  45. data/lib/glue/tasks/scanjs-eslintrc +106 -0
  46. data/lib/glue/tasks/scanjs.rb +31 -0
  47. data/lib/glue/tasks/sfl.rb +67 -0
  48. data/lib/glue/tasks/snyk.rb +81 -0
  49. data/lib/glue/tasks/test.rb +47 -0
  50. data/lib/glue/tasks/zap.rb +99 -0
  51. data/lib/glue/tracker.rb +47 -0
  52. data/lib/glue/util.rb +36 -0
  53. data/lib/glue/version.rb +3 -0
  54. metadata +294 -0
@@ -0,0 +1,14 @@
1
+ # Tracks internal glue events.
2
+ # Can be used for control, but also tracking what happens.
3
+ class Glue::Event
4
+ attr_reader :parent
5
+ attr_accessor :path
6
+ attr_accessor :appname
7
+
8
+ def initialize appname, parent = nil
9
+ @appname = appname
10
+ @parent = parent
11
+ @timestamp = Time.now
12
+ end
13
+
14
+ end
@@ -0,0 +1,41 @@
1
+ class Glue::Filters
2
+ @filters = []
3
+
4
+ #Add a task. This will call +_klass_.new+ when running tests
5
+ def self.add klass
6
+ @filters << klass unless @filters.include? klass
7
+ end
8
+
9
+ def self.filters
10
+ @filters
11
+ end
12
+
13
+ def self.initialize_filters filters_directory = ""
14
+ Dir.glob(File.join(filters_directory, "*.rb")).sort.each do |f|
15
+ require f
16
+ end
17
+ end
18
+
19
+ #No need to use this directly.
20
+ def initialize options = { }
21
+ end
22
+
23
+ #Run all the tasks on the given Tracker.
24
+ #Returns a new instance of tasks with the results.
25
+ def self.filter(tracker)
26
+ @filters.each do |c|
27
+ filter = c.new()
28
+ begin
29
+ filter.filter tracker
30
+ rescue => e
31
+ Glue.error e.message
32
+ tracker.error e
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ #Load all files in filters/ directory
39
+ Dir.glob("#{File.expand_path(File.dirname(__FILE__))}/filters/*.rb").sort.each do |f|
40
+ require f.match(/(glue\/filters\/.*)\.rb$/)[0]
41
+ end
@@ -0,0 +1,19 @@
1
+ class Glue::BaseFilter
2
+ attr_accessor :name
3
+ attr_accessor :description
4
+
5
+ def initialize()
6
+ end
7
+
8
+ def name
9
+ @name
10
+ end
11
+
12
+ def description
13
+ @description
14
+ end
15
+
16
+ def filter
17
+ end
18
+
19
+ end
@@ -0,0 +1,57 @@
1
+ require 'glue/filters/base_filter'
2
+ require 'json'
3
+ require 'curb'
4
+
5
+ class Glue::JiraOneTimeFilter < Glue::BaseFilter
6
+
7
+ # Glue::Filters.add self
8
+
9
+ def initialize
10
+ @name = "Jira One Time Filter"
11
+ @description = "Checks that each issue that will be reported doesn't already exist in JIRA."
12
+ end
13
+
14
+ def filter tracker
15
+ @project = tracker.options[:jira_project.to_s]
16
+ @api = tracker.options[:jira_api_url.to_s]
17
+ @cookie = tracker.options[:jira_cookie.to_s]
18
+ @component = tracker.options[:jira_component.to_s]
19
+ @appname = tracker.options[:appname]
20
+
21
+ potential_findings = Array.new(tracker.findings)
22
+ tracker.findings.clear
23
+ potential_findings.each do |finding|
24
+ if confirm_new finding
25
+ tracker.report finding
26
+ end
27
+ end
28
+ end
29
+
30
+ private
31
+ def confirm_new finding
32
+ json = get_jira_query_json finding
33
+ http = Curl.post("#{@api}/search", json.to_s) do |http|
34
+ http.headers['Content-Type'] = "application/json"
35
+ http.headers['Cookie'] = @cookie
36
+ end
37
+ if http.response_code != 200 # OK ...
38
+ Glue.error "Problem with HTTP #{http.response_code} - #{http.body_str}"
39
+ end
40
+
41
+ result = JSON.parse(http.body_str)
42
+ # Glue.error "Got back #{result} with #{result['total']}"
43
+
44
+ if result['total'] < 1
45
+ return true
46
+ end
47
+ return false
48
+ end
49
+
50
+ def get_jira_query_json finding
51
+ json =
52
+ {"jql"=>"project=#{@project} AND component='#{@component}' AND labels='#{@appname}' AND description ~ 'FINGERPRINT: #{finding.fingerprint}'"}.to_json
53
+ json
54
+ end
55
+ end
56
+
57
+ # project = APPS and component = "Automated Findings" and Labels = "service-portal" and description ~ "FINGERPRINT: bundlerauditgemsource"
@@ -0,0 +1,16 @@
1
+ require 'glue/filters/base_filter'
2
+
3
+ class Glue::RemoveAllFilter < Glue::BaseFilter
4
+
5
+ #Glue::Filters.add self
6
+
7
+ def initialize
8
+ @name = "Remove All Filter"
9
+ @description = "Remove all the things..."
10
+ end
11
+
12
+ def filter tracker
13
+ tracker.findings.clear
14
+ end
15
+
16
+ end
@@ -0,0 +1,76 @@
1
+ require 'glue/filters/base_filter'
2
+
3
+ class Glue::ZAPCondensingFilter < Glue::BaseFilter
4
+
5
+ Glue::Filters.add self
6
+
7
+ def initialize
8
+ @name = "ZAP Condensing Filter"
9
+ @description = "Consolidate N ZAP warnings to one per issue type."
10
+ end
11
+
12
+ def filter tracker
13
+ Glue.debug "Have #{tracker.findings.count} items pre ZAP filter."
14
+ tracker.findings.each do |finding|
15
+ if zap? finding
16
+ if xframe? finding
17
+ record tracker,finding
18
+ elsif xcontenttypeoptions? finding
19
+ record tracker, finding
20
+ elsif dirbrowsing? finding
21
+ record tracker, finding
22
+ elsif xxssprotection? finding
23
+ record tracker, finding
24
+ end
25
+ end
26
+ end
27
+ Glue.debug "Have #{tracker.findings.count} items post ZAP filter."
28
+ end
29
+
30
+ def zap? finding
31
+ if finding.source =~ /\AZAP/
32
+ return true
33
+ end
34
+ return false
35
+ end
36
+
37
+ def xframe? finding
38
+ if finding.description == "X-Frame-Options header is not included in the HTTP response to protect against 'ClickJacking' attacks."
39
+ return true
40
+ end
41
+ return false
42
+ end
43
+
44
+ def xcontenttypeoptions? finding
45
+ if finding.description == "The Anti-MIME-Sniffing header X-Content-Type-Options was not set to 'nosniff'. This allows older versions of Internet Explorer and Chrome to perform MIME-sniffing on the response body, potentially causing the response body to be interpreted and displayed as a content type other than the declared content type. Current (early 2014) and legacy versions of Firefox will use the declared content type (if one is set), rather than performing MIME-sniffing."
46
+ return true
47
+ end
48
+ return false
49
+ end
50
+
51
+ def dirbrowsing? finding
52
+ if finding.description == "It is possible to view the directory listing. Directory listing may reveal hidden scripts, include files , backup source files etc which be accessed to read sensitive information."
53
+ return true
54
+ end
55
+ return false
56
+ end
57
+
58
+ def xxssprotection? finding
59
+ if finding.description == "Web Browser XSS Protection is not enabled, or is disabled by the configuration of the 'X-XSS-Protection' HTTP response header on the web server"
60
+ return true
61
+ end
62
+ return false
63
+ end
64
+
65
+ # Is it always true that the findings will be on the same site?
66
+ # For now, we can assume that.
67
+ # Note that this may not be an efficient way to to do this, but it seems to work as a first pass.
68
+ def record tracker, finding
69
+ tracker.findings.delete_if do |to_delete|
70
+ to_delete.description == finding.description
71
+ end
72
+ finding.detail << "\n\t** Consolidated ** - Potentially identified in > 1 spot." # Make sure to note that there were
73
+ tracker.findings.push finding
74
+ end
75
+
76
+ end
@@ -0,0 +1,52 @@
1
+ require 'json'
2
+
3
+ class Glue::Finding
4
+ attr_reader :appname
5
+ attr_reader :timestamp
6
+ attr_reader :severity
7
+ attr_reader :source
8
+ attr_reader :description
9
+ attr_reader :detail
10
+ attr_reader :fingerprint
11
+
12
+ def initialize appname, description, detail, source, severity, fingerprint
13
+ @appname = appname
14
+ @timestamp = Time.now
15
+ @description = description
16
+ @detail = detail
17
+ @source = source
18
+ @stringsrc = source.to_s
19
+ @severity = severity
20
+ @fingerprint = fingerprint
21
+ end
22
+
23
+ def to_string
24
+ s = "Finding: #{@appname}"
25
+ s << "\n\tDescription: #{@description}"
26
+ s << "\n\tTimestamp: #{@timestamp}"
27
+ s << "\n\tSource: #{@stringsrc}"
28
+ s << "\n\tSeverity: #{@severity}"
29
+ s << "\n\tFingerprint: #{@fingerprint}"
30
+ s << "\n\tDetail: #{@detail}"
31
+ s
32
+ end
33
+
34
+ def to_csv
35
+ s = "#{@appname},#{@description},#{@timestamp},#{@source.to_s},#{@severity},#{@fingerprint},#{@detail}\n"
36
+ s
37
+ end
38
+
39
+ def to_json
40
+ json = {
41
+ 'appname' => @appname,
42
+ 'description' => @description,
43
+ 'fingerprint' => @fingerprint,
44
+ 'detail' => @detail,
45
+ 'source' => @source,
46
+ 'severity' => @severity,
47
+ 'timestamp' => @timestamp
48
+ }.to_json
49
+ json
50
+ end
51
+
52
+ end
@@ -0,0 +1,55 @@
1
+ # Knows how to test file types and then defer to the helper.
2
+
3
+ require 'glue/event'
4
+
5
+ class Glue::Mounters
6
+ @mounters = []
7
+
8
+ attr_reader :target
9
+ attr_reader :warnings
10
+
11
+ def self.add klass
12
+ @mounters << klass unless @mounters.include? klass
13
+ end
14
+
15
+ def self.mounters
16
+ @mounters
17
+ end
18
+
19
+ def initialize
20
+ @warnings = []
21
+ end
22
+
23
+ def add_warning warning
24
+ @warnings << warning
25
+ end
26
+
27
+ def self.mount tracker
28
+ target = tracker.options[:target]
29
+ Glue.debug "Mounting target: #{target}"
30
+ trigger = Glue::Event.new(tracker.options[:appname])
31
+ @mounters.each do | c |
32
+ mounter = c.new trigger, tracker.options
33
+ begin
34
+ Glue.debug "Checking about mounting #{target} with #{mounter}"
35
+ if mounter.supports? target
36
+ Glue.notify "Mounting #{target} with #{mounter}"
37
+ path = mounter.mount target
38
+ Glue.notify "Mounted #{target} with #{mounter}"
39
+ return path
40
+ end
41
+ rescue => e
42
+ Glue.notify e.message
43
+ end
44
+ end
45
+ end
46
+
47
+ def self.get_mounter_name mounter_class
48
+ mounter_class.to_s.split("::").last
49
+ end
50
+ end
51
+
52
+ #Load all files in mounters/ directory
53
+ Dir.glob("#{File.expand_path(File.dirname(__FILE__))}/mounters/*.rb").sort.each do |f|
54
+ require f.match(/(glue\/mounters\/.*)\.rb$/)[0]
55
+ end
@@ -0,0 +1,31 @@
1
+
2
+ class Glue::BaseMounter
3
+ attr_reader :errors
4
+ attr_reader :trigger
5
+ attr_accessor :name
6
+ attr_accessor :description
7
+
8
+ def initialize(trigger)
9
+ @errors = []
10
+ @trigger = trigger
11
+ end
12
+
13
+ def error error
14
+ @errors << error
15
+ end
16
+
17
+ def name
18
+ @name
19
+ end
20
+
21
+ def description
22
+ @description
23
+ end
24
+
25
+ def mount
26
+ end
27
+
28
+ def supports? target
29
+ end
30
+
31
+ end
@@ -0,0 +1,44 @@
1
+ require 'glue/mounters/base_mounter'
2
+
3
+ class Glue::DockerMounter < Glue::BaseMounter
4
+
5
+ Glue::Mounters.add self
6
+
7
+ #Pass in path to the root of the Rails application
8
+ def initialize trigger, options
9
+ super(trigger)
10
+ @options = options
11
+ end
12
+
13
+ def mount target
14
+ base = @options[:working_dir]
15
+ target = target.slice(0, target.length - 7)
16
+ working_target = base + "/docker/" + target + "/"
17
+ Glue.notify "Cleaning directory: #{working_target}"
18
+ if ! working_target.match(/\A.*\/line\/tmp\/.*/)
19
+ Glue.notify "Bailing in case #{working_target} is malicious."
20
+ else
21
+ result = `rm -rf #{working_target}`
22
+ Glue.debug result
23
+ result = `mkdir -p #{working_target}`
24
+ Glue.debug result
25
+ result = `docker export #{target} > #{working_target}#{target}.tar`
26
+ Glue.debug result
27
+ result = `tar -C #{working_target} -xf #{working_target}#{target}.tar`
28
+ Glue.debug result
29
+ result = `rm #{working_target}#{target}.tar`
30
+ Glue.debug result
31
+ end
32
+ return working_target
33
+ end
34
+
35
+ def supports? target
36
+ last = target.slice(-7,target.length)
37
+ Glue.debug "In Docker mounter, target: #{target} became: #{last} ... wondering if it matched .docker"
38
+ if last === ".docker"
39
+ return true
40
+ else
41
+ return false
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,20 @@
1
+ require 'glue/mounters/base_mounter'
2
+
3
+ class Glue::FileSystemMounter < Glue::BaseMounter
4
+ Glue::Mounters.add self
5
+
6
+ def initialize trigger, options
7
+ super(trigger)
8
+ @options = options
9
+ @name = "FileSystem"
10
+ @description = "Mount a file via normal file system commands."
11
+ end
12
+
13
+ def mount target
14
+ return target
15
+ end
16
+
17
+ def supports? target
18
+ File.directory? target
19
+ end
20
+ end
@@ -0,0 +1,52 @@
1
+ require 'glue/mounters/base_mounter'
2
+ require 'fileutils'
3
+
4
+ class Glue::GitMounter < Glue::BaseMounter
5
+
6
+ Glue::Mounters.add self
7
+
8
+ def initialize trigger, options
9
+ super(trigger)
10
+ @options = options
11
+ @name = "Git"
12
+ @description = "Pull a repo."
13
+ end
14
+
15
+ def mount target
16
+ base = @options[:working_dir]
17
+
18
+ Glue.debug "Making base."
19
+ FileUtils.mkdir_p base
20
+
21
+ # Grap the path part of the git url.
22
+ protocol, path, suffix = target.match(/\A(.*\/\/)(.*)(.git)\z/i).captures
23
+ working_target = File.expand_path(base + "" + path + "/")
24
+
25
+ Glue.notify "Cleaning directory: #{working_target}"
26
+ if ! Dir.exists? working_target
27
+ Glue.notify "#{working_target} is not a directory."
28
+ FileUtils.mkdir_p working_target
29
+ else
30
+ Glue.debug "Removing : #{working_target}"
31
+ FileUtils.rm_rf working_target
32
+ FileUtils.mkdir_p working_target
33
+ end
34
+ # result = `rm -rf #{working_target}`
35
+ # puts result
36
+ Glue.debug "Cloning into: #{working_target}"
37
+ result = `git clone -q #{target} #{working_target}`
38
+ # puts result
39
+ #end
40
+ return working_target
41
+ end
42
+
43
+ def supports? target
44
+ last = target.slice(-4,target.length)
45
+ if last === ".git"
46
+ return true
47
+ else
48
+ return false
49
+ end
50
+ end
51
+
52
+ end