owasp-pipeline 0.8.3

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 (49) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES +23 -0
  3. data/FEATURES +19 -0
  4. data/README.md +101 -0
  5. data/bin/pipeline +67 -0
  6. data/lib/pipeline.rb +301 -0
  7. data/lib/pipeline/event.rb +14 -0
  8. data/lib/pipeline/filters.rb +41 -0
  9. data/lib/pipeline/filters/base_filter.rb +19 -0
  10. data/lib/pipeline/filters/jira_one_time_filter.rb +57 -0
  11. data/lib/pipeline/filters/remove_all_filter.rb +16 -0
  12. data/lib/pipeline/finding.rb +52 -0
  13. data/lib/pipeline/mounters.rb +55 -0
  14. data/lib/pipeline/mounters/base_mounter.rb +31 -0
  15. data/lib/pipeline/mounters/docker_mounter.rb +44 -0
  16. data/lib/pipeline/mounters/filesystem_mounter.rb +25 -0
  17. data/lib/pipeline/mounters/git_mounter.rb +52 -0
  18. data/lib/pipeline/mounters/iso_mounter.rb +42 -0
  19. data/lib/pipeline/mounters/url_mounter.rb +28 -0
  20. data/lib/pipeline/options.rb +240 -0
  21. data/lib/pipeline/reporters.rb +50 -0
  22. data/lib/pipeline/reporters/base_reporter.rb +21 -0
  23. data/lib/pipeline/reporters/csv_reporter.rb +19 -0
  24. data/lib/pipeline/reporters/jira_reporter.rb +61 -0
  25. data/lib/pipeline/reporters/json_reporter.rb +20 -0
  26. data/lib/pipeline/reporters/text_reporter.rb +19 -0
  27. data/lib/pipeline/scanner.rb +28 -0
  28. data/lib/pipeline/tasks.rb +124 -0
  29. data/lib/pipeline/tasks/av.rb +43 -0
  30. data/lib/pipeline/tasks/base_task.rb +64 -0
  31. data/lib/pipeline/tasks/brakeman.rb +60 -0
  32. data/lib/pipeline/tasks/bundle-audit.rb +93 -0
  33. data/lib/pipeline/tasks/checkmarx.rb +62 -0
  34. data/lib/pipeline/tasks/eslint.rb +71 -0
  35. data/lib/pipeline/tasks/fim.rb +61 -0
  36. data/lib/pipeline/tasks/nsp.rb +59 -0
  37. data/lib/pipeline/tasks/owasp-dep-check.rb +120 -0
  38. data/lib/pipeline/tasks/patterns.json +394 -0
  39. data/lib/pipeline/tasks/retirejs.rb +106 -0
  40. data/lib/pipeline/tasks/scanjs-eslintrc +106 -0
  41. data/lib/pipeline/tasks/scanjs.rb +32 -0
  42. data/lib/pipeline/tasks/sfl.rb +67 -0
  43. data/lib/pipeline/tasks/test.rb +47 -0
  44. data/lib/pipeline/tasks/zap.rb +84 -0
  45. data/lib/pipeline/tracker.rb +47 -0
  46. data/lib/pipeline/util.rb +39 -0
  47. data/lib/pipeline/version.rb +3 -0
  48. data/lib/zapjson.json +0 -0
  49. metadata +205 -0
