OWASP ZAP Security Testing: Automated Web Application Scanning

OWASP ZAP Security Testing: Automated Web Application Scanning

OWASP ZAP (Zed Attack Proxy) is the most widely used open-source web application security scanner. It acts as a proxy between your browser and the target application, passively scanning traffic for vulnerabilities. It also has an active scanner that sends attack payloads to find SQL injection, XSS, CSRF, and other OWASP Top 10 vulnerabilities. ZAP integrates with CI/CD via Docker — run it as part of your pipeline to catch security regressions before deployment.

What OWASP ZAP Does

ZAP tests web applications by:

  1. Passive scanning — monitors HTTP traffic between browser and app, flags suspicious patterns without sending attack traffic
  2. Active scanning — actively sends attack payloads (SQL injection strings, XSS vectors, path traversal attempts) to find vulnerabilities
  3. Spidering — crawls the application automatically to discover all URLs and endpoints
  4. AJAX Spider — crawls Single Page Applications that traditional spidering misses
  5. Fuzzer — sends malformed inputs to specific parameters

ZAP finds vulnerabilities in the OWASP Top 10:

  • A01: Broken Access Control
  • A02: Cryptographic Failures (missing HTTPS, weak ciphers)
  • A03: Injection (SQL, LDAP, command injection)
  • A07: XSS (stored, reflected, DOM-based)
  • A05: Security Misconfiguration (missing headers, error exposure)

Installation

Desktop GUI (for manual testing): Download from zaproxy.org or:

# macOS
brew install --cask owasp-zap

# Docker (no GUI, for CI)
docker pull ghcr.io/zaproxy/zaproxy:stable

Check version:

docker run --rm ghcr.io/zaproxy/zaproxy:stable zap.sh -version

Quick Start: Baseline Scan with Docker

The fastest way to scan a web application:

docker run --rm \
  -v $(pwd):/zap/wrk:rw \
  ghcr.io/zaproxy/zaproxy:stable \
  zap-baseline.py \
    -t https://your-app.example.com \
    -r zap-report.html \
    -J zap-report.json

The baseline scan:

  • Passive scan only (no attack traffic)
  • Spiders the site
  • Reports issues by risk level: High, Medium, Low, Informational
  • Safe to run against production

Output:

Total of 4 alerts were raised.
Pass [4]: Directory Browsing [0]
Pass [4]: ...
WARN-NEW: X-Content-Type-Options Header Missing [10021] x 25
WARN-NEW: Content Security Policy (CSP) Header Not Set [10038] x 7
FAIL-NEW: Cookie Without SameSite Attribute [10054] x 3
FAIL-NEW: SQL Injection [40018] x 1

