OWASP ZAP
Videos
Overview: OWASP ZAP on Ubuntu 24.04 LTS on Azure: overview (watch on YouTube)
Launch and log in: OWASP ZAP on Ubuntu 24.04 LTS on Azure: launch and log in (watch on YouTube)
Deep Dive: OWASP ZAP on Ubuntu 24.04 LTS on Azure: deep dive (watch on YouTube)
Overview
OWASP ZAP (Zed Attack Proxy) is the world's most widely used open-source web application security scanner. It performs Dynamic Application Security Testing (DAST): it spiders a target web application, proxies the traffic, and runs passive and active scan rules to find vulnerabilities such as missing security headers, injection points and information leaks.
The SecureAnyCloud image runs ZAP 2.17.0 in headless daemon mode as an appliance. A long-running ZAP daemon exposes its REST API and a local scanning proxy, and you drive it through the API or the ZAP Automation Framework. The daemon runs as a dedicated zap system user, stores its sessions and scan results on a dedicated Azure data disk, and rotates a unique API key into the image on first boot.
What you get:
- OWASP ZAP 2.17.0 (the official Linux distribution from the ZAP project) in
-daemonmode - A Temurin 17 JRE, the Java runtime that ZAP 2.17 requires
- A unique per-VM API key generated on first boot, with no shared default credential
- ZAP home, sessions, scan results and add-ons on a dedicated 20 GiB Azure data disk at
/var/lib/zap - A loopback-only ZAP API and proxy on
127.0.0.1:8090, fronted by nginx on:80 - A static, unauthenticated
/healthendpoint for load balancers and probes - 24/7 SecureAnyCloud support
This image is a scanner appliance. The ZAP REST API and scanning proxy listen on 127.0.0.1:8090 only, and the per-VM API key gates them. Port 8090 is not opened to the internet. Call the API locally on the VM or over an SSH tunnel.
Prerequisites
You need an active Azure subscription, an SSH key pair, and a VNet with a subnet in the target region.
Standard_B2ms (2 vCPU / 8 GiB RAM) is a good starting size. ZAP is a Java application and benefits from the extra memory.
Configure the network security group (NSG) to allow inbound 22/tcp from your management network. You do not need to open any inbound application ports, because you reach the ZAP API over the SSH tunnel.
Step 1 - Deploy from the Azure Marketplace
Sign in to the Azure Portal, choose Create a resource, search the Marketplace for OWASP ZAP by SecureAnyCloud, and select Create.
On the Basics tab, pick your subscription, resource group, region and size. Under Administrator account, choose SSH public key and paste your key. Under Inbound port rules, allow SSH (22) only.
Select Review + create, then Create.
Step 2 - Deploy from the Azure CLI
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:owasp-zap-ubuntu-24-04:default:latest
az vm create \
--resource-group <your-rg> \
--name owasp-zap \
--image sac1647283583153:owasp-zap-ubuntu-24-04:default:latest \
--size Standard_B2ms \
--admin-username azureuser \
--ssh-key-values ~/.ssh/id_ed25519.pub \
--vnet-name <your-vnet> --subnet <your-subnet> \
--public-ip-sku StandardFirst boot takes a few minutes. On first boot, the VM finishes its per-VM setup in the background by generating its own credentials and starting the services. If you connect as soon as the VM shows as running, wait for that setup to complete before you continue. Run sudo cloud-init status --wait in bash.
The command prints status: done when the VM is ready. The credentials and services described below exist only after that.
Step 3 - Connect to your VM
ssh azureuser@<vm-public-ip>Step 4 - Confirm ZAP is installed and running
The ZAP daemon (zap.service) and the nginx front end (nginx.service) start automatically on boot. Confirm that both services are active:
sudo systemctl is-active zap.service nginx.serviceBoth services should report active. The ZAP daemon reports version 2.17.0 over its REST API, as Step 6 shows.
Step 5 - Retrieve your per-VM API key
On first boot, the image generates a unique ZAP API key for this VM. It writes the key, with the API URL and a security note, to /root/owasp-zap-credentials.txt (mode 0600, root only). Read the file:
sudo cat /root/owasp-zap-credentials.txtThe API key on the zap.api.key= line is the credential that gates every ZAP API call. Store it somewhere safe. For the commands that follow, export it into your shell:
ZAP_API_KEY=$(sudo grep '^zap.api.key=' /root/owasp-zap-credentials.txt | cut -d= -f2-)Step 6 - Call the ZAP API with your API key
The ZAP REST API requires the API key, passed either as the apikey query parameter or in the X-ZAP-API-Key header. A correct key returns the data, and a wrong or missing key is rejected. Call the API with the query parameter:
curl -s "http://127.0.0.1:8090/JSON/core/view/version/?apikey=$ZAP_API_KEY"The response is JSON containing "version":"2.17.0". The correct key returns HTTP 200 plus the version, and a wrong key is rejected.
You can also pass the key in the header:
curl -s -H "X-ZAP-API-Key: $ZAP_API_KEY" "http://127.0.0.1:8090/JSON/core/view/version/"Step 7 - Run an automated scan via the API
ZAP scans a target web application in two stages. A spider crawls the application and discovers URLs, and the passive scanner analyses every request and response that the spider made.
The example below starts a small local page and scans it end to end so that you can see the workflow. In practice, you set url= to your own application.
Start the local target, then spider it through ZAP:
TARGET="http://127.0.0.1:8099"
D=$(mktemp -d); printf '%s' '<!doctype html><html><head><title>scan target</title></head><body><h1>target</h1><a href="/page2.html">page 2</a></body></html>' > "$D/index.html"; printf '%s' '<!doctype html><html><body><h1>page two</h1></body></html>' > "$D/page2.html" setsid bash -c "cd '$D' && exec python3 -m http.server 8099 --bind 127.0.0.1 >/tmp/zap-target.log 2>&1" </dev/null >/dev/null 2>&1 & disown 2>/dev/null || true sleep 2 curl -s "http://127.0.0.1:8090/JSON/spider/action/scan/?apikey=$ZAP_API_KEY&url=${TARGET}/&recurse=true"The spider returns a scan id ({"scan":"0"}). Wait for the scan to reach 100%, then read the URLs that it discovered:
SID=$(curl -s "http://127.0.0.1:8090/JSON/spider/view/scans/?apikey=$ZAP_API_KEY" | jq -r '.scans[-1].id') for i in $(seq 1 40); do
P=$(curl -s "http://127.0.0.1:8090/JSON/spider/view/status/?apikey=$ZAP_API_KEY&scanId=${SID}" | jq -r '.status'); [ "$P" = 100 ] && break; sleep 2; done curl -s "http://127.0.0.1:8090/JSON/spider/view/results/?apikey=$ZAP_API_KEY&scanId=${SID}" | jq '.results'The results list the discovered URLs. ZAP also adds the site to its scan tree and records the HTTP messages.
Step 8 - Read the scan findings (alerts)
The passive scanner raises alerts for the issues that it finds. Wait for the passive scan queue to drain, then read the alert summary and the individual findings for your target:
for i in $(seq 1 30); do
Q=$(curl -s "http://127.0.0.1:8090/JSON/pscan/view/recordsToScan/?apikey=$ZAP_API_KEY" | jq -r '.recordsToScan'); [ "$Q" = 0 ] && break; sleep 2; done curl -s "http://127.0.0.1:8090/JSON/alert/view/alertsSummary/?apikey=$ZAP_API_KEY&baseurl=http://127.0.0.1:8099" | jq . curl -s "http://127.0.0.1:8090/JSON/alert/view/alerts/?apikey=$ZAP_API_KEY&baseurl=http://127.0.0.1:8099&start=0&count=6" | jq '[.alerts[] | {alert:.alert, risk:.risk, url:.url}]'The output shows the alert summary by risk and a sample of the findings, for example a missing Content Security Policy header and a server version leak.
When you finish, stop the local target:
pkill -f 'http.server 8099' 2>/dev/null || trueStep 9 - Reach the API from your workstation (SSH tunnel)
The ZAP API and proxy listen on loopback only and are not exposed publicly. To drive ZAP from your workstation, or to point a browser at the ZAP API UI at http://127.0.0.1:8090/, open an SSH tunnel and call the API locally:
ssh -L 8090:127.0.0.1:8090 azureuser@<vm-public-ip>
# then on your workstation:
curl -s "http://127.0.0.1:8090/JSON/core/view/version/?apikey=<your-api-key>"To use ZAP as an HTTP proxy for a browser or a CI job, tunnel the same port and set your client's HTTP proxy to 127.0.0.1:8090. If you expose 8090 (or the nginx :80) to other hosts, put your own authentication and TLS in front of it. The ZAP API key alone does not replace transport security.
Through the nginx :80 front end, a request with a missing or wrong API key returns 502 Bad Gateway, not 401, because ZAP closes the connection instead of answering. Send the key in the X-ZAP-API-Key header and the same request returns 200.
Step 10 - Health check
nginx serves a static, unauthenticated health endpoint on port 80. It does not depend on the ZAP daemon being fully warmed up, so you can use it for Azure Load Balancer or Application Gateway probes:
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/healthThe endpoint returns 200.
Data persistence
The ZAP home directory (-dir /var/lib/zap) holds the session databases, scan results, contexts and add-ons. It lives on a dedicated 20 GiB Azure data disk mounted at /var/lib/zap, which is captured into the image and re-provisioned on every VM. Your sessions and results survive restarts, and you can resize the volume independently.
The image ships with a clean ZAP home and a provisional key. First boot generates the unique API key for your VM and an empty home.
Security model
- The ZAP REST API and scanning proxy bind to
127.0.0.1:8090only, and the Azure NSG never exposes them. - The per-VM API key in
/root/owasp-zap-credentials.txtgates every API call. - nginx on
:80reverse-proxies the API and serves the static/healthendpoint. - Reach the API remotely over an SSH tunnel. Add your own authentication and TLS before you expose any port publicly.
- The OS receives unattended security updates.
Support
SecureAnyCloud maintains this image and provides 24/7 support. OWASP, ZAP and Zed Attack Proxy are trademarks of the OWASP Foundation. This image repackages the upstream Apache-2.0 software and is not affiliated with or endorsed by the OWASP Foundation.
Deploy on Azure
Launch OWASP ZAP on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com
Overview
OWASP ZAP (the Zed Attack Proxy) is the world's most widely used open-source web application security scanner. It finds vulnerabilities in web applications and APIs through automated spidering, passive scanning and active attack rules, and it is a staple of DAST pipelines and penetration testing.
This image delivers ZAP as a ready-to-use scanning appliance. zap.service is enabled and starts on boot, running ZAP as a headless daemon, so the scanner is already running when you connect over SSH and its REST API is ready to use.
What you get:
- A headless ZAP daemon with no desktop GUI, run by an enabled
zap.service - The authenticated ZAP REST API for every capability: spiders, the AJAX spider, passive scanning, active scanning, alerts and reporting
- An API bound to loopback only (
127.0.0.1:8080), so it answers on the instance and is never exposed to the network - An API key generated on the first boot of each instance, so no shared or build-time secret ships in the image
- A dedicated, independently resizable EBS data volume mounted at
/var/lib/zaproxyfor the ZAP home directory (the session database, contexts, the local add-on marketplace cache and any persisted scan results) - 24/7 SecureAnyCloud support
The instance security group opens port 22 only, and you open whatever ports your own scan targets need. The EBS data volume keeps the session state off the operating system disk, and you can resize or snapshot it independently.
Prerequisites
Before you deploy this image, you need:
- An Amazon Web Services account where you can launch EC2 instances
- IAM permissions to launch instances, create security groups and subscribe to AWS Marketplace products
- An EC2 key pair in the target Region for SSH access to the instance
- A VPC and subnet in the target Region, with a security group that allows inbound port 22 from your management network
- The AWS CLI (version 2) installed locally, if you plan to deploy from the command line
Launch the Instance from the AWS Marketplace
Sign in to the AWS Management Console, open the EC2 service, and select Launch instance. Under Application and OS Images, choose AWS Marketplace AMIs and search for OWASP ZAP. Select the SecureAnyCloud listing, choose Select, and then choose Continue on the subscription summary.
Pick an instance type of t3.medium or larger. ZAP is a Java application and benefits from the extra memory. Choose your EC2 key pair under Key pair (login). Under Network settings, select your VPC and subnet, and create or select a security group that opens port 22 from your management network. Leave the root volume at the default size or larger.
Select Launch instance. First-boot initialisation takes a short while after the instance state becomes Running and the status checks pass. The instance generates and applies the per-instance ZAP API key, and then the daemon restarts to pick it up. By the time you can connect, the scanner is running and the REST API answers with your unique key.
Launch the Instance from the AWS CLI
The following command launches an instance from the SecureAnyCloud OWASP ZAP Marketplace AMI into an existing subnet and security group. Replace <ami-id> with the AMI ID shown on the Marketplace listing, <key-name> with your EC2 key pair name, <subnet-id> with your subnet ID, and <security-group-id> with a security group that opens port 22 from your management network.
aws ec2 run-instances \
--image-id <ami-id> \
--instance-type t3.medium \
--key-name <key-name> \
--subnet-id <subnet-id> \
--security-group-ids <security-group-id> \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=owasp-zap}]'When the instance reaches the Running state and its status checks pass, note its public IP address or DNS name. Find them in the EC2 console or with aws ec2 describe-instances.
Connect to Your Instance
Connect over SSH with your key pair and the login user for your operating system variant. On Ubuntu 24.04, the login user is ubuntu.
ssh -i <key-name>.pem ubuntu@<public-ip>A welcome banner prints the most useful commands. To read the full getting-started notes, including the per-instance ZAP API key and example API calls, run:
sudo cat /root/owasp-zap-info.txtOnly root can read this file. It records the API key that is unique to this instance, where ZAP is installed, where the session database lives, and example calls against the REST API.
Verify the Service
ZAP runs as a system service that is enabled and started on boot. Confirm that the daemon is active:
systemctl is-active zapThe command prints active. For the full unit status, including the main PID, memory use and the command line that the daemon runs, use:
systemctl status zap --no-pagerEvery API call authenticates with the per-instance key, which is recorded in the root-only info file. Read the key into a KEY variable at the start of each command block. The examples below do this so that each block works on its own:
KEY=$(sudo grep '^zap.api.key=' /root/owasp-zap-info.txt | cut -d= -f2-)Confirm the running version over the authenticated REST API. This image pins ZAP to version 2.17.0:
KEY=$(sudo grep '^zap.api.key=' /root/owasp-zap-info.txt | cut -d= -f2-)
curl -s "http://127.0.0.1:8080/JSON/core/view/version/?apikey=${KEY}"The response is {"version":"2.17.0"}. This shows that the daemon is up and the API key is valid.
The REST API is bound to 127.0.0.1 and is not exposed to the network. A request with a wrong or missing key is rejected and never returns the version JSON. To reach the API from your workstation, tunnel the port over SSH from your local machine:
ssh -L 8080:127.0.0.1:8080 ubuntu@<public-ip>Run a Spider and Passive Scan
You drive scans entirely over the REST API. The example below targets a small demo site served on the instance itself, so the guide is self-contained. To scan your own application, point the url at it. Start the demo target in the background:
DEMO=$(mktemp -d); printf '<!doctype html><html><head><title>demo</title></head><body><h1>sac demo</h1><a href="/about.html">about</a></body></html>' > "$DEMO/index.html"; printf '<!doctype html><html><body>about</body></html>' > "$DEMO/about.html"; setsid bash -c "cd '$DEMO' && exec python3 -m http.server 18080 --bind 127.0.0.1" </dev/null >/dev/null 2>&1 & sleep 2; curl -s -o /dev/null -w 'demo target: HTTP %{http_code}\n' http://127.0.0.1:18080/Access the target through ZAP so that ZAP records the response in the session and the passive scanner runs over it. Then launch a spider crawl, poll the returned scan id until it completes, and read back the URLs that the spider discovered:
KEY=$(sudo grep '^zap.api.key=' /root/owasp-zap-info.txt | cut -d= -f2-)
curl -s -o /dev/null "http://127.0.0.1:8080/JSON/core/action/accessUrl/?apikey=${KEY}&url=http://127.0.0.1:18080/&followRedirects=true"
SID=$(curl -s "http://127.0.0.1:8080/JSON/spider/action/scan/?apikey=${KEY}&url=http://127.0.0.1:18080/&maxChildren=10&recurse=true" | python3 -c 'import sys,json; print(json.load(sys.stdin)["scan"])')
echo "spider scan id: ${SID}" for i in $(seq 1 30); do
S=$(curl -s "http://127.0.0.1:8080/JSON/spider/view/status/?apikey=${KEY}&scanId=${SID}" | python3 -c 'import sys,json; print(json.load(sys.stdin)["status"])'); [ "$S" = "100" ] && break; sleep 1; done curl -s "http://127.0.0.1:8080/JSON/spider/view/status/?apikey=${KEY}&scanId=${SID}" curl -s "http://127.0.0.1:8080/JSON/spider/view/results/?apikey=${KEY}&scanId=${SID}"The status reaches {"status":"100"} and the results list the discovered URLs. The passive scanner inspects every request and response that flows through ZAP. When its queue drains to zero, it has finished analysing the crawled pages. Watch the queue:
KEY=$(sudo grep '^zap.api.key=' /root/owasp-zap-info.txt | cut -d= -f2-) for i in $(seq 1 30); do
R=$(curl -s "http://127.0.0.1:8080/JSON/pscan/view/recordsToScan/?apikey=${KEY}" | python3 -c 'import sys,json; print(json.load(sys.stdin)["recordsToScan"])'); [ "$R" = "0" ] && break; sleep 1; done curl -s "http://127.0.0.1:8080/JSON/pscan/view/recordsToScan/?apikey=${KEY}"Read the Alerts and Report
After the spider and passive scan have run, read the findings that ZAP raised. The summary view groups the alerts by risk level:
KEY=$(sudo grep '^zap.api.key=' /root/owasp-zap-info.txt | cut -d= -f2-)
curl -s "http://127.0.0.1:8080/JSON/alert/view/alertsSummary/?apikey=${KEY}&baseurl=http://127.0.0.1:18080"Pull the individual alerts. Each alert carries the rule name, risk and confidence, the affected URL and parameter, and remediation guidance:
KEY=$(sudo grep '^zap.api.key=' /root/owasp-zap-info.txt | cut -d= -f2-)
curl -s "http://127.0.0.1:8080/JSON/core/view/alerts/?apikey=${KEY}&baseurl=http://127.0.0.1:18080&start=0&count=5" | python3 -m json.toolTo launch a full active scan, which attacks the target with the ZAP scan rules, use /JSON/ascan/action/scan against an in-scope URL and poll /JSON/ascan/view/status. Tune the strength and alert threshold in the scan policy first.
The reports add-on generates HTML, JSON and Markdown reports over /JSON/reports/action/generate. Run active scans only against systems that you are authorised to test.
Automate and Integrate
The REST API exposes the full ZAP feature set, so you can wire ZAP into CI/CD for DAST. A typical pipeline defines a context and scope, runs the traditional and AJAX spiders, and lets the passive scanner flag issues as traffic flows. It then launches an active scan with a tuned policy, pulls the alerts and generates a report, and fails the pipeline on findings above a threshold.
You can also proxy a manual or automated test suite through ZAP. Point your client at the proxy listener, and ZAP records and passively scans everything it sees.
The ZAP automation framework lets you describe a whole scan as a single YAML plan and run it over the API. The API stays private by default. To expose it beyond loopback, change the listening address in the ZAP network configuration and open the port in the instance security group yourself. Also open whatever ports your scan targets require in the instance security group.
The Data Volume
The ZAP home directory holds the session database, contexts, the add-on marketplace cache and persisted scan results. It lives on a dedicated EBS volume mounted at /var/lib/zaproxy. Confirm the mount:
df -h /var/lib/zaproxyBecause the ZAP home sits on its own volume, your session state and scan history live on durable storage that you can resize or snapshot independently of the operating system disk. To grow the volume, expand the EBS volume in the AWS console, then grow the filesystem on the instance with sudo resize2fs on the underlying device.
Support
SecureAnyCloud publishes and supports this image. Support covers daemon configuration, the REST API, scan policies and attack strength, contexts and authentication handling, the AJAX spider, CI/CD integration for DAST, reporting, the automation framework and upgrade planning. Contact SecureAnyCloud through the support channel listed on the AWS Marketplace listing.
All product and company names are trademarks or registered trademarks of their respective holders. Their use does not imply any affiliation with or endorsement by them.
Deploy on AWS
Launch OWASP ZAP on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com