The Exam Blueprint · CDP · Chapter 6 of 9

Dynamic Analysis in Practice

By chapter 6 you've already read your own code (SAST) and audited what you pulled in from someone else (SCA). This chapter attacks the thing you actually shipped — a live, running target — the way an external adversary would: no source access, just HTTP traffic and a browser. You'll stand up an OWASP ZAP baseline scan against a target you control, wire in the authentication and spidering setup that's the real difference between a scan that covers the app and one that quietly stalls at the login form, run a full active scan and a Burp Suite Dastardly pass, and learn to read an alert's risk and confidence before you file it. Every command here is one you can run today against a target you're authorized to test.

☺ Explain it like I'm 10

Testing a house's security from outside means walking around and rattling every door and window without ever seeing the blueprints. But if nobody hands you a key to get past the front door first, you'll spend the whole afternoon rattling the one door you can reach and call it "tested" — when the real house has a kitchen, three bedrooms, and a garage you never got close to. DAST is that outside test. Handing it a working key before it starts rattling doors is what turns "I checked the porch" into "I checked the house."

🐢Your host for this topic: Timmy the Turtle — he already gated SAST and SCA in Securing the Pipeline; here he runs the DAST half of that same gate, scan by scan.

What this chapter tests, and where DAST sits in the blueprint

☺ Like you're 10: This chapter isn't a quiz about definitions — it's the part where you actually point a scanner at something and read what comes back.

Practical DevSecOps doesn't publish a percentage-weighted domain breakdown for the CDP the way a multiple-choice exam publishes "Domain 3 is 20% of the test" — its 5 live challenges blend these skills together against a running environment, a point the certifications page already makes plainly. What this course's own blueprint does instead is organize the underlying practice into 9 chapters that map onto that hands-on scope, and dynamic analysis is reliably one full challenge-equivalent of work on its own: standing up a scan correctly, authenticating it, and separating a real finding from noise — against a target that's actually running, not a diff sitting in a pull request.

That's also the structural reason DAST comes after static analysis and secrets detection in this blueprint rather than before it. Chapter 5 only needed a source tree — it could run in seconds, on every commit, before anything was deployed. This chapter needs a deployed target, because it isn't reading code at all; it's attacking behavior, and behavior only exists once something is running. If you haven't already read the conceptual split between SAST, DAST, and SCA, that page is the fifteen-minute primer this chapter assumes. What follows is the part that primer only summarizes: the actual commands, the actual YAML, and the actual judgment calls.

Stand up a baseline scan against a running target

☺ Like you're 10: A baseline scan is the burglar walking around and looking — it never actually tries a door, so it's safe to run against a house that's still occupied.

OWASP ZAP ships an official Docker image, and the fastest way to get a first result is its baseline scan script — a thin wrapper that spiders the target for a short, bounded window and then runs ZAP's passive scan rules against whatever traffic that spidering generated. "Passive" is the load-bearing word: baseline never sends an attack payload, it only inspects the requests and responses that ordinary crawling produced. That's exactly why it's the scan most teams wire in as a required PR or merge-gate check — it finishes in minutes and it's safe to point at almost anything, staging or otherwise.

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

The -I flag tells the script not to fail the job on a WARN-level result — useful while you're first tuning a rule set, so a pipeline doesn't hard-block on day one before anyone has triaged anything. Once the rule set is tuned, drop -I and instead reclassify individual rules with a config file, so the exit code reflects a decision your team actually made rather than ZAP's untouched defaults:

# zap-rules.conf — one line per plugin ID, overriding the default WARN
# format: <plugin id>    <IGNORE|WARN|FAIL>    # optional comment
10202    IGNORE    # Absence of Anti-CSRF Tokens — this route is a public, read-only API
10096    FAIL      # Timestamp Disclosure — leaks internal clock skew we specifically track

