Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Automate ZAP Security Tests With Selenium Webdriver

DZone 's Guide to

Automate ZAP Security Tests With Selenium Webdriver

Want to automate your ZAP security tests? Look no further.

· Security Zone ·
Free Resource

OWASP 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:

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

  1. In Eclipse, create a new Maven project with the name: ZAPSeleniumIntegration
  2. In the project, create packages and classes as given below:

Image titleRefer 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

Image title

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

 @Before setup() 

To configure ZAP scanner, API client, and perform User Registration

 @After tearDown() 

To close the driver connection

 filterAlerts() 

To filter the generated alerts based on Risk and Confidence

 setAlert_AttackStrength() 

To specify the strength for the ZAP Scanner as High, Medium, or Low

 activateZapPolicy() 

To configure the ZAP Scanner for specified security policies and enable the scanner

 spiderWithZAP() 

To configure spider settings, execute ZAP spider, log the progress and found URLs

 scanWithZAP() 

To execute scan and log the progress

 testVulnerabilitiesBeforeLogin() 

Test method containing test steps like  navigateBeforeLogin, spiderWithZAP,  setAlert_AttackStrength,  scanWithZAP, filter alerts, and log the found alerts and assert the count of alerts

 testVulnerabilitiesAfterLogin() 

Test method containing test steps like login as a user, navigateAfterLoginspiderWithZAP, setAlert_AttackStrengthscanWithZAP, filter alerts, and log the found alerts and assert the count of alerts


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:

Image title

  • The API key must be disabled:

Image title

Additional notes:

  1. Before executing the project, open the ZAP stand-alone interface in the background
  2. Execute the project using the Maven commands:  clean test
  3. 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:

Image title

Scan Progress:

Image title

Test Results:

Image title

Found Alerts:

Image title

Results: ZAP Interface

Switch to ZAP stand-alone interface and go to Alerts Tab. All the security vulnerabilities are listed in the ZAP interface.

Image title

Hope you enjoyed this tutorial! Let us know your thoughts in the comments below.

Topics:
selenium webdriver ,zap ,security testing ,security ,automation ,penetration testing ,owasp ,owasp zap ,maven ,tutorial

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}