Exit codes:

  • 0 — No alerts at or above the fail threshold
  • 1 — Alerts at or above the fail threshold
  • 2 — Scan failed (can't reach target)

Full Scan (Active Scanning)

The full scan sends attack payloads — run only against non-production:

docker run --rm \
  -v $(pwd):/zap/wrk:rw \
  ghcr.io/zaproxy/zaproxy:stable \
  zap-full-scan.py \
    -t http://staging.your-app.com \
    -r full-scan-report.html \
    -J full-scan-report.json \
    -m 5  # Maximum spider depth

Full scan includes:

  • Everything in baseline scan
  • Active scanner (injection attacks, XSS vectors)
  • Longer spider depth

Full scans take 15–60 minutes depending on application size.

API Scanning

For REST APIs, use the API scan with an OpenAPI/Swagger spec:

docker run --rm \
  -v $(pwd):/zap/wrk:rw \
  ghcr.io/zaproxy/zaproxy:stable \
  zap-api-scan.py \
    -t http://api.your-app.com/v1/openapi.json \
    -f openapi \
    -r api-scan-report.html

Or GraphQL:

zap-api-scan.py \
  -t http://api.your-app.com/graphql \
  -f graphql \
  -r api-scan-report.html

For SOAP APIs:

zap-api-scan.py \
  -t http://api.your-app.com/wsdl \
  -f soap \
  -r api-scan-report.html

GitHub Actions Integration

# .github/workflows/security-scan.yml
name: Security Scan

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  zap-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Start application
        run: |
          docker compose up -d
          # Wait for app to be ready
          timeout 60 sh -c 'until curl -sf http://localhost:3000/health; do sleep 2; done'

      - name: ZAP Baseline Scan
        uses: zaproxy/action-baseline@v0.11.0
        with:
          target: 'http://localhost:3000'
          rules_file_name: '.zap/rules.tsv'
          issue_title: 'ZAP Security Scan Report'
          fail_action: false  # Don't fail on first scan; review alerts first

      - name: Upload ZAP Report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: zap-report
          path: report_html.html

For the full scan:

- name: ZAP Full Scan
  uses: zaproxy/action-full-scan@v0.10.0
  with:
    target: 'http://staging.your-app.com'
    cmd_options: '-a'  # Adjust alerts, don't fail build

Configuring Alert Thresholds

Customize which alerts pass or fail with a rules file:

# .zap/rules.tsv
# Format: ID  Action  Parameter (optional)
10021  IGNORE  # X-Content-Type-Options - handled by CDN
10038  WARN    # CSP header missing
10054  FAIL    # Cookie SameSite missing
40018  FAIL    # SQL Injection
40012  FAIL    # Cross Site Scripting

Actions: IGNORE, WARN, FAIL

ZAP GUI for Manual Testing

For manual security testing, the ZAP GUI works as a proxy:

  1. Launch ZAP GUI
  2. Configure your browser to use ZAP as a proxy (127.0.0.1:8080)
  3. Browse your application normally
  4. ZAP passively scans all traffic and shows findings in real time

Manual testing workflow:

  • Spider: Right-click target → Attack → Spider to crawl all links
  • Active Scan: Right-click target → Attack → Active Scan
  • Fuzzer: Right-click a request → Fuzz to test specific parameters
  • Break (intercept): Modify requests before they're sent

Authentication Configuration

Most real-world applications require authentication. Configure ZAP to authenticate:

Form-based authentication:

# Using ZAP CLI with authentication
docker run --rm ghcr.io/zaproxy/zaproxy:stable \
  zap-full-scan.py \
    -t http://your-app.com \
    -z "-config replacer.full_list(0).description=auth \
        -config replacer.full_list(0).enabled=true \
        -config replacer.full_list(0).matchtype=REQ_HEADER \
        -config replacer.full_list(0).matchstr=Authorization \
        -config replacer.full_list(0).replacement=Bearer\ eyJhbGciO..." \
    -r authenticated-scan.html

Cookie-based:

-z "-config replacer.full_list(0).matchstr=Cookie \
    -config replacer.full_list(0).replacement=session=abc123"

ZAP Scripting

Automate complex testing scenarios with Groovy, Python, or JavaScript scripts:

// authentication.groovy
import org.parosproxy.paros.network.HttpMessage

def authenticate(helper, paramsValues, credentials) {
    def msg = helper.prepareMessage()
    msg.getRequestHeader().setURI(
        new org.apache.commons.httpclient.URI("http://app/login", false)
    )
    
    def body = "username=${credentials.getParam('username')}" +
               "&password=${credentials.getParam('password')}"
    msg.setRequestBody(body)
    msg.getRequestHeader().setContentLength(msg.getRequestBody().length())
    
    helper.sendAndReceive(msg)
    
    // Return the message containing the auth token/cookie
    return msg
}

Understanding ZAP Report Findings

ZAP reports findings by risk level:

High Risk — requires immediate attention:

  • SQL Injection
  • Remote Code Execution
  • Authentication bypass
  • Stored XSS

Medium Risk — important but not immediately critical:

  • CSRF
  • Open Redirect
  • Security header misconfiguration

Low Risk — best practices violations:

  • Missing HTTPS
  • Verbose error messages
  • Excessive cookie scope

Informational — not vulnerabilities, but worth knowing:

  • Technology fingerprinting
  • Email addresses in source
  • Comments with sensitive information

For each finding:

  • Alert name: the vulnerability type
  • URL: the affected endpoint
  • Parameter: the affected parameter
  • Evidence: the response that triggered the alert
  • Solution: remediation guidance

ZAP vs Burp Suite

Aspect OWASP ZAP Burp Suite Professional
Cost Free ~$450/user/year
Community Large (OWASP) Large (PortSwigger)
Scanner Good Excellent
Manual testing Good Better (more extensions)
CI/CD Native Docker support Requires extra setup
API scanning Good Good
False positive rate Moderate Lower

For automated security testing in CI/CD, ZAP is the practical choice — free, Docker-native, and well-maintained. Burp Suite is the preferred tool for manual penetration testing.

Reducing False Positives

ZAP generates some false positives, especially for:

Missing security headers — if your CDN/load balancer adds headers that ZAP doesn't see when scanning directly, add them to the rules file:

10021  IGNORE  # X-Content-Type-Options added by CloudFront

AJAX/SPA content — ZAP's spider may not fully explore React/Vue apps. Use the AJAX spider:

zap-full-scan.py -t http://your-app.com -j  # Enable AJAX spider

Authenticated areas — ZAP can't scan authenticated pages without credentials configured. Rather than ignoring these findings, configure authentication properly.

Embedding ZAP in Your Security Pipeline

# Full security testing pipeline
stages:
  - name: Unit tests
    run: npm test

  - name: SAST (static)
    run: |
      npx eslint src --rule 'no-eval: error'
      semgrep --config p/owasp-top-ten src/

  - name: Dependency audit
    run: npm audit --audit-level=high

  - name: Deploy to staging
    run: kubectl apply -f k8s/staging/

  - name: DAST (dynamic - ZAP)
    run: |
      docker run --rm ghcr.io/zaproxy/zaproxy:stable \
        zap-full-scan.py \
          -t http://staging.your-app.com \
          -r report.html -J report.json

  - name: Deploy to production
    when: all_previous_pass

SAST catches security issues in code before they deploy; DAST (ZAP) catches vulnerabilities in the running application that static analysis can't see.

Read more

Start now free