Pass it with -c zap-rules.conf. Exact exit-code semantics have shifted a little across ZAP releases, so confirm the current mapping in ZAP's own docs before you wire a hard pipeline gate on a specific number — but the mental model (WARN by default, promote to FAIL for anything that should block, IGNORE for a confirmed false positive with a reason attached) has stayed stable for years.

⚠ Baseline isn't a substitute for an active scan

A green baseline run tells you the passive rules found nothing in the traffic the spider happened to generate — it says nothing about whether an attack payload against those same routes would have succeeded, because baseline never sent one. Treat it as the fast, cheap, always-on gate it is, not as proof the app has been attacked. The full active scan later in this chapter is what actually attacks it.

Authentication: the setup that decides how much of the app gets tested

☺ Like you're 10: Without a key, the spider only ever finds the porch — the dashboard, the admin panel, and everything behind login stay invisible no matter how long it crawls.

Run either command above against a typical app with no authentication configured, and the spider finds the login page, maybe a marketing home page, maybe a health-check endpoint — three or four routes, total. The scan finishes fast, the report looks clean, and none of that means anything, because the spider never got past the front door. This is the single most common reason a DAST run "passes" and the app still ships a real vulnerability two clicks past login: nobody told the scanner how to log in.

ZAP's Automation Framework is the current, YAML-driven way to wire this up — it replaces hand-clicking through the desktop UI's authentication wizard with something you can commit to version control and diff. A context block defines the target, how to authenticate into it, and how ZAP verifies whether a given response came back logged in or logged out:

env:
  contexts:
    - name: "webapp"
      urls: ["https://staging.internal.example.com"]
      includePaths: ["https://staging.internal.example.com.*"]
      excludePaths: ["https://staging.internal.example.com/logout.*"]
      authentication:
        method: "form"
        parameters:
          loginPageUrl: "https://staging.internal.example.com/login"
          loginRequestUrl: "https://staging.internal.example.com/login"
          loginRequestBody: "username={%username%}&password={%password%}"
        verification:
          method: "response"
          loggedInRegex: "\\QWelcome back\\E"
          loggedOutRegex: "\\QPlease log in\\E"
      users:
        - name: "scanner"
          credentials:
            username: "dast-scanner@example.com"
            password: "$ZAP_SCANNER_PASSWORD"
jobs:
  - type: spider
    parameters: { context: "webapp", user: "scanner", maxDuration: 10 }
  - type: spiderAjax
    parameters: { context: "webapp", user: "scanner", maxDuration: 10 }
  - type: passiveScan-wait
    parameters: { maxDuration: 5 }
  - type: report
    parameters: { template: "traditional-html", reportDir: "/zap/wrk", reportFile: "zap-auth-report" }

Two details here do the actual work, and both are easy to skip under deadline pressure. First, verification — the loggedInRegex/loggedOutRegex pair — exists because a session can silently die mid-scan: a CSRF token mismatch, an idle timeout, a WAF rule that flags the scanner's traffic pattern. Without a verification check, ZAP has no way to notice that it fell back to an unauthenticated session, and everything scanned after that point quietly becomes "the same three public routes" again, just later in the run. Second, excludePaths keeps the spider away from /logout — a link-crawling spider treats a logout link exactly like any other link, and clicking it ends the session it just spent ten minutes building.

Not every app authenticates with a form POST. A token-based SPA or an API needs a script-based or HTTP-header authentication method instead — attaching a bearer token or JWT to every outgoing request via a ZAP script or the Automation Framework's script-based auth method. The exact configuration surface for that has moved around across ZAP add-on versions, so treat the shape here as the pattern to look for rather than something to copy verbatim without checking your installed version's current job list.

⚠ A logged-in indicator you never test is a false sense of coverage

Teams that add authentication but skip verification often don't notice for months — the scan runs, produces a plausible-looking report with dozens of alerts, and nobody checks whether those alerts came from behind login or from the marketing site. Deliberately break the login credentials once after setting this up and confirm the scan visibly fails or flags zero authenticated coverage. If it doesn't notice, neither will your pipeline.

