DAST - OWASP ZAP docker

Dynamic Application Security Testing (DAST) attempts to identify security vulnerabilities in applications that are running in a near production-like environment.  There are many types of DAST scans one of such is the opensource OWASP ZAP scan.

This article is a shallow overview of how DAST can be run hooked up to Jenkins and configured to run an OWASP ZAP scan from a Docker container. We will start by creating a Git repository for storing scripts used to run the scans:

<em>Git repository storing the scan scripts used to run OWASP ZAP docker</em>

Jenkins

Assuming you have already set up a working Jenkins pipeline environment, you can hookup Jenkins to the Jenkinsfile shown above by creating a new ‘Multi-branch Pipeline’ and associate the pipeline to the Jenkinsfile.
In summary, the Jenkins file coordinates the running of scan scripts and does the following steps in sequence:

  1. Retrieves script files stored in a Git repository.
  2. Runs ‘validate_input.sh’
  3. Runs ‘runZapScan.sh’
  4. Runs ‘runZapScan.sh’
  5. Publishes scan results to Jenkins job

Jenkinsfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
def checkoutGitSCM(branch,gitUrl) {
checkout([$class: 'GitSCM',
branches: [[name: branch ]],
doGenerateSubmoduleConfigurations: false,
extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: '.']],
submoduleCfg: [],
userRemoteConfigs: [[url: gitUrl]]
])
}
pipeline {
agent {
node { label 'standard_ubuntu18' }
}
options {
timestamps()
disableConcurrentBuilds()
buildDiscarder(logRotator(numToKeepStr: '10'))
timeout(time: 180, unit: 'MINUTES')
ansiColor('xterm')
}
parameters {
string(name: 'ZAP_TARGET_URL', defaultValue:'https:planningtasks.com', description:'')
choice(name: 'ZAP_ALERT_LVL', choices: ['High', 'Medium', 'Low'], description: 'See Zap documentation, default High')
}
stages{
stage('Initialize'){
steps{
script {
currentBuild.displayName = "ZAP scan='${env.BUILD_NUMBER}''"
currentWorkspace=pwd()
cleanWs()
}
}
}
stage('ZAP'){
when { branch 'master' }
steps{
sh("echo ${env.WORKSPACE}; ls -l;")
checkoutGitSCM("master","https://your_git_repo")
sh("bash -c \"chmod +x ${env.WORKSPACE}/*.sh\"")
sh("${env.WORKSPACE}/validate_input.sh")
sh("${env.WORKSPACE}/runZapScan.sh ${params.ZAP_TARGET_URL} ${env.WORKSPACE} ${params.ZAP_ALERT_LVL}")
}
}
stage('Publish'){
when { branch 'master' }
steps{
publishHTML([allowMissing: false,
alwaysLinkToLastBuild: false,
keepAll: false,
reportDir: './reports',
reportFiles: 'report.html',
reportName: 'ZAP scan report',
reportTitles: ''])
}
}
}
post {
always {
sh("${env.WORKSPACE}/runCleanup.sh")
}
}
}

runZapScan.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#!/bin/bash -eux
ZAP_TARGET_URL=$1
ZAP_REPORTS_PATH=$2
ZAP_ALERT_LVL=$3

echo "ZAP_TARGET_URL='$ZAP_REPORTS_PATH'"
echo "ZAP_REPORTS_PATH='$ZAP_REPORTS_PATH'"
echo "ZAP_REPORTS_PATH='$ZAP_ALERT_LVL'"

reportsDirectory="$ZAP_REPORTS_PATH/reports"
mkdir $reportsDirectory

ZAP_CONT_ID=$(docker run --name zap -p 2375:2375 -d owasp/zap2docker-stable zap.sh -daemon \
-port 2375 \
-host 127.0.0.1 \
-config api.disablekey=true \
-config scanner.attackOnStart=true \
-config view.mode=attack \
-config connection.dnsTtlSuccessfulQueries=-1 \
-config api.addrs.addr.name=.* \
-config api.addrs.addr.regex=true)

docker exec $ZAP_CONT_ID zap-cli -v -p 2375 status -t 120
docker exec $ZAP_CONT_ID zap-cli -v -p 2375 open-url $ZAP_TARGET_URL
docker exec $ZAP_CONT_ID zap-cli -v -p 2375 active-scan $ZAP_TARGET_URL
docker exec $ZAP_CONT_ID zap-cli -v -p 2375 spider $ZAP_TARGET_URL
docker exec $ZAP_CONT_ID zap-cli -v -p 2375 active-scan --scanners xss,sqli --recursive $ZAP_TARGET_URL

docker exec $ZAP_CONT_ID zap-cli -p 2375 report -o /home/zap/report.html -f html
docker exec $ZAP_CONT_ID zap-cli -p 2375 report -o /home/zap/report.xml -f xml

docker cp $ZAP_CONT_ID:/home/zap/report.xml $reportsDirectory
docker cp $ZAP_CONT_ID:/home/zap/report.html $reportsDirectory

# Check alerts
ALERT_CNT=$(docker exec $ZAP_CONT_ID zap-cli -p 2375 --verbose alerts --alert-level $ZAP_ALERT_LVL -f json | jq length)

if [[ "${ALERT_CNT}" -gt 0 ]]; then
echo "Vulnerabilities dectected, Lvl='$ZAP_ALERT_LVL' Alert count='${ALERT_CNT}'"
exit 1
fi

validate_input.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
checkInput () {
if [ -z "$1" ]
then
echo "input missing:$2"
exit 1
else
echo "[validate_input] $2=$1"
fi

}
checkInput "${ZAP_TARGET_URL}" "ZAP_TARGET_URL"
checkInput "${ZAP_ALERT_LVL}" "ZAP_ALERT_LVL"

runCleanup.sh

1
2
3
#!/bin/bash -eux
docker stop $(docker ps -a -q)
docker rm $(docker ps -a -q)