@@ -0,0 +1,106 @@
1
+ require 'pipeline/tasks/base_task'
2
+ require 'json'
3
+ require 'pipeline/util'
4
+ require 'jsonpath'
5
+ require 'pathname'
6
+
7
+ class Pipeline::RetireJS < Pipeline::BaseTask
8
+
9
+ Pipeline::Tasks.add self
10
+ include Pipeline::Util
11
+
12
+ def initialize(trigger, tracker)
13
+ super(trigger, tracker)
14
+ @name = "RetireJS"
15
+ @description = "Dependency analysis for JavaScript"
16
+ @stage = :code
17
+ @labels << "code" << "javascript"
18
+ end
19
+
20
+ def run
21
+ rootpath = @trigger.path
22
+ Pipeline.debug "Retire rootpath: #{rootpath}"
23
+ Dir.chdir("#{rootpath}") do
24
+ if @tracker.options.has_key?(:npm_registry)
25
+ registry = "--registry #{@tracker.options[:npm_registry]}"
26
+ else
27
+ registry = nil
28
+ end
29
+ @result = `npm install --ignore-scripts #{registry}` # Need this even though it is slow to get full dependency analysis.
30
+ end
31
+ @result = `retire -c --outputformat json --path #{rootpath} 2>&1`
32
+ end
33
+
34
+ def analyze
35
+ begin
36
+ vulnerabilities = parse_retire_json(JSON.parse(@result))
37
+
38
+ vulnerabilities.each do |vuln|
39
+ report "Package #{vuln[:package]} has known security issues", vuln[:detail], vuln[:source], vuln[:severity], fingerprint("#{vuln[:package]}#{vuln[:source]}#{vuln[:severity]}")
40
+ end
41
+ rescue Exception => e
42
+ Pipeline.warn e.message
43
+ Pipeline.warn e.backtrace
44
+ end
45
+ end
46
+
47
+ def parse_retire_json result
48
+ Pipeline.debug "Retire JSON Raw Result: #{result}"
49
+ vulnerabilities = []
50
+ # This is very ugly, but so is the json retire.js spits out
51
+ # Loop through each component/version combo and pull all results for it
52
+ JsonPath.on(result, '$..component').uniq.each do |comp|
53
+ JsonPath.on(result, "$..results[?(@.component == \'#{comp}\')].version").uniq.each do |version|
54
+ vuln_hash = {}
55
+ vuln_hash[:package] = "#{comp}-#{version}"
56
+
57
+ version_results = JsonPath.on(result, "$..results[?(@.component == \'#{comp}\')]").select { |r| r['version'] == version }.uniq
58
+
59
+ # If we see the parent-->component relationship, dig through the dependency tree to try and make a dep map
60
+ deps = []
61
+ obj = version_results[0]
62
+ while !obj['parent'].nil?
63
+ deps << obj['parent']['component']
64
+ obj = obj['parent']
65
+ end
66
+ if deps.length > 0
67
+ vuln_hash[:source] = { :scanner => @name, :file => "#{deps.reverse.join('->')}->#{comp}-#{version}", :line => nil, :code => nil }
68
+ end
69
+
70
+ vuln_hash[:severity] = 'unknown'
71
+ # pull detail/severity
72
+ version_results.each do |version_result|
73
+ JsonPath.on(version_result, '$..vulnerabilities').uniq.each do |vuln|
74
+ vuln_hash[:severity] = severity(vuln[0]['severity'])
75
+ vuln_hash[:detail] = vuln[0]['info'].join('\n')
76
+ end
77
+ end
78
+
79
+ vulnerabilities << vuln_hash
80
+ end
81
+ end
82
+
83
+ # Loop through the separately reported 'file' findings so we can tag the source (no dep map here)
84
+ result.select { |r| !r['file'].nil? }.each do |file_result|
85
+ JsonPath.on(file_result, '$..component').uniq.each do |comp|
86
+ JsonPath.on(file_result, "$..results[?(@.component == \'#{comp}\')].version").uniq.each do |version|
87
+ source_path = Pathname.new(file_result['file']).relative_path_from Pathname.new(@trigger.path)
88
+ vulnerabilities.select { |v| v[:package] == "#{comp}-#{version}" }.first[:source] = { :scanner => @name, :file => source_path.to_s, :line => nil, :code => nil }
89
+ end
90
+ end
91
+ end
92
+ return vulnerabilities
93
+ end
94
+
95
+ def supported?
96
+ supported=runsystem(true, "retire", "--help")
97
+ if supported =~ /command not found/
98
+ Pipeline.notify "Install RetireJS"
99
+ return false
100
+ else
101
+ return true
102
+ end
103
+ end
104
+
105
+ end
106
+
@@ -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,32 @@
1
+ require 'pipeline/tasks/base_task'
2
+
3
+ class Pipeline::ScanJS < Pipeline::BaseTask
4
+
5
+ # WIP
6
+ # Pipeline::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
+ Pipeline.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
32
+
@@ -0,0 +1,67 @@
1
+ require 'pipeline/tasks/base_task'
2
+ require 'json'
3
+ require 'pipeline/util'
4
+ require 'find'
5
+
6
+ class Pipeline::SFL < Pipeline::BaseTask
7
+
8
+ Pipeline::Tasks.add self
9
+ include Pipeline::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
+ # Pipeline.debug "initialized SFL"
18
+ @patterns = read_patterns_file!
19
+ end
20
+
21
+ def run
22
+ # Pipeline.notify "#{@name}"
23
+ @files = Find.find(@trigger.path)
24
+ Pipeline.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
+ Pipeline.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
+ Pipeline.warn "Cannot parse pattern file: #{e.message}"
66
+ end
67
+ end
@@ -0,0 +1,47 @@
1
+ require 'pipeline/tasks/base_task'
2
+ require 'pipeline/util'
3
+
4
+ class Pipeline::Test < Pipeline::BaseTask
5
+ Pipeline::Tasks.add self
6
+ include Pipeline::Util
7
+
8
+ def initialize(trigger, tracker)
9
+ super(trigger, tracker)
10
+ @name = "Test"
11
+ @description = "Test"
12
+ @stage = :code
13
+ @labels << "code" << "ruby"
14
+ end
15
+
16
+ def run
17
+ # Pipeline.notify "#{@name}"
18
+ rootpath = @trigger.path
19
+ Pipeline.debug "Rootpath: #{rootpath}"
20
+ Dir.chdir("#{rootpath}") do
21
+ @result= runsystem(true, "grep", "-R", "secret")
22
+ end
23
+ end
24
+
25
+ def analyze
26
+ begin
27
+ list = @result.split(/\n/)
28
+ list.each do |match|
29
+ report "Match", match, @name, :low, "fingerprint"
30
+ end
31
+ rescue Exception => e
32
+ Pipeline.warn e.message
33
+ Pipeline.notify "Error grepping ... "
34
+ end
35
+ end
36
+
37
+ def supported?
38
+ supported=runsystem(true, "grep", "-h")
39
+ if supported =~ /usage/
40
+ Pipeline.notify "Install grep."
41
+ return false
42
+ else
43
+ return true
44
+ end
45
+ end
46
+
47
+ end
@@ -0,0 +1,84 @@
1
+ require 'pipeline/tasks/base_task'
2
+ require 'pipeline/util'
3
+ require 'json'
4
+ require 'curb'
5
+
6
+ class Pipeline::Zap < Pipeline::BaseTask
7
+
8
+ Pipeline::Tasks.add self
9
+ include Pipeline::Util
10
+
11
+ def initialize(trigger,tracker)
12
+ super(trigger,tracker)
13
+ @name = "ZAP"
14
+ @description = "App Scanning"
15
+ @stage = :live
16
+ @labels << "live"
17
+ end
18
+
19
+ def run
20
+ rootpath = @trigger.path
21
+ base = "#{@tracker.options[:zap_host]}:#{@tracker.options[:zap_port]}"
22
+ Pipeline.debug "Running ZAP on: #{rootpath} from #{base}"
23
+
24
+ # TODO: Add API Key
25
+ # TODO: Find out if we need to worry about "contexts" stepping on each other.
26
+
27
+ # Spider
28
+ Curl.get("#{base}/JSON/spider/action/scan/?#{rootpath}")
29
+ poll_until_100("#{base}/JSON/spider/view/status")
30
+
31
+ # Active Scan
32
+ Curl.get("#{base}/JSON/ascan/action/scan/?recurse=true&inScopeOnly=true&url=#{rootpath}")
33
+ poll_until_100("#{base}/JSON/ascan/view/status/")
34
+
35
+ # Result
36
+ @result = Curl.get("#{base}/JSON/core/view/alerts/").body_str
37
+ end
38
+
39
+ def poll_until_100(url)
40
+ count = 0
41
+ loop do
42
+ sleep 5
43
+ status = JSON.parse(Curl.get(url).body_str)
44
+ count = count + 1
45
+ Pipeline.notify "Count ... #{count}"
46
+ break if status["status"] == "100" or count > 100
47
+ end
48
+ end
49
+
50
+ def analyze
51
+ begin
52
+ json = JSON.parse @result
53
+ alerts = json["alerts"]
54
+ count = 0
55
+ alerts.each do |alert|
56
+ count = count + 1
57
+ description = alert["description"]
58
+ detail = "Url: #{alert["url"]} Param: #{alert["param"]} \nReference: #{alert["reference"]}\n"+
59
+ "Solution: #{alert["solution"]}\nCWE: #{alert["cweid"]}\tWASCID: #{alert["wascid"]}"
60
+ source = @name + alert["url"]
61
+ sev = severity alert["risk"]
62
+ fingerprint = @name + alert["url"] + alert["alert"] + alert["param"]
63
+ report description, detail, source, sev, fingerprint
64
+ end
65
+ Pipeline.debug "ZAP Identified #{count} issues."
66
+ rescue Exception => e
67
+ Pipeline.warn e.message
68
+ Pipeline.notify "Problem running ZAP."
69
+ end
70
+ end
71
+
72
+ def supported?
73
+ base = "#{@tracker.options[:zap_host]}:#{@tracker.options[:zap_port]}"
74
+ supported=JSON.parse(Curl.get("#{base}/JSON/core/view/version/").body_str)
75
+ if supported["version"] == "2.4.3"
76
+ return true
77
+ else
78
+ Pipeline.notify "Install ZAP from owasp.org and ensure that the configuration to connect is correct."
79
+ return false
80
+ end
81
+ end
82
+
83
+ end
84
+