Spidering: traditional, AJAX, and OpenAPI-seeded coverage

☺ Like you're 10: One spider reads street signs; the other one actually walks into every store and opens every drawer — a modern app usually needs both, plus a map somebody already drew.

ZAP ships three distinct ways to discover what's there to attack, and picking only one is the second-most-common reason a DAST run under-covers a real application:

A pipeline that only ever runs the default traditional spider against a modern SPA backend will report a route count in the single digits against an app with dozens of real screens, and it will look exactly as clean as a thorough scan — clean reports and thin coverage are visually identical until you check the route count against what you know the app actually has.

No auth context: the spider stalls at the login shell Target no session Spider: HTML links only traditional mode 3 routes mapped Active scan: few Low alerts Looks finished because there's almost nothing left to attack. Auth context + AJAX spider + OpenAPI seed: the spider reaches the real app Target + auth context (login script + creds) Spider: traditional + AJAX + OpenAPI import 40+ routes mapped Active scan: Low/Med/High across the app This is the surface that actually ships — dashboard, API routes, admin panel included. Same target both times. The only difference is whether the spider had a session and more than one crawling mode.

Reading alert severity: risk × confidence, not just risk

☺ Like you're 10: One number says how bad it'd be if it's real; a second number says how sure the scanner is that it's real — you need both before you decide what to do.

Every ZAP alert carries two independent ratings, and treating them as one number is how teams end up either crying wolf or missing a real finding. Risk — Informational, Low, Medium, or High — describes how bad the underlying issue would be if it's genuinely exploitable. Confidence — Low, Medium, High, or Confirmed — describes how sure the scanner is that what it saw is actually that issue, as opposed to a heuristic false alarm. A few concrete alerts, and where they typically land:

AlertTypical riskTypical confidenceWhy
SQL InjectionHighVaries — Low for a timing-based heuristic, High/Confirmed once a payload provably alters query behaviorEasy to suspect from response-time drift, hard to prove without a confirmed behavioral change
Cross Site Scripting (Reflected)HighUsually HighThe scanner can directly verify its own payload came back unescaped in the response
Absence of Anti-CSRF TokensMediumMediumStructurally suspicious, but some routes are legitimately token-free (public GETs, API-only endpoints)
Content Security Policy Header Not SetMediumHighA missing header is a simple, unambiguous check — no room for the scanner to be wrong about whether it's present
Cookie No HttpOnly FlagLowHighSame — directly observable from the response headers, nothing to infer
Information Disclosure – Suspicious CommentsInformationalLowPattern-matches source comments left in shipped HTML/JS; frequently benign

The triage rule that keeps a team from either type of failure: a High risk, Low confidence alert gets manually verified before it blocks anything — reproduce it by hand, or it doesn't go in the release-blocking queue. A High risk, High/Confirmed confidence alert blocks the merge immediately; there's no ambiguity left to resolve. When you do confirm something is a false positive, suppress it narrowly — a ZAP Alert Filter scoped to that specific plugin ID and URL regex, with a written reason — never the whole plugin globally, or you've just blinded yourself to the next real instance of that same alert on a different route.

Risk High Low Low High / Confirmed Confidence Verify by hand first e.g. a timing-based "possible SQLi" hit File now — block the merge e.g. confirmed reflected XSS, payload echoed unescaped Ignore, or batch e.g. a stray comment flagged as "possible info leak" Track, don't block e.g. confirmed missing X-Content-Type-Options header
◆ Key idea

Risk tells you how bad it would be if it's real. Confidence tells you how sure the scanner is that it is real. A severity label that collapses both into one number is throwing away exactly the information a human triager needs to decide what happens next.

Going active: full scans, and where Burp Suite Dastardly fits in CI

☺ Like you're 10: This is the part where the burglar actually tries the doors — which is exactly why you only ever do it to a house you own.

