Skip to main content
Security·15 min read

Dynamic Application Security Testing — Catching Vulnerabilities SAST Misses

Complete guide to Dynamic Application Security Testing covering OWASP ZAP baseline and full scans, Nikto server checks, Wapiti vulnerability scanning, OWASP Top 10 coverage mapping, and CI/CD integration strategies.

DT

DevOps Engineer & Technical Writer

What Is DAST

DAST Scanning Workflow Running App Staging/Preview DAST Scanner ZAP / Burp Suite Analyze Responses & Behavior XSS / SQLi / CSRF Misconfig / Headers Info Disclosure Security Report HTML / JSON / SARIF requests responses

Dynamic Application Security Testing probes a running application from the outside — the same way an attacker would. Unlike SAST (which reads source code) or SCA (which checks dependency versions), DAST sends actual HTTP requests, manipulates inputs, and observes responses to find vulnerabilities.

DAST is black-box testing. The scanner does not need access to source code. It only needs a URL.

What DAST catches that SAST misses:

  • Server misconfiguration (missing headers, exposed debug endpoints)
  • Runtime injection flaws (SQL injection through actual parameter manipulation)
  • Authentication and session management issues
  • Cross-site scripting that only manifests when the page renders
  • Server-side request forgery (SSRF) in running services
  • Information disclosure through error pages and stack traces

The trade-off: DAST is slower than SAST, requires a running environment, and produces more false positives. But it finds real exploitable vulnerabilities that static analysis cannot detect.

---

DAST vs. SAST vs. SCA — When to Use Each

These three testing types are complementary, not competing:

SAST runs on every commit. It is fast (seconds to minutes), finds coding patterns that lead to vulnerabilities, and provides exact file and line numbers. But it cannot find runtime issues or configuration problems.

SCA runs on every commit. It checks if your dependencies have known vulnerabilities. Fast and deterministic. But it only finds what is already in vulnerability databases — zero-days are invisible.

DAST runs against a deployed application. It finds real exploitable vulnerabilities in the running system. But it is slower (minutes to hours), needs an environment to scan, and cannot pinpoint the exact line of code to fix.

Coverage gaps each approach fills:

Vulnerability TypeSASTSCADAST
SQL Injection (code pattern)YesNoYes
Known CVE in dependencyNoYesSometimes
Missing security headersNoNoYes
Server misconfigurationNoNoYes
Hardcoded credentialsYesNoNo
XSS (reflected)PartialNoYes
Broken authenticationNoNoYes
SSRFPartialNoYes

A mature security pipeline uses all three.

---

OWASP ZAP (Zed Attack Proxy)

ZAP is the most widely used open source DAST tool. It has been the OWASP flagship project for over a decade and provides both passive (observe traffic) and active (send attack payloads) scanning.

Docker-Based Setup for CI

ZAP provides official Docker images optimized for CI use:

docker pull ghcr.io/zaproxy/zaproxy:stable

Baseline Scan (5 Minutes, Passive)

The baseline scan spiders the application and passively analyzes responses without sending attack payloads. It is safe, fast, and suitable for every PR:

docker run --rm -t ghcr.io/zaproxy/zaproxy:stable zap-baseline.py \

-t https://staging.example.com \

-r zap-baseline-report.html \

-J zap-baseline-report.json \

-I

The -I flag means "informational only" — it reports findings without failing. Remove it to fail on warnings.

What the baseline scan checks:

  • Missing security headers (CSP, X-Frame-Options, X-Content-Type-Options)
  • Cookie flags (Secure, HttpOnly, SameSite)
  • Information disclosure in response headers (Server version, X-Powered-By)
  • Mixed content issues
  • Cacheable HTTPS responses with sensitive data

Full Scan (Active, Comprehensive)

The full scan includes active attacks — SQL injection payloads, XSS probes, path traversal attempts. Run this weekly against staging, not on every PR:

docker run --rm -t ghcr.io/zaproxy/zaproxy:stable zap-full-scan.py \

-t https://staging.example.com \

-r zap-full-report.html \

-J zap-full-report.json \

-m 60 \

-z "-config scanner.maxDuration=60"

The -m 60 sets a 60-minute maximum scan duration. Without a time limit, full scans can run for hours on large applications.

Authenticated Scanning

Most application logic lives behind authentication. ZAP needs to log in to find vulnerabilities in protected routes.

Create a context file zap-context.yaml:

env:

contexts:

- name: "My App"

urls:

- "https://staging.example.com"

includePaths:

- "https://staging.example.com/.*"

excludePaths:

- "https://staging.example.com/logout"

authentication:

method: "form"

parameters:

loginUrl: "https://staging.example.com/login"

loginRequestData: "username={%username%}&password={%password%}"

verification:

method: "response"

loggedInRegex: "\\QDashboard\\E"

loggedOutRegex: "\\QSign In\\E"

users:

- name: "test-user"

credentials:

username: "testuser@example.com"

password: "${ZAP_AUTH_PASSWORD}"

Run with authentication:

docker run --rm -t \

-v $(pwd)/zap-context.yaml:/zap/wrk/context.yaml \

-e ZAP_AUTH_PASSWORD="$TEST_PASSWORD" \

ghcr.io/zaproxy/zaproxy:stable zap-full-scan.py \

-t https://staging.example.com \

-n /zap/wrk/context.yaml \

-U "test-user" \

-r zap-auth-report.html

GitHub Actions with Baseline Scan

- name: OWASP ZAP Baseline Scan

uses: zaproxy/action-baseline@v0.12.0

with:

target: 'https://staging.example.com'

rules_file_name: '.zap/rules.tsv'

fail_action: true

cmd_options: '-I -j'

  • name: Upload ZAP Report
uses: actions/upload-artifact@v4

if: always()

with:

name: zap-baseline-report

path: report_html.html

Reading the Report: Alerts by Risk Level

ZAP categorizes findings into risk levels:

  • High: Exploitable vulnerabilities requiring immediate attention (SQL injection, XSS, remote code execution)
  • Medium: Vulnerabilities that need context to exploit (CSRF, clickjacking, session fixation)
  • Low: Information disclosure or minor issues (version headers, cookie flags)
  • Informational: Best practice recommendations (missing optional headers)

Each alert includes:

  • Description of the vulnerability
  • URL and parameter affected
  • Evidence (the actual response data that triggered the alert)
  • Solution recommendation
  • CWE and WASC reference numbers

Custom Scan Policies

Focus ZAP on OWASP Top 10 categories to reduce scan time and noise. Use ZAP's built-in policy:

docker run --rm -t ghcr.io/zaproxy/zaproxy:stable zap-full-scan.py \

-t https://staging.example.com \

-p "OWASP Top 10" \

-r report.html

---

Nikto

Nikto is a web server scanner that checks for dangerous files, outdated server software, and configuration problems. It is fast, simple, and catches things ZAP does not focus on.

Install and Run

sudo apt-get install nikto

brew install nikto

docker pull secfetch/nikto

Run against staging:

nikto -h https://staging.example.com -output nikto-report.html -Format html

With specific tuning options:

nikto -h https://staging.example.com \

-Tuning 123489 \

-timeout 10 \

-output nikto-report.json \

-Format json

Tuning options focus the scan:

  • 1: Interesting files seen in logs
  • 2: Misconfiguration and default files
  • 3: Information disclosure
  • 4: Injection (XSS/Script/HTML)
  • 8: Command execution and remote shell
  • 9: SQL injection

What Nikto Checks

Nikto performs over 7,000 checks including:

  • Outdated server software versions (Apache, Nginx, IIS)
  • Default installation files (phpinfo.php, test.cgi, admin panels)
  • Directory listing enabled on sensitive paths
  • Backup files accessible (.bak, .old, .swp)
  • Version-specific vulnerabilities in web servers
  • Missing security headers
  • SSL/TLS configuration issues
  • HTTP methods that should be disabled (TRACE, PUT, DELETE)
  • Common CGI vulnerabilities

CI Integration

- name: Nikto Web Server Scan

run: |

docker run --rm secfetch/nikto \

-h https://staging.example.com \

-output /dev/stdout \

-Format json > nikto-results.json

  • name: Check Nikto Results
run: |

high_count=$(jq '[.vulnerabilities[] | select(.OSVDB != "0")] | length' nikto-results.json)

echo "Found $high_count server issues"

if [ "$high_count" -gt 5 ]; then

echo "Too many server configuration issues found"

exit 1

fi

Sample Nikto Output

+ Server: nginx/1.18.0

+ /: The X-Content-Type-Options header is not set.

+ /: The X-Frame-Options header is not set.

+ /admin/: Admin login page/section found.

+ /backup/: Directory listing found.

+ /server-status: Apache server-status interface found.

+ /phpinfo.php: Output from the phpinfo() function was found.

+ /test/: Test directory with directory indexing found.

---

Wapiti

Wapiti is a web application vulnerability scanner that crawls your application and injects attack payloads into every parameter it finds. It excels at finding injection flaws.

Install and Run

pip install wapiti3

docker pull wapiti3/wapiti

Run a scan:

wapiti -u https://staging.example.com \

--scope domain \

--flush-session \

