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