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.
- checksums.yaml +7 -0
- data/CHANGES +27 -0
- data/FEATURES +19 -0
- data/README.md +117 -0
- data/bin/glue +67 -0
- data/lib/glue.rb +317 -0
- data/lib/glue/event.rb +14 -0
- data/lib/glue/filters.rb +41 -0
- data/lib/glue/filters/base_filter.rb +19 -0
- data/lib/glue/filters/jira_one_time_filter.rb +57 -0
- data/lib/glue/filters/remove_all_filter.rb +16 -0
- data/lib/glue/filters/zap_consdensing_filter.rb +76 -0
- data/lib/glue/finding.rb +52 -0
- data/lib/glue/mounters.rb +55 -0
- data/lib/glue/mounters/base_mounter.rb +31 -0
- data/lib/glue/mounters/docker_mounter.rb +44 -0
- data/lib/glue/mounters/filesystem_mounter.rb +20 -0
- data/lib/glue/mounters/git_mounter.rb +52 -0
- data/lib/glue/mounters/iso_mounter.rb +42 -0
- data/lib/glue/mounters/url_mounter.rb +28 -0
- data/lib/glue/options.rb +269 -0
- data/lib/glue/reporters.rb +50 -0
- data/lib/glue/reporters/base_reporter.rb +21 -0
- data/lib/glue/reporters/csv_reporter.rb +19 -0
- data/lib/glue/reporters/jira_reporter.rb +59 -0
- data/lib/glue/reporters/json_reporter.rb +20 -0
- data/lib/glue/reporters/text_reporter.rb +19 -0
- data/lib/glue/scanner.rb +28 -0
- data/lib/glue/tasks.rb +124 -0
- data/lib/glue/tasks/av.rb +42 -0
- data/lib/glue/tasks/base_task.rb +80 -0
- data/lib/glue/tasks/brakeman.rb +58 -0
- data/lib/glue/tasks/bundle-audit.rb +95 -0
- data/lib/glue/tasks/checkmarx.rb +60 -0
- data/lib/glue/tasks/dawnscanner.rb +55 -0
- data/lib/glue/tasks/eslint.rb +69 -0
- data/lib/glue/tasks/fim.rb +60 -0
- data/lib/glue/tasks/findsecbugs.rb +90 -0
- data/lib/glue/tasks/npm.rb +58 -0
- data/lib/glue/tasks/nsp.rb +65 -0
- data/lib/glue/tasks/owasp-dep-check.rb +117 -0
- data/lib/glue/tasks/patterns.json +394 -0
- data/lib/glue/tasks/pmd.rb +63 -0
- data/lib/glue/tasks/retirejs.rb +107 -0
- data/lib/glue/tasks/scanjs-eslintrc +106 -0
- data/lib/glue/tasks/scanjs.rb +31 -0
- data/lib/glue/tasks/sfl.rb +67 -0
- data/lib/glue/tasks/snyk.rb +81 -0
- data/lib/glue/tasks/test.rb +47 -0
- data/lib/glue/tasks/zap.rb +99 -0
- data/lib/glue/tracker.rb +47 -0
- data/lib/glue/util.rb +36 -0
- data/lib/glue/version.rb +3 -0
- metadata +294 -0
data/lib/glue/event.rb
ADDED
@@ -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
|
data/lib/glue/filters.rb
ADDED
@@ -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,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
|
data/lib/glue/finding.rb
ADDED
@@ -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
|