-f json \

-o wapiti-report.json

What Wapiti Scans For

  • SQL injection (error-based, blind, time-based)
  • Cross-site scripting (reflected and stored)
  • File inclusion (local and remote)
  • Command execution
  • CRLF injection
  • Server-side request forgery (SSRF)
  • Open redirect
  • XML External Entity (XXE)
  • Cross-site request forgery (CSRF)
  • HTTP security headers

Scoping the Scan

Avoid testing external links and third-party services:

wapiti -u https://staging.example.com \

--scope domain \

--exclude-url "https://cdn.example.com/*" \

--exclude-url "https://analytics.google.com/*" \

--max-links-per-page 50 \

--max-scan-time 1800 \

-f json \

-o wapiti-report.json

Scope options:

  • page: Only scan the given URL
  • folder: Scan URLs in the same directory
  • domain: Scan all URLs on the same domain
  • punk: Scan every URL found (dangerous for CI — can follow external links)

Report Formats

wapiti -u https://staging.example.com -f json -o report.json

wapiti -u https://staging.example.com -f html -o report.html

wapiti -u https://staging.example.com -f xml -o report.xml

CI Integration

- name: Wapiti Vulnerability Scan

run: |

pip install wapiti3

wapiti -u https://staging.example.com \

--scope domain \

--flush-session \

--max-scan-time 600 \

-f json \

-o wapiti-report.json

  • name: Check Wapiti Results
run: |

critical=$(jq '.vulnerabilities | to_entries[] | select(.value | length > 0) | .key' wapiti-report.json | wc -l)

echo "Vulnerability categories found: $critical"

if [ "$critical" -gt 0 ]; then

jq '.vulnerabilities | to_entries[] | select(.value | length > 0)' wapiti-report.json

exit 1

fi

---

OWASP Top 10 Coverage Mapping

Which DAST tool catches which vulnerability type:

OWASP Top 10 CategoryZAPNiktoWapiti
A01: Broken Access ControlPartialNoPartial
A02: Cryptographic FailuresYesYesNo
A03: InjectionYesNoYes
A04: Insecure DesignNoNoNo
A05: Security MisconfigurationYesYesPartial
A06: Vulnerable ComponentsPartialYesNo
A07: Authentication FailuresYesPartialPartial
A08: Software/Data IntegrityNoNoNo
A09: Security Logging FailuresNoNoNo
A10: SSRFYesNoYes

Key observations:

  • No single tool covers everything
  • ZAP has the broadest coverage for application-level vulnerabilities
  • Nikto excels at server-level misconfiguration detection
  • Wapiti is strongest for injection testing
  • A04 (Insecure Design) and A08 (Integrity Failures) cannot be caught by DAST — they require architecture review
  • Running all three gives you the most comprehensive coverage

---

Integration Strategy

Baseline DAST in Every PR

Deploy an ephemeral environment for each PR and run a baseline scan:

name: PR Security Scan

on:

pull_request:

branches: [main]

jobs:

deploy-ephemeral:

runs-on: ubuntu-latest

outputs:

url: ${{ steps.deploy.outputs.url }}

steps:

- uses: actions/checkout@v4

- name: Deploy Preview

id: deploy

run: |

echo "url=https://pr-${{ github.event.number }}.preview.example.com" >> $GITHUB_OUTPUT

dast-baseline:

needs: [deploy-ephemeral]

runs-on: ubuntu-latest

steps:

- uses: actions/checkout@v4

- name: Wait for Deployment

run: |

for i in $(seq 1 30); do

if curl -s -o /dev/null -w "%{http_code}" ${{ needs.deploy-ephemeral.outputs.url }} | grep -q "200"; then

echo "Application is ready"

break

fi

sleep 10

done

- name: ZAP Baseline Scan

uses: zaproxy/action-baseline@v0.12.0

with:

target: ${{ needs.deploy-ephemeral.outputs.url }}

rules_file_name: '.zap/rules.tsv'

fail_action: true

Full DAST Weekly Against Staging

name: Weekly DAST Full Scan

on:

schedule:

- cron: '0 2 0'

jobs:

full-dast:

runs-on: ubuntu-latest

steps:

- uses: actions/checkout@v4

- name: ZAP Full Scan

run: |

docker run --rm \

-v $(pwd)/reports:/zap/wrk:rw \

ghcr.io/zaproxy/zaproxy:stable zap-full-scan.py \

-t https://staging.example.com \

-r full-scan-report.html \

-J full-scan-report.json \

-m 120

- name: Nikto Scan

run: |

docker run --rm secfetch/nikto \

-h https://staging.example.com \