Where baseline only listens, ZAP's full/active scan actually attacks: it takes every parameter the spider discovered and throws SQL injection payloads, XSS strings, path traversal sequences, and command-injection attempts at each one, then inspects the real response for evidence something worked. zap-full-scan.py -t https://staging.internal.example.com -r zap-full-report.html runs it end to end (spider, then active scan, then report) against whatever context and auth you've wired in the same way as the baseline run.

⚠ Active scanning is attacking, not observing

An active scan sends real, malicious-shaped payloads. It can create test data, trip alerting your on-call didn't expect, and in rare cases knock over a fragile endpoint. Only ever point it at a target you're explicitly authorized to attack — staging, a dedicated test environment, or a deliberately vulnerable practice app like OWASP Juice Shop or DVWA. Never a production system without a documented, agreed authorization window, and never someone else's system at all.

Burp Suite Dastardly, PortSwigger's free scanner built specifically for CI, trades thoroughness for speed and zero configuration. It runs a fixed, non-tunable scan — a fast crawl plus a curated set of high-confidence checks — and emits JUnit XML instead of a standalone report, so a DAST finding shows up next to your unit test failures in the same CI test-results UI instead of living in a separate silo nobody opens:

docker run --rm \
  -e BURP_START_URL="https://staging.internal.example.com" \
  -e BURP_REPORT_FILE_PATH="/dastardly/dastardly-report.xml" \
  -v "$(pwd)":/dastardly \
  public.ecr.aws/portswigger/dastardly:latest

Verify that image path on PortSwigger's own Dastardly docs before wiring it into a pipeline — registry locations move. What doesn't move is the positioning: Dastardly is explicitly a lightweight complement to a full ZAP or Burp Suite Professional scan, not a replacement for one. It has no authentication-configuration surface of its own, so it's best used on public-facing surfaces or paired with an already-authenticated ZAP run, not as your only DAST pass against an app that's mostly behind login.

Tool / modeWhat it doesNeeds auth wired inSafe against prodOutput
ZAP BaselineSpider + passive scan only, no attack payloadsRecommended, not requiredYesHTML/JSON/XML report, CI exit code
ZAP Full / Active ScanFull spidering + real attack payloads against every parameterYes — or coverage collapses to the login shellNo — authorized targets onlyDetailed HTML/JSON report
ZAP API ScanSeeds from an OpenAPI/GraphQL spec, active-scans every operationYes, via token/headerNo — same active payloadsHTML/JSON report
Burp Suite DastardlyFixed, fast crawl + curated check-set, tuned for CINo built-in configTest/staging onlyJUnit XML — plugs into CI test reporting
🐢 Timmy's drill · 15 min

Run a ZAP baseline scan against a local, deliberately vulnerable target you're allowed to attack — OWASP Juice Shop or DVWA in a container — unauthenticated first, then again with a login context wired in through the Automation Framework. Compare the route count and alert count between the two runs; that gap is this chapter. Then run Burp Suite Dastardly against the same target and diff its JUnit output against ZAP's report. Same target, three different levels of thoroughness, in about the time it takes to make coffee twice.

Common exam traps in the DAST chapter

☺ Like you're 10: Most of the ways to lose points here aren't about not knowing DAST — they're about a scan that ran, looked fine, and quietly tested almost nothing.

