From eec2d827dfadcf4c8fa65d459b66e9a9a62e665d Mon Sep 17 00:00:00 2001 From: Matt Konda Date: Sat, 2 Jan 2016 22:00:13 -0600 Subject: [PATCH] Working ZAP via API. Jenkins example script. --- docker/pipeline/pipeline-0.8.docker | 0 {hooks => integrations/hooks}/pre-commit | 0 integrations/jenkins/pipeline-active.sh | 9 + integrations/jenkins/pipeline-code.sh | 9 + lib/pipeline.rb | 2 + lib/pipeline/options.rb | 22 +-- lib/pipeline/tasks.rb | 17 +- lib/pipeline/tasks/zap.rb | 224 ++++++----------------- 8 files changed, 89 insertions(+), 194 deletions(-) create mode 100644 docker/pipeline/pipeline-0.8.docker rename {hooks => integrations/hooks}/pre-commit (100%) create mode 100644 integrations/jenkins/pipeline-active.sh create mode 100644 integrations/jenkins/pipeline-code.sh diff --git a/docker/pipeline/pipeline-0.8.docker b/docker/pipeline/pipeline-0.8.docker new file mode 100644 index 0000000..e69de29 diff --git a/hooks/pre-commit b/integrations/hooks/pre-commit similarity index 100% rename from hooks/pre-commit rename to integrations/hooks/pre-commit diff --git a/integrations/jenkins/pipeline-active.sh b/integrations/jenkins/pipeline-active.sh new file mode 100644 index 0000000..bcfab25 --- /dev/null +++ b/integrations/jenkins/pipeline-active.sh @@ -0,0 +1,9 @@ +# Use existing managed scripts to add this to your Jenkins build +# Obviously depends on having docker-machine and docker set up. + +echo "Starting Pipeline Tool" +echo "Script executed from: ${PWD}" + +eval $(docker-machine env patched) +GUID="$RANDOM" +docker run -v ${PWD}:/tmp/$GUID/ jemurai/pipeline:0.8 -z -t zap -d /tmp/$GUID/ diff --git a/integrations/jenkins/pipeline-code.sh b/integrations/jenkins/pipeline-code.sh new file mode 100644 index 0000000..68ea51b --- /dev/null +++ b/integrations/jenkins/pipeline-code.sh @@ -0,0 +1,9 @@ +# Use existing managed scripts to add this to your Jenkins build +# Obviously depends on having docker-machine and docker set up. + +echo "Starting Pipeline Tool" +echo "Script executed from: ${PWD}" + +eval $(docker-machine env patched) +GUID="$RANDOM" +docker run -v ${PWD}:/tmp/$GUID/ jemurai/pipeline:0.8 -z -L code -d /tmp/$GUID/ diff --git a/lib/pipeline.rb b/lib/pipeline.rb index e021847..f7a81d4 100644 --- a/lib/pipeline.rb +++ b/lib/pipeline.rb @@ -103,6 +103,8 @@ def self.default_options :exit_on_warn => true, :output_format => :text, :working_dir => "~/line/tmp/", + :zap_host => "http://localhost", + :zap_port => "9999", :labels => Set.new() << "filesystem" << "code" # Defaults to run. } end diff --git a/lib/pipeline/options.rb b/lib/pipeline/options.rb index 2500e88..50922e5 100644 --- a/lib/pipeline/options.rb +++ b/lib/pipeline/options.rb @@ -149,26 +149,18 @@ def get_options args, destructive = false end opts.separator "" - opts.separator "Checkmarx options:" + opts.separator "ZAP options:" - opts.on "--checkmarx-user USER", "Specify the Checkmarx user to use when connecting to the API" do |user| - options[:checkmarx_user] = user + opts.on "--zap-api-token token", "Specify the ZAP API token to use when connecting to the API" do |token| + options[:zap_api_token] = token end - opts.on "--checkmarx-password PASSWORD", "Specify password for the Checkmarx API user" do |password| - options[:checkmarx_password] = password + opts.on "--zap-host HOST", "Specify the host ZAP is running on." do |host| + options[:zap_host] = host end - opts.on "--checkmarx-server server", "Specify the API server to use for Checkmarx scans" do |server| - options[:checkmarx_server] = server - end - - opts.on "--checkmarx-log logfile", "Specify the log file to use for Checkmarx scans" do |logfile| - options[:checkmarx_log] = logfile - end - - opts.on "--checkmarx-project project", "Specify the full path of the Checkmarx project for this scan" do |project| - options[:checkmarx_project] = project + opts.on "--zap-port PORT", "Specify the port ZAP is running on." do |port| + options[:zap_port] = port end opts.separator "" diff --git a/lib/pipeline/tasks.rb b/lib/pipeline/tasks.rb index 056ff44..c673d33 100644 --- a/lib/pipeline/tasks.rb +++ b/lib/pipeline/tasks.rb @@ -73,13 +73,16 @@ def self.run_tasks(target, stage, tracker) task = c.new(trigger, tracker) begin - if (task.supported? && ( task.stage === stage ) && ( task.labels.intersect? tracker.options[:labels] ) ) # Only run tasks with lables. - Pipeline.notify "#{stage} - #{task_name} - #{task.labels}" - task.run - task.analyze - task.findings.each do | finding | - tracker.report finding - end + if task.supported? and task.stage == stage + if task.labels.intersect? tracker.options[:labels] or # Only run tasks with labels + ( run_tasks and run_tasks.include? task_name.downcase ) # or that are explicitly requested. + Pipeline.notify "#{stage} - #{task_name} - #{task.labels}" + task.run + task.analyze + task.findings.each do | finding | + tracker.report finding + end + end end rescue => e Pipeline.notify e.message diff --git a/lib/pipeline/tasks/zap.rb b/lib/pipeline/tasks/zap.rb index 2411f33..9a94dbe 100644 --- a/lib/pipeline/tasks/zap.rb +++ b/lib/pipeline/tasks/zap.rb @@ -1,154 +1,7 @@ require 'pipeline/tasks/base_task' -require 'json' require 'pipeline/util' - -require 'rexml/document' -require 'rexml/streamlistener' -include REXML - -# SAX Like Parser for OWASP ZAP XML. -class Pipeline::ZAPListener - include StreamListener - - def initialize(task) - @task = task - @count = 0 - @pluginid = "" - @alert = "" - @confidence = "" - @riskdesc = "" - @desc = "" - @url = "" - @param = "" - @attack = "" - @otherinfo = "" - @solution = "" - @reference = "" - @wascid = "" - @cwe = "" - @fingerprint = "" - end - - def tag_start(name, attrs) - case name - when "alertitem" - @count = @count + 1 - # Pipeline.debug "Grabbed #{@count} vulns." - @pluginid = "" - @alert = "" - @confidence = "" - @riskdesc = "" - @desc = "" - @url = "" - @param = "" - @attack = "" - @otherinfo = "" - @solution = "" - @reference = "" - @wascid = "" - @cwe = "" - @fingerprint = "" - end - end - - def tag_end(name) - case name - when "pluginid" - @pluginid = @text - when "alert" - @alert = @text - when "confidence" - @confidence = @text - when "riskdesc" - @riskdesc = @text - when "desc" - @desc = @text - when "uri" - @url = @text - when "param" - @param = @text.strip - when "attack" - @attack = @text.strip - when "otherinfo" - @otherinfo = @text.chomp - when "solution" - @solution = @text - when "reference" - @reference = @text - when "wascid" - @wascid = @text - when "cwe" - @cwe = @text - when "alertitem" - detail = get_detail - description = get_description - source = get_source - get_fingerprint - risk = "Risk: #{@riskdesc} / Confidence (of 1-3 Low, Medium, High): #{@confidence}" - - # puts "Vuln: #{@alert} Severity: #{risk}\n\tDescription: #{description}\n\tDetail: #{detail}" - # puts "\tFingerprint: #{@fingerprint}" - @task.report description, detail, source, risk, @fingerprint - end - end - - def get_fingerprint - @fingerprint = "ZAP-#{@pluginid}-#{@url}-#{@alert}" - if @param != "" - @fingerprint << "-#{@param}" - end - if @cwe != "" - @fingerprint << "-#{@cwe}" - end - if @wascid != "" - @fingerprint << "-#{@wascid}" - end - @fingerprint = @fingerprint.strip.gsub("\n", "") - end - - def get_source - source = "ZAP Plugin: #{@pluginid} URL: #{@url}" - source - end - - - def get_detail - detail = "URL: #{@url}\n\t" - if @param != "" - detail << "Param: #{@param}\t" - end - if @attack != "" - detail << "Attack: #{@attack}\n\t" - end - if @otherinfo != "" and @otherinfo.strip != "" - detail << "Background: #{@otherinfo}\n\t" - end - if @reference != "" - detail << "Reference: #{@reference}\n\t" - end - if @solution != "" - detail << "Solution: #{@solution}" - end - detail - end - - def get_description - # Format description. - description = "" - if @cwe != "" - description = "CWE: #{@cwe}\t" - end - if @wascid != "" - description << "WASC ID: #{@wascid}\t" - end - description << "\n\tDesc: #{@desc}" - description - end - - def text(text) - @text = text.chomp - end -end +require 'json' +require 'curb' class Pipeline::Zap < Pipeline::BaseTask @@ -164,25 +17,55 @@ def initialize(trigger,tracker) end def run - Pipeline.notify "#{@name}" rootpath = @trigger.path + host = @tracker.options[:zap_host] + port = @tracker.options[:zap_port] + base = "#{host}:#{port}" + Pipeline.debug "Running ZAP on: #{rootpath} from #{base}" + + # TODO: Add API Key + # TODO: Find out if we need to worry about "contexts" stepping on each other. + + # Spider + Curl.get("#{base}/JSON/spider/action/scan/?#{rootpath}") + poll_until_100("#{base}/JSON/spider/view/status") + + # Active Scan + Curl.get("#{base}/JSON/ascan/action/scan/?recurse=true&inScopeOnly=true&url=#{rootpath}") + poll_until_100("#{base}/JSON/ascan/view/status/") + + # Result + @result = Curl.get("#{base}/JSON/core/view/alerts/").body_str + end - Pipeline.debug "Running ZAP on: #{rootpath}" - #@result = runsystem(true, "rm", "/tmp/zap.xml") - #Pipeline.debug "Remove old ZAP file." - - # See /docker/zap for details on how this is going to work: - @result=runsystem(true, "docker","run","-u","zap","-i","pipeline/zap:v1","zap-cli","--api-key","123","quick-scan","--spider","-l","Medium","-sc","-r","-o","'-config api.key=123'","#{rootpath}") - - + def poll_until_100(url) + count = 0 + loop do + sleep 5 + status = JSON.parse(Curl.get(url).body_str) + count = count + 1 + Pipeline.notify "Count ... #{count}" + break if status["status"] == "100" or count > 100 + end end def analyze - puts @result - begin - #path = @trigger.path + "/tmp/zap.xml" - path = "/tmp/zap.xml" - get_warnings(path) + begin + json = JSON.parse @result + alerts = json["alerts"] + count = 0 + alerts.each do |alert| + # def report description, detail, source, severity, fingerprint + description = alert["description"] + detail = "Url: #{alert["url"]} Param: #{alert["param"]} \nReference: #{alert["reference"]}\n"+ + "Solution: #{alert["solution"]}\nCWE: #{alert["cweid"]}\tWASCID: #{alert["wascid"]}" + source = @name + alert["url"] + sev = severity alert["risk"] + fingerprint = @name + alert["url"] + alert["alert"] + alert["param"] + report description, detail, source, sev, fingerprint + count = count + 1 + end + Pipeline.debug "ZAP Identified #{count} issues." rescue Exception => e Pipeline.warn e.message Pipeline.notify "Problem running ZAP." @@ -190,20 +73,17 @@ def analyze end def supported? - supported=runsystem(true, "java","-Xmx512m","-jar", "/area52/ZAP_2.4.1/zap-2.4.1.jar", "-version") - if supported =~ /2.4.1/ + host = @tracker.options[:zap_host] + port = @tracker.options[:zap_port] + base = "#{host}:#{port}" + supported=JSON.parse(Curl.get("#{base}/JSON/core/view/version/?zapapiformat=JSON").body_str) + if supported["version"] == "2.4.3" return true else - Pipeline.notify "Install ZAP from owasp.org" + Pipeline.notify "Install ZAP from owasp.org and ensure that the configuration to connect is correct." return false end end - def get_warnings(path) - listener = Pipeline::ZAPListener.new(self) - parser = Parsers::StreamParser.new(File.new(path), listener) - parser.parse - end - end