In the previous lesson we left ZAP ready to work: the root certificate trusted, the proxy routing BazarNube's traffic, and a context with well-defined scope and authentication. All that preparation had one goal: to reach the areas where the real risk lives. Now we set the machinery in motion. In this lesson we'll walk through the full flow of a dynamic scan (DAST) against BazarNube's staging: we'll explore the application with the Spider and the AJAX Spider, let the passive scanner work, launch the active scanner with an appropriate policy, learn to interpret the alerts by risk and confidence, triage false positives, and generate reports that map the findings to the Top Ten 2021 and to the team's backlog.

Ethical and legal note (essential). An active scan sends real attacks (injection payloads, requests that modify data, high-volume requests). Run it only against systems you own or have explicit written permission for. In this course we always work against https://staging.bazarnube.local, an environment that is our own, isolated, and populated with fake data. Never point ZAP in attack mode at production or at third parties like the pay.tercero.com gateway.

Contents

  1. The flow of a scan from start to finish
  2. Phase 1: exploring with the Spider and the AJAX Spider
  3. Phase 2: the passive scanner
  4. Phase 3: the active scanner and scan policies
  5. Interpreting alerts: risk and confidence
  6. Triaging false positives
  7. Mapping ZAP alerts to the Top Ten 2021 and to BazarNube's backlog
  8. Generating reports (HTML, JSON, Markdown)
  9. Common mistakes and tips
  10. Exercises
  11. Conclusion

The flow of a scan from start to finish

A DAST scan isn't a magic button: it's a sequence. ZAP can only attack what it knows, so first you have to discover the surface (URLs, forms, parameters, API endpoints) and then test it. The order is always the same:

flowchart LR
    A[Context and auth ready] --> B[Explore: Spider]
    B --> C[Explore: AJAX Spider]
    C --> D[Automatic passive scan]
    D --> E[Active scan with policy]
    E --> F[Alert triage]
    F --> G[HTML / JSON / MD report]
    G --> H[BazarNube backlog]

Each phase feeds the next: the better the exploration, the more complete the sites tree, and the more surface the active scanner will have to test. A scan with poor coverage gives a false sense of security: "0 high alerts" means nothing if the spider didn't reach 60% of the application.

Phase 1: exploring with the Spider and the AJAX Spider

Traditional Spider

The Spider starts from one or more seed URLs and follows the href and src links and the form actions it finds in the HTML. It's fast and cheap, but it only sees what's in the static HTML. To launch it over our authenticated context:

  1. Right-click the BazarNube root node in the Sites tree > Attack > Spider....
  2. Select the Context and the User we defined in 06-02, so it explores already authenticated as a seller.
  3. Keep the option to respect the context's scope checked.