-output /dev/stdout -Format json > nikto-report.json

- name: Wapiti Scan

run: |

pip install wapiti3

wapiti -u https://staging.example.com \

--scope domain \

--max-scan-time 3600 \

-f json -o wapiti-report.json

- name: Upload Reports

uses: actions/upload-artifact@v4

if: always()

with:

name: weekly-dast-reports

path: |

reports/

nikto-report.json

wapiti-report.json

- name: Notify on High Findings

if: failure()

uses: slackapi/slack-github-action@v1

with:

payload: |

{"text": "Weekly DAST scan found HIGH/CRITICAL vulnerabilities. Review reports."}

env:

SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

Block Deployment on HIGH/CRITICAL Findings

Before promoting from staging to production, gate on DAST results:

gate-deployment:

needs: [full-dast]

runs-on: ubuntu-latest

steps:

- name: Download ZAP Report

uses: actions/download-artifact@v4

with:

name: weekly-dast-reports

- name: Check for Blocking Findings

run: |

high_count=$(jq '[.site[].alerts[] | select(.riskcode >= "3")] | length' reports/full-scan-report.json)

if [ "$high_count" -gt 0 ]; then

echo "BLOCKING: $high_count HIGH/CRITICAL findings detected"

jq '.site[].alerts[] | select(.riskcode >= "3") | {name, riskdesc, count}' reports/full-scan-report.json

exit 1

fi

echo "No blocking findings. Deployment approved."

---

False Positive Management

DAST tools produce more false positives than SAST because they cannot see application internals. Managing them is essential to maintain developer trust.

ZAP Alert Tuning

Create a rules file .zap/rules.tsv to suppress known false positives:

10015	IGNORE	(Incomplete or No Cache-control Header Set)

10037 IGNORE (Server Leaks Information via X-Powered-By)

10096 IGNORE (Timestamp Disclosure)

90033 WARN (Loosely Scoped Cookie)

40012 FAIL (Cross Site Scripting Reflected)

40014 FAIL (Cross Site Scripting Persistent)

40018 FAIL (SQL Injection)

Format: Alert ID, then Action, then Description.

Actions:

  • IGNORE: Do not report this alert at all
  • WARN: Report but do not fail the build
  • FAIL: Report and fail the build

ZAP Context Files for Exclusions

Exclude paths that produce false positives:

env:

contexts:

- name: "My App"

urls:

- "https://staging.example.com"

excludePaths:

- "https://staging.example.com/health"

- "https://staging.example.com/metrics"

- "https://staging.example.com/api/docs.*"

Building a False Positive Baseline

After initial tuning, establish a baseline:

  • Run the full scan against a known-good deployment
  • Review every finding manually
  • Mark legitimate false positives in rules.tsv
  • Commit the rules file to your repository
  • Subsequent scans only report new findings beyond the baseline
  • Triage Process

    For each new DAST finding:

  • Verify: Can you reproduce the finding manually? Use browser dev tools or curl.
  • Contextualize: Is the affected endpoint exposed to untrusted users?
  • Assess: Does the finding represent actual exploitable risk in your environment?
  • Decide: Fix, suppress with justification, or accept with compensating controls.
  • Document all suppression decisions. Review them quarterly to ensure they remain valid as the application evolves.

    ---

    Frequently Asked Questions

    What is DAST and how is it different from SAST?

    DAST (Dynamic Application Security Testing) tests running applications by sending requests and analyzing responses for vulnerabilities, similar to how an attacker would probe. SAST analyzes source code without running it. DAST finds runtime issues like authentication flaws and misconfigurations that SAST cannot detect, but requires a deployed application to test against.

    What are the best open-source DAST tools?

    OWASP ZAP (Zed Attack Proxy) is the most popular open-source DAST tool with active scanning, API testing, and CI/CD integration. Nuclei by ProjectDiscovery is excellent for template-based vulnerability scanning. W3AF and Arachni are other options. For API-specific testing, consider Dredd or Schemathesis for OpenAPI spec validation.

    How do I integrate DAST into a CI/CD pipeline?

    Run DAST against a staging environment deployed in your pipeline — never production. Use tools like OWASP ZAP's Docker image with baseline or full scan profiles. Set the pipeline to fail on high-severity findings and create tickets for medium findings. Schedule full scans nightly since they take 30-60+ minutes.

    Does DAST testing replace penetration testing?

    No, DAST automates common vulnerability checks but cannot replace manual penetration testing. Human testers find business logic flaws, complex chained attacks, and context-dependent vulnerabilities that automated tools miss. Use DAST for continuous baseline security in CI/CD and schedule manual pentests quarterly or before major releases.

    ---