Automate ZAP Security Tests With Selenium Webdriver
Want to automate your ZAP security tests? Look no further.
Join the DZone community and get the full member experience.
Join For FreeOWASP ZAP (Zed Attack Proxy) is an open-source and easy-to-use penetration testing tool for finding security vulnerabilities in the web applications and APIs. As a cross-platform tool with just a basic Java installation pre-requisite, it provides vulnerability scanning for beginners and penetration testing for professionals. It can be downloaded and installed as a standalone application or Docker image.
Additionally, the OWASP community has exposed ZAP APIs, which allows ZAPs to integrate with other tools/frameworks.
For this post, we will look closer at using Selenium Webdriver for security testing. Selenium Webdriver is a web-automation framework that allows us to write tests and automate web applications. This is most famous for creating robust, regression test suites.
Vulnerability Scanning Using OWASP ZAP
Security engineers must install OWASP ZAP and execute the below steps manually to perform vulnerability scanning of desired web applications:
- Configure ZAP proxy in a browser (Chrome, Firefox, or others)
- Use the same browser to open the web application and traverse through all the pages
- This would allow ZAP to intercept the request-response traffic
- Once interception is complete, execute ZAP spider to traverse the hidden paths/inaccessible pages/missed out pages by the user in the application
- This would automatically run a Passive Scan in the background to detect simple vulnerabilities
- Execute Active Scan on the intercepted request-response traffic and find out deep website vulnerabilities like SQL injection and XSS
- The vulnerabilities are raised as Alerts (High, Medium, Low)
Use Case
A project team is using OWASP ZAP to find security vulnerabilities in the application. The whole process of performing a vulnerability scan on the application is manual, time-consuming, and repetitive. But, it is extremely important to perform vulnerability scanning each time the application goes through any change (regression).
The project team requires an open-source solution to reduce manual effort and save time in performing security regression tests.
Integrating security tests with a web-automation framework can solve the problem, and security tests can run automatically with the functional tests. This would eliminate the manual effort for running a vulnerability scan each time.
Let's look at how to implement this in your own web application.
Automating ZAP Security Tests With Selenium Webdriver
Selenium Webdriver and Java commands will interact with ZAP APIs to automate security tests in ZAP.
Installation and Pre-Requisites:
- Eclipse IDE
- Download the latest Chrome driver for browser automation from https://sites.google.com/a/chromium.org/chromedriver/downloads
- Download and Install OWASP ZAP from https://github.com/zaproxy/zaproxy/wiki/Downloads
- ZAP API jar files from https://github.com/continuumsecurity/zap-webdriver/tree/master/libs
- harlib-1.1.1.jar
- proxy-2.4.2-SNAPSHOT.jar
- zap-api-2.4-v6.jar
- The test application BodgeIt Store has been configured and deployed in the local machine. Download BodgeIt Store application from https://code.google.com/archive/p/bodgeit/
Please note: It is not legal to perform penetration testing on publicly hosted applications. Please do not perform security scans on applications without appropriate permissions.
For educational purposes, use sample test applications, deploy them in local environments, and perform security scans.
Steps to Automate Security Test
- In Eclipse, create a new Maven project with the name: ZAPSeleniumIntegration
- In the project, create packages and classes as given below:
Refer to the below table for a description of classes:
Class Name | Description |
WebSiteNavigation.java |
Selenium Webdriver commands to automate functionalities in the web application like User registration, User Log in, and many more |
BrowserDriverFactory.java |
Code to create a browser driver object, configured with ZAP proxy settings |
ZapSecurityTest.java |
Code to interact with ZAP APIs and perform operations like configuring ZAP settings, activating security policies, passive scan, spider, active scan, and filter alerts |
3. Download the ZAP API jar files (harlib-1.1.1.jar, proxy-2.4.2-SNAPSHOT.jar, zap-api-2.4-v6.jar). Create a folder libs in the project directory and place jar files in the folder
4. Implement the following pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zapSeleniumTest</groupId>
<artifactId>ZapSeleniumIntegration</artifactId>
<version>0.0.1-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>3.8.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hamcrest/hamcrest-all -->
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.continuumsecurity</groupId>
<artifactId>zap-java-api</artifactId>
<version>2.4.2</version>
<scope>system</scope>
<systemPath>${project.basedir}/libs/proxy-2.4.2-SNAPSHOT.jar</systemPath>
</dependency>
<dependency>
<groupId>org.owasp</groupId>
<artifactId>zaproxy-client-api</artifactId>
<version>2.4-6</version>
<scope>system</scope>
<systemPath>${project.basedir}/libs/zap-api-2.4-v6.jar</systemPath>
</dependency>
<dependency>
<groupId>edu.umass.cs.benchlab</groupId>
<artifactId>harlib</artifactId>
<version>1.1.1</version>
<scope>system</scope>
<systemPath>${project.basedir}/libs/harlib-1.1.1.jar</systemPath>
</dependency>
</dependencies>
</project>
The above code of the pom.xml has configured plugins and dependencies that are required by Selenium and the ZAP integration project.
5. Here is the log4j.properties
in src/test/resources:
log4j.rootLogger=INFO,console
#console appender
log4j.appender.console=org.apache.log4j.ConsoleAppender
#define pattern layout for console appender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d [%t] %-5p %c %x - %m%n
The above code of the log4j.properties has been configured to capture log messages in the Eclipse console. It can also be modified to capture log messages in external files.
6. Here is the code for BrowserDriverFactory.java
:
package com.ZAP_Selenium_BrowserDriver;
import org.openqa.selenium.Proxy;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
public class BrowserDriverFactory {
// Make reference variable for WebDriver
static WebDriver driver;
public static WebDriver createChromeDriver(Proxy proxy, String path) {
// Set proxy in the chrome browser
DesiredCapabilities capabilities = DesiredCapabilities.chrome();
capabilities.setCapability("proxy", proxy);
// Set system property for chrome driver with the path
System.setProperty("webdriver.chrome.driver", path);
capabilities.setCapability(CapabilityType.ACCEPT_SSL_CERTS, true);
ChromeOptions options = new ChromeOptions();
options.merge(capabilities);
return new ChromeDriver(options);
}
}
The above code represents the creation and configuration of Chrome driver with proxy, driver path, and SSL certificates access. Note: Similar methods can be created to configure other supported browsers like IE, Firefox, Safari, Edge, and others.
7. Here is the example code for WebSiteNavigation.java
:
package com.ZAP_Selenium;
import java.util.concurrent.TimeUnit;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
public class WebSiteNavigation {
WebDriver driver;
/*
* Necessary information is provided about AUT (Test application - BodgeIt Store)
* URL, Logout_URL, Username, Password to be used for registration
*/
final static String BASE_URL = "http://localhost:8081/bodgeit/";
final static String LOGOUT_URL = "http://localhost:8081/bodgeit/logout.jsp";
final static String USERNAME = "test101@demo.com";
final static String PASSWORD = "demotest";
/*
* Apply synchronization/wait techniques
*/
public WebSiteNavigation(WebDriver driver) {
this.driver = driver;
this.driver.manage().timeouts().pageLoadTimeout(5, TimeUnit.SECONDS);
this.driver.manage().timeouts().implicitlyWait(5,TimeUnit.SECONDS);
}
/*
* Registration of a new user
* - Automate the steps to register a new user in the application
* - Selenium Webdriver commands are used to identify the elements
*/
public void registerNewUser() {
driver.get(BASE_URL+"register.jsp");
driver.findElement(By.id("username")).clear();
driver.findElement(By.id("username")).sendKeys(USERNAME);
driver.findElement(By.id("password1")).clear();
driver.findElement(By.id("password1")).sendKeys(PASSWORD);
driver.findElement(By.id("password2")).clear();
driver.findElement(By.id("password2")).sendKeys(PASSWORD);
driver.findElement(By.id("submit")).click();
}
/*
* User navigates before Login
* - Automate the steps to navigate to pages without performing Login
* - Selenium Webdriver commands are used to identify the elements
*/
public void navigateBeforeLogin() {
driver.get(BASE_URL);
driver.findElement(By.linkText("Home")).click();
driver.findElement(By.linkText("Doodahs")).click();
driver.findElement(By.linkText("About Us")).click();
driver.findElement(By.linkText("Your Basket")).click();
driver.findElement(By.linkText("Search")).click();
driver.findElement(By.name("q")).clear();
driver.findElement(By.name("q")).sendKeys("test");
driver.findElement(By.cssSelector("input[type=\"submit\"]")).click();
verifyPresenceOfText("Results Found");
}
/*
* User performs Login Operation
* - Automate the steps to perform login operation in the application
* - Selenium Webdriver commands are used to identify the elements
*/
public void loginAsUser() {
driver.get(BASE_URL+"login.jsp");
driver.findElement(By.id("username")).clear();
driver.findElement(By.id("username")).sendKeys(USERNAME);
driver.findElement(By.id("password")).clear();
driver.findElement(By.id("password")).sendKeys(PASSWORD);
driver.findElement(By.id("submit")).click();
verifyPresenceOfText("successfully");
}
/*
* Verification Point
* - Verify the page title must contain expected text
*/
public void verifyPresenceOfText(String text) {
String pageSource = this.driver.getPageSource();
if (!pageSource.contains(text))
throw new RuntimeException("Expected text: ["+text+"] was not found.");
}
/*
* User navigates after Login
* - Automate the steps to navigate to pages after performing Login
* - Selenium Webdriver commands are used to identify the elements
*/
public void navigateAfterLogin() {
driver.findElement(By.linkText("Doodahs")).click();
driver.findElement(By.linkText("Zip a dee doo dah")).click();
driver.findElement(By.id("submit")).click();
}
}
The above code represents methods used to automate business functionalities in the test application, like New User Registration, Navigate before Login, Login as User, Verify the presence of Text, and Navigate after logout. Note: Similar methods can be created to automate other business functionalities in the application.
8. Here is the example code for ZapSecurityTest.java
:
package com.ZAP_Selenium;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsEqual.equalTo;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import org.junit.*;
import org.openqa.selenium.Proxy;
import org.openqa.selenium.WebDriver;
import org.zaproxy.clientapi.core.Alert;
import com.ZAP_Selenium_BrowserDriver.BrowserDriverFactory;
import com.ZAP_Selenium.WebSiteNavigation;
import net.continuumsecurity.proxy.ScanningProxy;
import net.continuumsecurity.proxy.Spider;
import net.continuumsecurity.proxy.ZAProxyScanner;
public class ZapSecurityTest {
/*
* Provide details about ZAP Proxy
*/
static Logger log = Logger.getLogger(ZapSecurityTest.class.getName());
private static final String ZAP_PROXYHOST = "localhost";
private static final int ZAP_PROXYPORT = 8098;
private static final String ZAP_APIKEY = null;
// Provide Chrome driver path
private static final String BROWSER_DRIVER_PATH = "C:\\chromedriver.exe";
private final static String MEDIUM = "MEDIUM";
private final static String HIGH = "HIGH";
private ScanningProxy zapScanner;
private Spider zapSpider;
private WebDriver driver;
private WebSiteNavigation siteNavigation;
// Provide scan policy names
private final static String[] policyNames =
{"directory-browsing","cross-site-scripting",
"sql-injection","path-traversal","remote-file-inclusion",
"server-side-include","script-active-scan-rules",
"server-side-code-injection","external-redirect",
"crlf-injection"};
int currentScanID;
// Create ZAP proxy by specifying proxy host and proxy port
private static Proxy createZapProxyConfiguration() {
Proxy proxy = new Proxy();
proxy.setHttpProxy(ZAP_PROXYHOST + ":" + ZAP_PROXYPORT);
proxy.setSslProxy(ZAP_PROXYHOST + ":" + ZAP_PROXYPORT);
return proxy;
}
/*
* Method to configure ZAP scanner, API client and perform User Registration
*/
@Before
public void setUp()
{
// Configure ZAP Scanner
zapScanner = new ZAProxyScanner(ZAP_PROXYHOST, ZAP_PROXYPORT, ZAP_APIKEY);
// Start new session
zapScanner.clear();
log.info("Started a new session: Scanner");
// Create ZAP API client
zapSpider=(Spider) zapScanner;
log.info("Created client to ZAP API");
// Create driver object
driver = BrowserDriverFactory.createChromeDriver(createZapProxyConfiguration(), BROWSER_DRIVER_PATH);
siteNavigation = new WebSiteNavigation(driver);
// First test the "Register a new user"
siteNavigation.registerNewUser();
}
/*
* Method to close the driver connection
*/
@After
public void tearDown()
{
driver.quit();
}
// ZAP Operations -- filterAlerts, setAlert_AttackStrength, activateZapPolicy, spiderwithZAP, scanWithZAP
// ---------------------------------------------------------------------------------------------------------
/*
* Method to filter the generated alerts based on Risk and Confidence
*/
private List<Alert> filterAlerts(List<Alert> alerts)
{
List<Alert> filteredAlerts = new ArrayList<Alert>();
for (Alert alert : alerts)
{
// Filtering based on Risk: High and Confidence: Not Low
if (alert.getRisk().equals(Alert.Risk.High) && alert.getConfidence() != Alert.Confidence.Low)
filteredAlerts.add(alert);
}
return filteredAlerts;
}
/*
* Method to specify the strength for the ZAP Scanner as High, Medium, or Low
*/
public void setAlert_AttackStrength()
{
for (String ZapPolicyName : policyNames)
{
String ids = activateZapPolicy(ZapPolicyName);
for (String id : ids.split(",")) {
zapScanner.setScannerAlertThreshold(id,MEDIUM);
zapScanner.setScannerAttackStrength(id,HIGH);
}
}
}
/*
* Method to configure the ZAP Scanner for specified security policies and enable the scanner
*/
private String activateZapPolicy(String policyName)
{
String scannerIds = null;
// Compare the security policies and specify scannerIds (these scannerIds are standard)
switch (policyName.toLowerCase()) {
case "directory-browsing":
scannerIds = "0";
break;
case "cross-site-scripting":
scannerIds = "40012,40014,40016,40017";
break;
case "sql-injection":
scannerIds = "40018";
break;
case "path-traversal":
scannerIds = "6";
break;
case "remote-file-inclusion":
scannerIds = "7";
break;
case "server-side-include":
scannerIds = "40009";
break;
case "script-active-scan-rules":
scannerIds = "50000";
break;
case "server-side-code-injection":
scannerIds = "90019";
break;
case "remote-os-command-injection":
scannerIds = "90020";
break;
case "external-redirect":
scannerIds = "20019";
break;
case "crlf-injection":
scannerIds = "40003";
break;
case "source-code-disclosure":
scannerIds = "42,10045,20017";
break;
case "shell-shock":
scannerIds = "10048";
break;
case "remote-code-execution":
scannerIds = "20018";
break;
case "ldap-injection":
scannerIds = "40015";
break;
case "xpath-injection":
scannerIds = "90021";
break;
case "xml-external-entity":
scannerIds = "90023";
break;
case "padding-oracle":
scannerIds = "90024";
break;
case "el-injection":
scannerIds = "90025";
break;
case "insecure-http-methods":
scannerIds = "90028";
break;
case "parameter-pollution":
scannerIds = "20014";
break;
default : throw new RuntimeException("No policy found for: "+policyName);
}
zapScanner.setEnableScanners(scannerIds, true);
return scannerIds;
}
/*
* Method to configure spider settings, execute ZAP spider, log the progress and found URLs
*/
public void spiderWithZap()
{
log.info("Spidering started");
// Configure spider settings
zapSpider.excludeFromSpider(WebSiteNavigation.LOGOUT_URL);
zapSpider.setThreadCount(5);
zapSpider.setMaxDepth(5);
zapSpider.setPostForms(false);
// Execute the ZAP spider
zapSpider.spider(WebSiteNavigation.BASE_URL);
int currentSpiderID = zapSpider.getLastSpiderScanId();
int progressPercent = 0;
while (progressPercent < 100) {
progressPercent = zapSpider.getSpiderProgress(currentSpiderID);
log.info("Spider is " + progressPercent + "% complete.");
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
// Log the found URLs after spider
for (String url : zapSpider.getSpiderResults(currentSpiderID)) {
log.info("Found URL after spider: "+url);
}
log.info("Spidering ended");
}
/*
* Method to execute scan and log the progress
*/
public void scanWithZap()
{
log.info("Scanning started");
// Execute the ZAP scanner
zapScanner.scan(WebSiteNavigation.BASE_URL);
int currentScanId = zapScanner.getLastScannerScanId();
int progressPercent = 0;
while (progressPercent < 100) {
progressPercent = zapScanner.getScanProgress(currentScanId);
log.info("Scan is " + progressPercent + "% complete.");
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
log.info("Scanning ended");
}
// Test methods -- testVulnerabilitiesBeforeLogin, testVulnerabilitiesAfterLogin
// ---------------------------------------------------------------------------------------------------------
/*
* Test method containing test steps like navigateBeforeLogin, spiderWithZAP,
* setAlert_AttackStrength, scanWithZAP, filterAlerts, and
* log the found alerts and assert the count of alerts
*/
@Test
public void testVulnerabilitiesBeforeLogin()
{
siteNavigation.navigateBeforeLogin();
// Using ZAP Spider
log.info("Started spidering");
spiderWithZap();
log.info("Ended spidering");
// Setting alert and attack
setAlert_AttackStrength();
zapScanner.setEnablePassiveScan(true);
// Using ZAP Scanner
log.info("Started scanning");
scanWithZap();
log.info("Ended scanning");
List<Alert> generatedAlerts = filterAlerts(zapScanner.getAlerts());
for (Alert alert : generatedAlerts)
{
log.info("Alert: "+alert.getAlert()+" at URL: "+alert.getUrl()+" Parameter: "+alert.getParam()+" CWE ID: "+alert.getCweId());
}
assertThat(generatedAlerts.size(), equalTo(0));
}
/*
* Test method containing test steps like loginAsUser, navigateAfterLogin,
* spiderWithZAP, setAlert_AttackStrength, scanWithZAP, filterAlerts, and
* log the found alerts and assert the count of alerts
*/
@Test
public void testVulnerabilitiesAfterLogin()
{
siteNavigation.loginAsUser();
siteNavigation.navigateAfterLogin();
// Using ZAP Spider
log.info("Started spidering");
spiderWithZap();
log.info("Ended spidering");
// Setting alert and attack
setAlert_AttackStrength();
zapScanner.setEnablePassiveScan(true);
// Using ZAP Scanner
log.info("Started scanning");
scanWithZap();
log.info("Ended scanning");
List<Alert> generatedAlerts = filterAlerts(zapScanner.getAlerts());
for (Alert alert : generatedAlerts)
{
log.info("Alert: "+alert.getAlert()+" at URL: "+alert.getUrl()+" Parameter: "+alert.getParam()+" CWE ID: "+alert.getCweId());
}
assertThat(generatedAlerts.size(), equalTo(0));
}
}
The above code represents utility methods and test methods used to perform security tests with ZAP APIs.
Refer to the below table for a summary of the methods:
Method Name | Description |
|
To configure ZAP scanner, API client, and perform User Registration |
|
To close the driver connection |
|
To filter the generated alerts based on Risk and Confidence |
|
To specify the strength for the ZAP Scanner as High, Medium, or Low |
|
To configure the ZAP Scanner for specified security policies and enable the scanner |
|
To configure spider settings, execute ZAP spider, log the progress and found URLs |
|
To execute scan and log the progress |
|
Test method containing test steps like |
|
Test method containing test steps like login as a user, |
9. Open ZAP stand-alone interface and verify the settings mentioned below:
- Proxy details in the
ZapSecurityTest.java
class must match proxy details in ZAP stand-alone interface with the Options as highlighted:
- The API key must be disabled:
Additional notes:
- Before executing the project, open the ZAP stand-alone interface in the background
- Execute the project using the Maven commands:
clean test
- The log results will appear in the console. These results will specify the presence of security vulnerabilities in the web application
Results: Console
Spidering Progress and Found URLs:
Scan Progress:
Test Results:
Found Alerts:
Results: ZAP Interface
Switch to ZAP stand-alone interface and go to Alerts Tab. All the security vulnerabilities are listed in the ZAP interface.
Hope you enjoyed this tutorial! Let us know your thoughts in the comments below.
Opinions expressed by DZone contributors are their own.
Comments