owasp-pipeline 0.8.3

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