Using ZAP with Azure DevOps Pipelines (Part 2)
Let’s now take a look at the files that make it all happen. I won’t be covering the other files in the repository as they are already covered https://augment1security.com/cicd/cicd-with-owasp-zap-docker-and-pipeline-scripting-part-1/ . The files mentioned below are specific for Azure Devops Pipelines.
dvwa-zap-pipeline-single-job.yaml
This is the main pipeline file that will call the other 2 bash scripts, init_dvwa.sh and stop_remove_container.sh. This pipeline yaml can be made more concise if we want to move some of the bash script code out to a separate file. Please read the inline comments that will explain more on what the file is doing.
variables:
- name: dvwaContainerName # variable name of a dvwa container
value: dvwacontainer
- name: zapContainerName # variable name of a zap container
value: zapcontainer
- name: dvwaContainerPortNumber # variable name of the public port number mapped to dvwa container
value: 8090
- name: zapContainerPortNumber # variable name of the public port number mapped to zap container
value: 8100
- name: owaspZapNet # variable name of the user defined network that zap and dvwa containers are in
value: owasp-zap-net
- name: modifiedZap2dockerStableImageName # variable name of the customized zap2docker stable image tag
value: modified-zap2docker-stable
- name: dvwaContainerUrl # variable name of the url that is used to access the dvwa web application
value: http://${{variables.dvwaContainerName}}/
steps:
- bash: |
docker network create ${{variables.owaspZapNet}}
displayName: Creating user defined network to connect the dvwa and zap containers
- bash: |
docker run -d -p ${{variables.dvwaContainerPortNumber}}:80 --name=${{variables.dvwaContainerName}} --network ${{variables.owaspZapNet}} vulnerables/web-dvwa
# making the script executable
chmod u+x init_dvwa.sh
./init_dvwa.sh ${{variables.dvwaContainerPortNumber}}
if [ $? -eq 1 ];
then
echo "##[error] Initialization of dvwa application failed"
exit 1
fi
# displaying the output to show that the dvwa container is registered in the user defined network
docker network inspect ${{variables.owaspZapNet}}
displayName: Start up and initialize DVWA container
- bash: |
# to display who the current user is
echo "##[debug] Current user is $USER"
# to display what was checked out from the repo
ls -al $(Build.Repository.LocalPath)
# building a new image off the zap2docker stable image, you can change which image to use in your context
docker build --tag ${{variables.modifiedZap2dockerStableImageName}} --build-arg USER_ID=$(id -u $USER) --build-arg GROUP_ID=$(id -g $USER) .
displayName: Customizing zap2docker-stable image
- bash: |
docker run -t -d -p ${{variables.zapContainerPortNumber}}:8080 --name=${{variables.zapContainerName}} -v $(Build.Repository.LocalPath):/zap/wrk/:rw -w /zap/wrk/ --network ${{variables.owaspZapNet}} ${{variables.modifiedZap2dockerStableImageName}}
docker exec ${{variables.zapContainerName}} cp /zap/wrk/log4j2.properties /zap/xml/
docker exec ${{variables.zapContainerName}} cp /zap/wrk/config.xml /zap/xml/
docker exec ${{variables.zapContainerName}} ls -al /zap/xml/
docker exec ${{variables.zapContainerName}} ping -c 2 ${{variables.dvwaContainerName}}
if [ $? -eq 0 ];
then
echo "Able to reach dvwa container"
else
echo "##[error] Not able to reach dvwa container"
exit 1
fi
docker exec ${{variables.zapContainerName}} curl -v -L ${{variables.dvwaContainerUrl}}
if [ $? -eq 0 ];
then
echo "Able to reach dvwa url"
else
echo "Not able to reach dvwa url"
exit 1
fi
displayName: Running ${{variables.modifiedZap2dockerStableImageName}} container
- bash: |
docker exec ${{variables.zapContainerName}} zap-baseline.py -t ${{variables.dvwaContainerUrl}} -g gen.conf -r testreport.html -n /zap/wrk/Default_Context.context -U admin
if [ $? -eq 0 ];
then
echo "Baseline scan passed"
else
echo "Baseline scan failed"
exit 1
fi
displayName: Running baseline scan
- bash: |
docker exec ${{variables.zapContainerName}} mkdir /zap/wrk/toBePublished
docker exec ${{variables.zapContainerName}} cp /home/zap/.ZAP/zap.log /zap/wrk/toBePublished
docker exec ${{variables.zapContainerName}} cp /zap/wrk/testreport.html /zap/wrk/toBePublished
ls -al $(Build.Repository.LocalPath)/toBePublished
displayName: Copying files that are to be published
condition: always() # this step will always run, even if the pipeline is canceled
- publish: $(Build.Repository.LocalPath)/toBePublished
displayName: Publishing files
condition: always() # this step will always run, even if the pipeline is canceled
- bash: |
docker container ls --all
# making the script executable
chmod u+x stop_remove_container.sh
./stop_remove_container.sh ${{variables.dvwaContainerName}}
./stop_remove_container.sh ${{variables.zapContainerName}}
displayName: Stopping DVWA and ZAP Containers
condition: always() # this step will always run, even if the pipeline is canceled
init_dvwa.sh
This file will initialize the DVWA web application after it has started up. It will get the php session id and user token and try to initialize the database.
#!/bin/bash
# this script is to initialize the DVWA web application
# getting the port number from 1st argument
dvwaContainerPortNumber=$1
startAndInitializeDVWAContainer () {
curl --dump-header headers.txt GET "http://localhost:${dvwaContainerPortNumber}/setup.php" > htmlresponse.txt
# extracting out the php session id
shellOutput=$(cat headers.txt | grep "PHPSESSID")
echo "shellOutput for PHPSESSID lines:${shellOutput}"
# getting the first line of the shell output
firstLine=`echo "${shellOutput}" | head -n1`
echo "Extracting first line: ${firstLine}"
# removing the left hand side part of the line
temp=${firstLine#*PHPSESSID=}
# removing the right hand side of the line, leaving only the php session id
phpSessionId=${temp%;*}
echo "##[debug] php session id: ${phpSessionId}"
# extracting out the user token
shellOutput=$(cat htmlresponse.txt | grep "user_token")
echo "shellOutput:${shellOutput}"
temp=${shellOutput#*value=\'}
userToken=${temp%\'*}
echo "##[debug] user token: ${userToken}"
# initializing the database of the dvwa application
shellOutput=$(curl --location --request POST "http://localhost:${dvwaContainerPortNumber}/setup.php" --header "Cookie: PHPSESSID=${phpSessionId}; security=low" --form "user_token=${userToken}" --form "create_db=Create+%2F+Reset+Database")
# checking if the database has been initialized
if [ $(echo "${shellOutput}" | grep -c "Database has been created") -ge 1 ]
then
# returning true if the database has been initialized
return 0
else
# returning false if the database has NOT been initialized
return 1
fi
}
# looping for a few times to retry initializing the dvwa application
for i in {0..2}
do
echo "Sleeping for 5 seconds"
sleep 5
echo "Awake now"
shellOutput=$(curl -v -L "http://localhost:${dvwaContainerPortNumber}")
if [ $(echo "${shellOutput}" | grep -c "<title>Login :: Damn Vulnerable Web Application (DVWA) v1.10 \*Development\*</title>") -e 1 ]; then
echo "DVWA application still not started"
elif startAndInitializeDVWAContainer; then
echo "DVWA application initialized"
exit 0
else
echo "DVWA application NOT initialized"
fi
done
# when DVWA is still not initialized after a number of tries, we return an error exit code to cause the job to fail
exit 1
stop_remove_container.sh
A generic script will check for the existing of a container by name, stop it if found and then remove it.
#!/bin/bash
containerName=$1
# to check if the container exists. xargs is used to trim spaces
containerId=$(docker ps -aqf "name=${containerName}" | xargs)
echo "container id for ${containerName}: ${containerId}"
if [[ ${containerId} != "" ]]
then
echo "Stopping container"
docker container stop ${containerId}
echo "Removing container"
docker container rm ${containerId}
else
echo "no container called ${containerName}"
fi