owasp-glue 0.9.0

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