OWASP ZAP is widely used security testing tool and it’s open sourced. Let’s see how we can integrate it with our automated functionality testing in CI.
Prerequisite:
RVM, Ruby and rubygems are installed.
Get Started:
Download ZAP from OWASP_Zed_Attack_Proxy_Project and install it.
Install Selenium-WebDriver, IO, Rest-Client and RSpec gems:
1) Install Selenium-WebDriver
gem install selenium-webdriver
2) Install IO
gem install io
3) Install Rest-Client
gem install rest-client
4) Install RSpec
gem install rspec
Write and run a basic Selenium-WebDriver script using Ruby. The script is the same as in my previous Gitbook BDD with PageObject.
The script will be like following:
1234567891011121314require 'selenium-webdriver'driver = Selenium::WebDriver.for :firefoxdriver.get "http://www.google.com"element = driver.find_element :name => "q"element.send_keys "Cheese!"element.submitp "Page title is #{driver.title}"wait = Selenium::WebDriver::Wait.new(:timeout => 10)wait.until { driver.title.downcase.start_with? "cheese!" }p "Page title is #{driver.title}"driver.quitYou can reference the script here.
Using
ruby simple_script.rb
to run and check the result.Add steps to start ZAP and proxy the testing script using ZAP.
The script will become as following:
1234567891011121314151617181920212223242526require 'selenium-webdriver'+ require 'io/console'+ system("pkill java") #To close any existing ZAP instance.+ system("pkill firefox") #To close any existing Firefox instance.+ IO.popen("/Applications/ZAP\\ 2.6.0.app/Contents/Java/zap.sh -daemon -config api.disablekey=true") #The path here should be the zap.sh path under ZAP package/folder on your machine; with the option -config api.disablekey=true, ZAP will not check the apikey, which is enable by default after ZAP 2.6.0+ p "OWASP ZAP launch completed"+ sleep 5 #To let ZAP start completely+ profile = Selenium::WebDriver::Firefox::Profile.new+ proxy = Selenium::WebDriver::Proxy.new(http: "localhost:8080") #Normally ZAP will listening at port 8080, if not, please change it to the actual port ZAP is listening+ profile.proxy = proxy+ options = Selenium::WebDriver::Firefox::Options.new(profile: profile)+ driver = Selenium::WebDriver.for :firefox, options: options- driver = Selenium::WebDriver.for :firefoxdriver.get "http://www.google.com"element = driver.find_element :name => "q"element.send_keys "Cheese!"element.submitp "Page title is #{driver.title}"wait = Selenium::WebDriver::Wait.new(:timeout => 10)wait.until { driver.title.downcase.start_with? "cheese!" }p "Page title is #{driver.title}"driver.quitYou can reference the script here.
Using
ruby add_zap_start.rb
to run and check the result.Read the test results from ZAP.
The script will become as following:
12345678910111213141516171819202122232425262728293031require 'selenium-webdriver'require 'io/console'+ require 'rest-client'system("pkill java") #To close any existing ZAP instance.system("pkill firefox") #To close any existing Firefox instance.IO.popen("/Applications/ZAP\\ 2.6.0.app/Contents/Java/zap.sh -daemon -config api.disablekey=true") #The path here should be the zap.sh path under ZAP package/folder on your machine; with the option -config api.disablekey=true, ZAP will not check the apikey, which is enable by default after ZAP 2.6.0p "OWASP ZAP launch completed"sleep 5 #To let ZAP start completelyprofile = Selenium::WebDriver::Firefox::Profile.newproxy = Selenium::WebDriver::Proxy.new(http: "localhost:8080") #Normally ZAP will listening at port 8080, if not, please change it to the actual port ZAP is listeningprofile.proxy = proxyoptions = Selenium::WebDriver::Firefox::Options.new(profile: profile)driver = Selenium::WebDriver.for :firefox, options: optionsdriver.get "http://www.google.com"element = driver.find_element :name => "q"element.send_keys "Cheese!"element.submitp "Page title is #{driver.title}"wait = Selenium::WebDriver::Wait.new(:timeout => 10)wait.until { driver.title.downcase.start_with? "cheese!" }p "Page title is #{driver.title}"+ JSON.parse RestClient.get "http://localhost:8080/json/core/view/alerts" #To trigger ZAP to raise alerts if any+ sleep 5 #Give ZAP some time to process+ response = JSON.parse RestClient.get "http://localhost:8080/json/core/view/alerts", params: { zapapiformat: 'JSON', baseurl: "http://clients1.google.com", start: 1 } #Get the alerts ZAP found, please note the baseurl will exact match from the beginningdriver.quit+ RestClient.get "http://localhost:8080/JSON/core/action/shutdown" #Close ZAP instanceYou can reference the script here.
Using
ruby read_zap_result.rb
to run and check the result.Set up assertions to check the Low Risks.
The script will become as following:
12345678910111213141516171819202122232425262728293031323334353637require 'selenium-webdriver'require 'io/console'require 'rest-client'+ require 'rspec/expectations'+ include RSpec::Matcherssystem("pkill java") #To close any existing ZAP instance.system("pkill firefox") #To close any existing Firefox instance.IO.popen("/Applications/ZAP\\ 2.6.0.app/Contents/Java/zap.sh -daemon -config api.disablekey=true") #The path here should be the zap.sh path under ZAP package/folder on your machine; with the option -config api.disablekey=true, ZAP will not check the apikey, which is enable by default after ZAP 2.6.0p "OWASP ZAP launch completed"sleep 5 #To let ZAP start completelyprofile = Selenium::WebDriver::Firefox::Profile.newproxy = Selenium::WebDriver::Proxy.new(http: "localhost:8080") #Normally ZAP will listening at port 8080, if not, please change it to the actual port ZAP is listeningprofile.proxy = proxyoptions = Selenium::WebDriver::Firefox::Options.new(profile: profile)driver = Selenium::WebDriver.for :firefox, options: optionsdriver.get "http://www.google.com"element = driver.find_element :name => "q"element.send_keys "Cheese!"element.submitp "Page title is #{driver.title}"wait = Selenium::WebDriver::Wait.new(:timeout => 10)wait.until { driver.title.downcase.start_with? "cheese!" }p "Page title is #{driver.title}"JSON.parse RestClient.get "http://localhost:8080/json/core/view/alerts" #To trigger ZAP to raise alerts if anysleep 5 #Give ZAP some time to processresponse = JSON.parse RestClient.get "http://localhost:8080/json/core/view/alerts", params: { zapapiformat: 'JSON', baseurl: "http://clients1.google.com", start: 1 } #Get the alerts ZAP found+ response['alerts'].each {|x| p "#{x['alert']} risk level: #{x['risk']}"} #Extract the risks found+ events = response['alerts']+ low_count = events.select{|x| x['risk'] == 'Low'}.size #Count the Low Risks+ expect(low_count).to equal(1) #Expecxt only one Low Riskdriver.quitRestClient.get "http://localhost:8080/JSON/core/action/shutdown" #Close ZAP instanceYou can reference the script here.
Using
ruby add_assertions_to_check_zap_result.rb
to run and check the result.Now we can trigger this script in any CI using command above.