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,63 @@
1
+ require 'glue/tasks/base_task'
2
+ require 'glue/util'
3
+ require 'nokogiri'
4
+ require 'pathname'
5
+
6
+ class Glue::PMD < Glue::BaseTask
7
+
8
+ Glue::Tasks.add self
9
+ include Glue::Util
10
+
11
+ def initialize(trigger, tracker)
12
+ super(trigger, tracker)
13
+ @name = "PMD"
14
+ @description = "PMD Source Code Analyzer"
15
+ @stage = :code
16
+ @labels << "code"
17
+ end
18
+
19
+ def run
20
+ @tracker.options[:pmd_checks] ||= "java-basic,java-sunsecure"
21
+ Dir.chdir @tracker.options[:pmd_path] do
22
+ @results = Nokogiri::XML(runsystem(true,'bin/run.sh', 'pmd', '-d', "#{@trigger.path}", '-f', 'xml', '-R', "#{@tracker.options[:pmd_checks]}")).xpath('//file')
23
+ end
24
+ end
25
+
26
+ def analyze
27
+ begin
28
+ @results.each do |result|
29
+ attributes = result.at_xpath('violation').attributes
30
+ description = result.children.children.to_s.strip
31
+ detail = "Ruleset: #{attributes['ruleset']}"
32
+ source = {:scanner => @name, :file => result.attributes['name'].to_s.split(Pathname.new(@trigger.path).cleanpath.to_s)[1][1..-1], :line => attributes['beginline'].to_s, :code => "package: #{attributes['package'].to_s}\nclass: #{attributes['class'].to_s}\nmethod: #{attributes['method'].to_s}" }
33
+ case attributes['priority'].value.to_i
34
+ when 3
35
+ sev = 1
36
+ when 2
37
+ sev = 2
38
+ when 1
39
+ sev = 3
40
+ else
41
+ sev = 0
42
+ end
43
+ fprint = fingerprint("#{description}#{detail}#{source}#{sev}")
44
+
45
+ report description, detail, source, sev, fprint
46
+ end
47
+ rescue Exception => e
48
+ Glue.warn e.message
49
+ Glue.warn e.backtrace
50
+ end
51
+ end
52
+
53
+ def supported?
54
+ unless @tracker.options.has_key?(:pmd_path) and File.exist?("#{@tracker.options[:pmd_path]}/bin/run.sh")
55
+ Glue.notify "#{@tracker.options[:pmd_path]}"
56
+ Glue.notify "Install PMD from: https://pmd.github.io/"
57
+ return false
58
+ else
59
+ return true
60
+ end
61
+ end
62
+
63
+ end
@@ -0,0 +1,107 @@
1
+ require 'glue/tasks/base_task'
2
+ require 'json'
3
+ require 'glue/util'
4
+ require 'jsonpath'
5
+ require 'pathname'
6
+ require 'pry'
7
+
8
+ class Glue::RetireJS < Glue::BaseTask
9
+
10
+ Glue::Tasks.add self
11
+ include Glue::Util
12
+
13
+ def initialize(trigger, tracker)
14
+ super(trigger, tracker)
15
+ @name = "RetireJS"
16
+ @description = "Dependency analysis for JavaScript"
17
+ @stage = :code
18
+ @labels << "code" << "javascript"
19
+ @results = []
20
+ end
21
+
22
+ def run
23
+ exclude_dirs = ['node_modules','bower_components']
24
+ exclude_dirs = exclude_dirs.concat(@tracker.options[:exclude_dirs]).uniq if @tracker.options[:exclude_dirs]
25
+ directories_with?('package.json', exclude_dirs).each do |dir|
26
+ Glue.notify "#{@name} scanning: #{dir}"
27
+ @results << runsystem(false, 'retire', '-c', '--outputformat', 'json', '--path', "#{dir}")
28
+ end
29
+ end
30
+
31
+ def analyze
32
+ begin
33
+ @results.each do |result|
34
+ parsed_json = JSON.parse(result)
35
+ vulnerabilities = parse_retire_json(parsed_json) if parsed_json
36
+
37
+ vulnerabilities.each do |vuln|
38
+ report "Package #{vuln[:package]} has known security issues", vuln[:detail], vuln[:source], vuln[:severity], fingerprint("#{vuln[:package]}#{vuln[:source]}#{vuln[:severity]}")
39
+ end
40
+ end
41
+ rescue JSON::ParserError => e
42
+ Glue.debug e.message
43
+ rescue Exception => e
44
+ Glue.warn e.message
45
+ Glue.warn e.backtrace
46
+ end
47
+ end
48
+
49
+ def parse_retire_json result
50
+ Glue.debug "Retire JSON Raw Result: #{result}"
51
+ vulnerabilities = []
52
+ # This is very ugly, but so is the json retire.js spits out
53
+ # Loop through each component/version combo and pull all results for it
54
+ JsonPath.on(result, '$..component').uniq.each do |comp|
55
+ JsonPath.on(result, "$..results[?(@.component == \'#{comp}\')].version").uniq.each do |version|
56
+ vuln_hash = {}
57
+ vuln_hash[:package] = "#{comp}-#{version}"
58
+
59
+ version_results = JsonPath.on(result, "$..results[?(@.component == \'#{comp}\')]").select { |r| r['version'] == version }.uniq
60
+
61
+ # If we see the parent-->component relationship, dig through the dependency tree to try and make a dep map
62
+ deps = []
63
+ obj = version_results[0]
64
+ while !obj['parent'].nil?
65
+ deps << obj['parent']['component']
66
+ obj = obj['parent']
67
+ end
68
+ if deps.length > 0
69
+ vuln_hash[:source] = { :scanner => @name, :file => "#{deps.reverse.join('->')}->#{comp}-#{version}", :line => nil, :code => nil }
70
+ end
71
+
72
+ vuln_hash[:severity] = 'unknown'
73
+ # pull detail/severity
74
+ version_results.each do |version_result|
75
+ JsonPath.on(version_result, '$..vulnerabilities').uniq.each do |vuln|
76
+ vuln_hash[:severity] = severity(vuln[0]['severity'])
77
+ vuln_hash[:detail] = vuln[0]['info'].join('\n')
78
+ end
79
+ end
80
+
81
+ vulnerabilities << vuln_hash
82
+ end
83
+ end
84
+
85
+ # Loop through the separately reported 'file' findings so we can tag the source (no dep map here)
86
+ result.select { |r| !r['file'].nil? }.each do |file_result|
87
+ JsonPath.on(file_result, '$..component').uniq.each do |comp|
88
+ JsonPath.on(file_result, "$..results[?(@.component == \'#{comp}\')].version").uniq.each do |version|
89
+ source_path = relative_path(file_result['file'], @trigger.path)
90
+ vulnerabilities.select { |v| v[:package] == "#{comp}-#{version}" }.first[:source] = { :scanner => @name, :file => source_path.to_s, :line => nil, :code => nil }
91
+ end
92
+ end
93
+ end
94
+ return vulnerabilities
95
+ end
96
+
97
+ def supported?
98
+ supported=runsystem(false, "retire", "--help")
99
+ if supported =~ /command not found/
100
+ Glue.notify "Install RetireJS"
101
+ return false
102
+ else
103
+ return true
104
+ end
105
+ end
106
+
107
+ end
@@ -0,0 +1,106 @@
1
+ { "ecmaFeatures" : {
2
+ "modules" : true
3
+ },
4
+ "env" : {
5
+ "browser" : true,
6
+ "es6" : true /** all es6 features except modules */
7
+ },
8
+ "plugins" : [
9
+ "scanjs-rules",
10
+ "no-unsafe-innerhtml"
11
+ ],
12
+ "rules" : {
13
+ /** useful rules from eslint **/
14
+
15
+ /** no-unsafe-innerhtml rule **/
16
+ "no-unsafe-innerhtml/no-unsafe-innerhtml" : 2,
17
+
18
+ /** ScanJS rules **/
19
+ "scanjs-rules/assign_to_hostname" : 1,
20
+ "scanjs-rules/assign_to_href" : 1,
21
+ "scanjs-rules/assign_to_location" : 1,
22
+ "scanjs-rules/assign_to_mozAudioChannel" : 1,
23
+ "scanjs-rules/assign_to_mozAudioChannelType" : 1,
24
+ "scanjs-rules/assign_to_onmessage" : 1,
25
+ "scanjs-rules/assign_to_pathname" : 1,
26
+ "scanjs-rules/assign_to_protocol" : 1,
27
+ "scanjs-rules/assign_to_search" : 1,
28
+ "scanjs-rules/assign_to_src" : 1,
29
+ "scanjs-rules/call_Function" : 1,
30
+ "scanjs-rules/call_addEventListener" : 1,
31
+ "scanjs-rules/call_addEventListener_cellbroadcastmsgchanged" : 1,
32
+ "scanjs-rules/call_addEventListener_deviceproximity" : 1,
33
+ "scanjs-rules/call_addEventListener_message" : 1,
34
+ "scanjs-rules/call_addEventListener_moznetworkdownload" : 1,
35
+ "scanjs-rules/call_addEventListener_moznetworkupload" : 1,
36
+ "scanjs-rules/call_connect" : 1,
37
+ "scanjs-rules/call_eval" : 1,
38
+ "scanjs-rules/call_execScript" : 1,
39
+ "scanjs-rules/call_generateCRMFRequest" : 1,
40
+ "scanjs-rules/call_getDeviceStorage_apps" : 1,
41
+ "scanjs-rules/call_getDeviceStorage_crashes" : 1,
42
+ "scanjs-rules/call_getDeviceStorage_music" : 1,
43
+ "scanjs-rules/call_getDeviceStorage_pictures" : 1,
44
+ "scanjs-rules/call_getDeviceStorage_sdcard" : 1,
45
+ "scanjs-rules/call_getDeviceStorage_videos" : 1,
46
+ "scanjs-rules/call_hide" : 1,
47
+ "scanjs-rules/call_mozSetMessageHandler" : 1,
48
+ "scanjs-rules/call_mozSetMessageHandler_activity" : 1,
49
+ "scanjs-rules/call_mozSetMessageHandler_wappush_received" : 1,
50
+ "scanjs-rules/call_open_attention" : 1,
51
+ "scanjs-rules/call_open_remote=true" : 1,
52
+ "scanjs-rules/call_parseFromString" : 1,
53
+ "scanjs-rules/call_setAttribute_mozapp" : 1,
54
+ "scanjs-rules/call_setAttribute_mozbrowser" : 1,
55
+ "scanjs-rules/call_setImmediate" : 1,
56
+ "scanjs-rules/call_setInterval" : 1,
57
+ "scanjs-rules/call_setMessageHandler_connect" : 1,
58
+ "scanjs-rules/call_setTimeout" : 1,
59
+ "scanjs-rules/call_write" : 1,
60
+ "scanjs-rules/call_writeln" : 1,
61
+ "scanjs-rules/identifier_indexedDB" : 1,
62
+ "scanjs-rules/identifier_localStorage" : 1,
63
+ "scanjs-rules/identifier_sessionStorage" : 1,
64
+ "scanjs-rules/new_Function" : 1,
65
+ "scanjs-rules/new_MozActivity" : 1,
66
+ "scanjs-rules/new_MozSpeakerManager" : 1,
67
+ "scanjs-rules/new_Notification" : 1,
68
+ "scanjs-rules/object_mozSystem" : 1,
69
+ "scanjs-rules/property_addIdleObserver" : 1,
70
+ "scanjs-rules/property_createContextualFragment" : 1,
71
+ "scanjs-rules/property_geolocation" : 1,
72
+ "scanjs-rules/property_getDataStores" : 1,
73
+ "scanjs-rules/property_getDeviceStorage" : 1,
74
+ "scanjs-rules/property_getUserMedia" : 1,
75
+ "scanjs-rules/property_indexedDB" : 1,
76
+ "scanjs-rules/property_lastKnownHomeNetwork" : 1,
77
+ "scanjs-rules/property_lastKnownNetwork" : 1,
78
+ "scanjs-rules/property_localStorage" : 1,
79
+ "scanjs-rules/property_mgmt" : 1,
80
+ "scanjs-rules/property_mozAlarms" : 1,
81
+ "scanjs-rules/property_mozBluetooth" : 1,
82
+ "scanjs-rules/property_mozCameras" : 1,
83
+ "scanjs-rules/property_mozCellBroadcast" : 1,
84
+ "scanjs-rules/property_mozContacts" : 1,
85
+ "scanjs-rules/property_mozDownloadManager" : 1,
86
+ "scanjs-rules/property_mozFMRadio" : 1,
87
+ "scanjs-rules/property_mozInputMethod" : 1,
88
+ "scanjs-rules/property_mozKeyboard" : 1,
89
+ "scanjs-rules/property_mozMobileConnection" : 1,
90
+ "scanjs-rules/property_mozMobileConnections" : 1,
91
+ "scanjs-rules/property_mozMobileMessage" : 1,
92
+ "scanjs-rules/property_mozNetworkStats" : 1,
93
+ "scanjs-rules/property_mozNfc" : 1,
94
+ "scanjs-rules/property_mozNotification" : 1,
95
+ "scanjs-rules/property_mozPermissionSettings" : 1,
96
+ "scanjs-rules/property_mozPhoneNumberService" : 1,
97
+ "scanjs-rules/property_mozPower" : 1,
98
+ "scanjs-rules/property_mozSettings" : 1,
99
+ "scanjs-rules/property_mozTCPSocket" : 1,
100
+ "scanjs-rules/property_mozTelephony" : 1,
101
+ "scanjs-rules/property_mozTime" : 1,
102
+ "scanjs-rules/property_mozVoicemail" : 1,
103
+ "scanjs-rules/property_mozWifiManager" : 1,
104
+ "scanjs-rules/property_sessionStorage" : 1
105
+ }
106
+ }
@@ -0,0 +1,31 @@
1
+ require 'glue/tasks/base_task'
2
+
3
+ class Glue::ScanJS < Glue::BaseTask
4
+
5
+ # WIP
6
+ # Glue::Tasks.add self
7
+
8
+ def initialize(trigger, tracker)
9
+ super(trigger)
10
+ @name = "ScanJS"
11
+ @description = "Source analysis for JavaScript"
12
+ @stage = :code
13
+ @labels << "code" << "javascript"
14
+ end
15
+
16
+ def run
17
+ Glue.notify "#{@name}"
18
+ rootpath = @trigger.path
19
+ @result=`scanner.js -t "#{rootpath}"`
20
+ end
21
+
22
+ def analyze
23
+ puts @result
24
+ end
25
+
26
+ def supported?
27
+ # In future, verify tool is available.
28
+ return true
29
+ end
30
+
31
+ end
@@ -0,0 +1,67 @@
1
+ require 'glue/tasks/base_task'
2
+ require 'json'
3
+ require 'glue/util'
4
+ require 'find'
5
+
6
+ class Glue::SFL < Glue::BaseTask
7
+
8
+ Glue::Tasks.add self
9
+ include Glue::Util
10
+
11
+ def initialize(trigger, tracker)
12
+ super(trigger,tracker)
13
+ @name = "SFL"
14
+ @description = "Sentive Files Lookup"
15
+ @stage = :code
16
+ @labels << "code"
17
+ # Glue.debug "initialized SFL"
18
+ @patterns = read_patterns_file!
19
+ end
20
+
21
+ def run
22
+ # Glue.notify "#{@name}"
23
+ @files = Find.find(@trigger.path)
24
+ Glue.debug "Found #{@files.count} files"
25
+ end
26
+
27
+ def analyze
28
+ begin
29
+ @files.each do |file|
30
+ @patterns.each do |pattern|
31
+ case pattern['part']
32
+ when 'filename'
33
+ if pattern_matched?(File.basename(file), pattern)
34
+ report pattern['caption'], pattern['description'], @name + ":" + file, 'unknown', 'TBD'
35
+ end
36
+ when 'extension'
37
+ if pattern_matched?(File.extname(file), pattern)
38
+ report pattern['caption'], pattern['description'], @name + ":" + file, 'unknown', 'TBD'
39
+ end
40
+ end
41
+ end
42
+ end
43
+ rescue Exception => e
44
+ Glue.warn e.message
45
+ end
46
+ end
47
+
48
+ def supported?
49
+ true
50
+ end
51
+
52
+ def pattern_matched?(txt, pattrn)
53
+ case pattrn['type']
54
+ when 'match'
55
+ return txt == pattrn['pattern']
56
+ when 'regex'
57
+ regex = Regexp.new(pattrn['pattern'], Regexp::IGNORECASE)
58
+ return !regex.match(txt).nil?
59
+ end
60
+ end
61
+
62
+ def read_patterns_file!
63
+ JSON.parse(File.read("#{File.dirname(__FILE__)}/patterns.json"))
64
+ rescue JSON::ParserError => e
65
+ Glue.warn "Cannot parse pattern file: #{e.message}"
66
+ end
67
+ end
@@ -0,0 +1,81 @@
1
+ require 'glue/tasks/base_task'
2
+ require 'glue/util'
3
+ require 'redcarpet'
4
+
5
+ class Glue::Snyk < Glue::BaseTask
6
+
7
+ Glue::Tasks.add self
8
+ include Glue::Util
9
+
10
+ def initialize(trigger, tracker)
11
+ super(trigger, tracker)
12
+ @name = "Snyk"
13
+ @description = "Snyk.io JS dependency checker"
14
+ @stage = :code
15
+ @labels << "code" << "javascript"
16
+ @results = []
17
+ end
18
+
19
+ def run
20
+ exclude_dirs = ['node_modules','bower_components']
21
+ exclude_dirs = exclude_dirs.concat(@tracker.options[:exclude_dirs]).uniq if @tracker.options[:exclude_dirs]
22
+ directories_with?('package.json', exclude_dirs).each do |dir|
23
+ Glue.notify "#{@name} scanning: #{dir}"
24
+ Dir.chdir(dir) do
25
+ @results << JSON.parse(runsystem(true, "snyk", "test", "--json"))["vulnerabilities"]
26
+ end
27
+ end
28
+ end
29
+
30
+ def analyze
31
+ begin
32
+ markdown = Redcarpet::Markdown.new Redcarpet::Render::HTML.new(link_attributes: {target: "_blank"}), autolink: true, tables: true
33
+
34
+ @results.each do |dir_result|
35
+ # We build a single finding for each uniq result ID, adding the unique info (upgrade path and files) as a list
36
+ dir_result.uniq {|r| r['id']}.each do |result|
37
+ description = "#{result['name']}@#{result['version']} - #{result['title']}"
38
+
39
+ # Use Redcarpet to render the Markdown details to something pretty for web display
40
+ detail = markdown.render(result['description']).gsub('h2>','strong>').gsub('h3>', 'strong>')
41
+ upgrade_paths = [ "Upgrade Path:\n" ]
42
+ files = []
43
+
44
+ # Pull the list of files and upgrade paths from all results matching this ID
45
+ # This uses the same form as the retirejs task so it all looks nice together
46
+ dir_result.select{|r| r['id'] == result['id']}.each do |res|
47
+ res['upgradePath'].each_with_index do |upgrade, i|
48
+ upgrade_paths << "#{res['from'][i]} -> #{upgrade}"
49
+ end
50
+ files << res['from'].join('->')
51
+ end
52
+
53
+ source = {
54
+ :scanner => @name,
55
+ :file => files.join('<br>'),
56
+ :line => nil,
57
+ :code => upgrade_paths.uniq.join("\n"),
58
+ }
59
+ sev = severity(result['severity'])
60
+ fprint = fingerprint("#{description}#{detail}#{source}#{sev}")
61
+
62
+ report description, detail, source, sev, fprint
63
+ end
64
+ end
65
+ rescue Exception => e
66
+ Glue.warn e.message
67
+ Glue.warn e.backtrace
68
+ end
69
+ end
70
+
71
+ def supported?
72
+ supported = find_executable0('snyk')
73
+ unless supported
74
+ Glue.notify "Install Snyk: 'npm install -g snyk'"
75
+ return false
76
+ else
77
+ return true
78
+ end
79
+ end
80
+
81
+ end