TrapWhat actually happensWhat the challenge wants instead
Treating "I ran ZAP" as "I ran DAST"Baseline never sends an attack payload — a green baseline proves nothing about exploitabilityAn active scan (full scan, or Automation Framework activeScan job) against an authorized target
Scanning without an auth contextSpider stalls at 3–4 public routes; the report looks complete because there's nothing left to findA form or script-based auth context with a working verification check
Spider clicks the logout link mid-crawlSession dies partway through; everything scanned after that point silently reverts to unauthenticated coverageexcludePaths on the logout route, plus a verification check that would actually catch this if it happened anyway
Only running the traditional spider on a SPAA React/Vue/Angular app's client-rendered routes are structurally invisible to link-crawlingThe AJAX Spider (and an OpenAPI import for any API surface with no HTML at all)
Filing every High-risk alert with equal urgencyA High-risk, Low-confidence heuristic gets treated the same as a confirmed exploit, burying the real finding in noiseRisk × confidence triage — verify low-confidence hits by hand before they block anything
Active-scanning a target without authorizationReal attack payloads against a system you don't have explicit permission to testAuthorized targets only — staging, a documented test window, or a deliberately vulnerable practice app
🎬 At the Shift-Left Squad
🦫

Benny: Baseline scan's green, zero fails. Merging to staging.

🐢

Timmy: Green against what, though? Did it ever actually log in?

🦫

Benny: ...it hit the login page. That counts, right?

🦝

Rocky the Raccoon: That counts as testing your porch. I could still walk straight into the dashboard through three routes it never even saw.

🦊

Foxy: So the scan report and the actual app disagree about how big the app even is?

🐢

Timmy: Exactly why the auth context and the AJAX spider aren't optional extras. Skip them and you're not scanning less of the app — you're scanning almost none of it.

Once ZAP and Dastardly hand you a stack of alerts, this chapter's job is done and the next one starts: turning risk-and-confidence-triaged noise into a fixed, tracked, closed ticket is exactly what vulnerability management & triage covers. For the API-only surfaces this chapter only touched briefly, API security in depth goes further, and if the hands-on itch isn't scratched yet, Part 6 of the capstone lab is this exact workflow against the pipeline you've been building across the course. Candidates who want to go past what any CDP challenge requires — real exploitation technique, not just scan-and-report — should look at offensive security for DevSecOps next.

✓ Checkpoint

1. What's the practical difference between a ZAP baseline scan and a full/active scan, and why does that difference determine which environments each one is allowed to run against? 2. What does an authentication verification check (loggedInRegex/loggedOutRegex) actually protect a DAST run from, and what happens if you skip it? 3. Name two ZAP spidering modes beyond the traditional link-crawling spider, and say what each one exists to cover. 4. A ZAP alert comes back High risk, Low confidence. What's the correct next step, and why is filing it straight into the release-blocking queue the wrong one? 5. What does Burp Suite Dastardly trade away compared to a full ZAP scan, and what does it get in return that makes that trade worth it in CI?

Check your answers
  1. Baseline only spiders and runs passive scan rules — it never sends an attack payload, so it's safe against almost any environment, including something production-adjacent. A full/active scan sends real attack payloads (SQLi, XSS, path traversal) against every discovered parameter, so it may only run against a target you're explicitly authorized to attack, never an environment you merely have read access to.
  2. It protects the run from silently continuing on an unauthenticated session after the real one dies mid-scan (idle timeout, CSRF mismatch, a WAF flag). Skip it and ZAP has no way to notice the fallback happened — everything scanned afterward looks like public, unauthenticated coverage even though the report doesn't say so.
  3. The AJAX Spider, which drives a real headless browser to execute JavaScript and discover client-rendered routes (needed for SPAs the traditional spider can't see), and OpenAPI/GraphQL import, which seeds the sitemap directly from an API spec for surfaces with no HTML to crawl at all.
  4. Verify it by hand — reproduce it manually — before it goes anywhere near a release-blocking queue. Filing it straight through is wrong because Low confidence means the scanner itself isn't sure it's real; treating an unverified heuristic as equivalent to a confirmed exploit is exactly the kind of noise that trains a team to stop reading DAST reports.
  5. It trades away configurability — no authentication setup, a fixed non-tunable check-set, less thorough than a full ZAP or Burp Suite Professional scan. In return it gets speed (minutes, not hours) and JUnit XML output that plugs directly into existing CI test reporting, making it a fast, zero-friction complement rather than a full DAST replacement.