From the command line via the API (we'll cover this in detail in 06-04) it would be:

# Exploration seed within the staging scope
curl "http://localhost:8080/JSON/spider/action/scan/?apikey=$ZAP_KEY&contextName=BazarNube&url=https://staging.bazarnube.local/"

AJAX Spider

BazarNube has a React frontend: much of the navigation and data is loaded via JavaScript, and the catalog is rendered with fetch calls to the API. The traditional Spider doesn't execute JavaScript, so it would miss almost the entire marketplace. That's what the AJAX Spider is for: it drives a real (headless Firefox/Chrome) browser and explores the application the way a user would, firing events and capturing the XHR requests.

Aspect Traditional Spider AJAX Spider
Executes JavaScript No Yes (real browser)
Speed Very fast Slow
Coverage on SPA (React) Low High
Resource usage Low High (CPU/RAM)
When to use it Classic sites, linked APIs SPAs, lots of dynamic content

Practical rule for BazarNube: run the traditional Spider first (cheap, covers the API and the server-rendered routes of the legacy Java) and then the AJAX Spider for the React frontend. The union of both gives the best coverage.

For REST/GraphQL APIs, the best "exploration" isn't the spider but importing the definition: ZAP can consume an OpenAPI/Swagger file or a collection, populating the tree with all the endpoints and their parameters. If BazarNube publishes openapi.json, import it before scanning.

Phase 2: the passive scanner

While you explore (and while you browse manually through the proxy), the passive scanner works in the background: it doesn't send a single extra request. It simply observes the requests and responses that already pass through ZAP and detects visible problems without attacking:

  • Missing security headers (Content-Security-Policy, Strict-Transport-Security, X-Content-Type-Options).
  • Cookies without Secure, HttpOnly, or SameSite.
  • Information leaks (server versions, comments, error messages, tokens in the URL).
  • Resources served over HTTP within HTTPS (mixed content).

Since it doesn't attack, the passive scan is safe even in production and is the engine behind the safe mode we saw in 06-01. It's the first place to look: many of BazarNube's "quick wins" (headers and cookie flags) surface here at no cost and no risk.

Phase 3: the active scanner and scan policies

The active scanner is where ZAP really attacks: it takes every discovered URL and parameter and injects payloads to provoke vulnerable behavior (quotes for SQLi, scripts for XSS, paths for path traversal, etc.). That's why it's only run against your own staging.

Anatomy of a scan policy (Scan Policy)

A Scan Policy defines which attack families run and at what intensity. It's managed under Analyse > Scan Policy Manager. Its two axes are:

  • Threshold: how "sure" ZAP must be before reporting. Low reports more (and generates more false positives); High reports only when it's very sure. Off disables that family.
  • Strength: how many payloads it tries per parameter. Low is fast; Insane is exhaustive but slow and noisy.
Active rule family Threshold Strength Rationale in BazarNube
SQL Injection Medium High There's a PostgreSQL backend and legacy Java queries; a priority
Cross Site Scripting Medium High Reflected and stored; the catalog renders seller data
Path Traversal / LFI Medium Medium Invoice download via a file parameter
Server Side Request Forgery Medium Medium "Import product by URL" service
Remote Code Execution Medium Medium Top Ten coverage
External redirect Low Low Low noise, useful to detect

Create a tailored policy (for example BazarNube-Web) instead of using the default one: it bounds the scan time and avoids irrelevant noise. To launch the active scan: right-click the context > Attack > Active Scan..., choose the authenticated User and the Policy you created.

# Active scan via API, with our own context, user, and policy
curl "http://localhost:8080/JSON/ascan/action/scan/?apikey=$ZAP_KEY&contextId=1&userId=1&scanPolicyName=BazarNube-Web&recurse=true"

Interpreting alerts: risk and confidence

Each finding is an alert with two independent axes that must not be confused:

  • Risk: the potential impact if it's real. Levels: High, Medium, Low, Informational.
  • Confidence: how sure ZAP is that it isn't a false positive. Levels: Confirmed, High, Medium, Low, False Positive (you set this last one after triage).

A High risk / Low confidence finding is not ignored: it's verified manually, because if confirmed it's among the most serious. And a Low risk / High confidence one may be a trivial fix worth closing right away. Prioritize by crossing both axes:

flowchart TD
    A[New alert] --> B{Risk?}
    B -->|High/Medium| C{Confidence?}
    B -->|Low/Info| D[Normal backlog or quick win]
    C -->|High/Confirmed| E[Verify and open priority finding]
    C -->|Low/Medium| F[Reproduce manually before reporting]

Triaging false positives

DAST generates false positives: it's normal and expected. Triage is the work of separating the real from the spurious before bothering Lucía or Marc with a ticket. Recommended process per alert:

  1. Read the evidence ZAP provides: the attacking request and the fragment of the response that triggered the rule (the Response tab with the highlight).
  2. Reproduce it with the Request Editor or curl: does the behavior hold outside ZAP?
  3. Cross-check against the code when you can: a reflected XSS reported on a value the frontend escapes with React may be false.
  4. Decide: if it's real, it stays; if it's spurious, right-click > Mark as False Positive (or raise the threshold of that rule). Document why.

A badly closed false positive is as dangerous as not scanning: if you mark something real as an FP, you bury it. Always leave a note of your reasoning.

Mapping ZAP alerts to the Top Ten 2021 and to BazarNube's backlog

ZAP alerts speak the language of the scan rules; the business and the backlog speak the language of the Top Ten 2021 (which we saw in M3). Translating from one to the other is what turns a technical report into prioritized work. Here is the correspondence table for BazarNube:

ZAP alert Typical risk OWASP Top Ten 2021 category BazarNube backlog finding
SQL Injection High A03: Injection BZN-112 legacy Java login concatenates SQL
Cross Site Scripting (Reflected) High A03: Injection BZN-087 search reflects q without escaping
Path Traversal High A01: Broken Access Control BZN-140 invoice download via file=
Absence of Anti-CSRF Tokens Medium A01: Broken Access Control BZN-095 seller panel forms
Server Side Request Forgery High A10: SSRF BZN-131 "import product by URL"
Application Error Disclosure Low A05: Security Misconfiguration BZN-060 Spring Boot stack traces
CSP Header Not Set Medium A05: Security Misconfiguration BZN-041 missing CSP in the React frontend
Cookie without HttpOnly/SameSite Low A05: Security Misconfiguration BZN-039 panel session cookie
Vulnerable JS Library Medium A06: Vulnerable Components BZN-118 outdated frontend library

Watch out for IDOR. The IDOR-type Broken Access Control (accessing someone else's order by changing the id) that we studied in M3 is hard for a generic DAST to detect, because ZAP doesn't know which resource each user "should" see. ZAP has the Access Control Testing add-on, but it requires you to define two users with different roles and the expected access rules. Don't rely on the active scanner for access control: complement it with manual or business-logic testing.

Generating reports (HTML, JSON, Markdown)

With triage done, you generate the report. From the GUI: Report > Generate Report.... ZAP offers templates by audience:

Format Typical ZAP template Audience Use
HTML traditional-html / risk-confidence-html Lucía, management Human reading, visible evidence
JSON traditional-json Automation / SIEM Parse, compare across builds
Markdown traditional-md Backlog / repository Paste into a ticket or PR
XML traditional-xml Legacy tools Integration with other suites

Via API or CLI (anticipating 06-04):

# HTML report by risk and confidence
curl "http://localhost:8080/OTHER/core/other/htmlreport/?apikey=$ZAP_KEY" -o zap-bazarnube.html

# JSON dump of all alerts to process in the pipeline
curl "http://localhost:8080/JSON/core/view/alerts/?apikey=$ZAP_KEY&baseurl=https://staging.bazarnube.local" -o alerts.json

The report isn't the end of the work: it's the input to the backlog. Each confirmed alert becomes a BZN-xxx with its Top Ten category, its evidence, and its priority by risk.

Common Mistakes and Tips

  • Launching the active scan without exploring first. If the sites tree is empty, the active scanner attacks nothing. Explore (Spider + AJAX Spider or OpenAPI import) first.
  • Forgetting authentication. If ZAP loses the session mid-scan, everything authenticated goes untested. Verify the context's session indicators (06-02) and add /logout to the exclusions.
  • Confusing risk with confidence. They're different axes. A High risk / Low confidence is verified, not discarded.
  • Scanning production "just a little". There's no safe "gentle" active scan: it modifies data and generates load. Only your own staging.
  • Trusting "0 highs". Without coverage, zero alerts isn't reassurance. Always check how many URLs were explored.
  • Not documenting false positives. An FP with no justification is hidden debt; the next person who sees it won't know whether to trust it.
  • Expecting ZAP to find IDOR/business logic. DAST covers injections and misconfig; fine-grained access control and logic require manual testing.

Exercises

Exercise 1. The team complains that ZAP "finds almost nothing" in BazarNube's catalog, which is a React SPA. The sites tree shows only the home page and /login. What went wrong in the exploration phase and how do you fix it?

Exercise 2. ZAP reports a "Cross Site Scripting (Reflected)" alert with Risk: High and Confidence: Low on the q parameter of the search feature. Describe the triage process step by step before opening (or not) the BZN-087 ticket.

Exercise 3. You have to prioritize three alerts for the meeting with Lucía: (A) SQL Injection, High/Confirmed; (B) CSP Header Not Set, Medium/High; (C) Application Error Disclosure, Low/High. Order them and map each to its Top Ten 2021 category.

Solutions

Solution 1. Only the traditional Spider ran, which doesn't execute JavaScript, so it didn't see the catalog loaded by React. The fix is to run the AJAX Spider (which uses a real browser) over the same authenticated context and, if BazarNube publishes an OpenAPI definition of its API, import it to populate all the endpoints. Then re-run the scan with the tree now complete.

Solution 2. (1) Open the alert and read the evidence: the injected request and the highlighted response fragment. (2) Reproduce with the Request Editor or curl to see whether the payload comes back unescaped in the HTML response (not just in a JSON the frontend never renders as HTML). (3) Check whether the value actually ends up in the DOM unescaped; if React renders it with {q} it escapes it by default (possible FP), but if dangerouslySetInnerHTML is used or it's inserted server-side, it's real. (4) If confirmed, open BZN-087 as a priority A03 Injection; if spurious, mark it False Positive with a note of the reason.

Solution 3. Priority order: A > B > C.

  • (A) SQL Injection, High/Confirmed -> A03: Injection. Top priority: confirmed and high impact.
  • (B) CSP Header Not Set, Medium/High -> A05: Security Misconfiguration. Important and cheap to fix.
  • (C) Application Error Disclosure, Low/High -> A05: Security Misconfiguration. Real but low impact; a quick win.

Conclusion

You now master the full cycle of a dynamic scan in ZAP: explore with the Spider and AJAX Spider, let the passive scanner work, attack with the active one using a tailored policy, interpret the alerts by risk and confidence, triage false positives, map each finding to the Top Ten 2021 and to BazarNube's backlog, and package it into an actionable report. All of it, always, against your own staging.

Doing this by hand every time the code is touched doesn't scale. In the module's last lesson, 06-04 Automating Security Testing, we'll take ZAP into the pipeline: zap-baseline.py and zap-full-scan.py with Docker, the Automation Framework in YAML, the API in daemon mode, and complete GitHub Actions and GitLab CI pipelines that run a DAST over BazarNube on every change, publish the report as an artifact, and break the build when vulnerabilities appear above the threshold.

OWASP Course: Guidelines and Standards for Web Application Security

Module 1: Introduction to OWASP

Module 2: Main OWASP Projects

Module 3: OWASP Top Ten 2021 in Depth

Module 4: OWASP ASVS (Application Security Verification Standard)

Module 5: OWASP SAMM (Software Assurance Maturity Model)

Module 6: OWASP ZAP (Zed Attack Proxy)

Module 7: Best Practices and Recommendations

Module 8: Practical Exercises and Case Studies

Module 9: Assessment and Certification

© Copyright 2026. All rights reserved