DAST scanner · active probe plus snapshot

CSRF Scanner

A logged-in browser attaches its cookie to any request, whoever started it. This scanner asks three questions: does a state-changing endpoint answer a request that claims to come from an attacker's site, do the cookies refuse to travel cross-site, and do the POST forms carry a token.

DASTMedium–HighAuth Weakness3 checks

The forged-origin probe

The only active test. The scanner takes every discovered endpoint that changes state and replays it with an Origin and Referer that belong to an attacker. A server that checks the origin refuses; a server that does not answers normally, and that answer is the evidence.

endpoint from discovery what the scanner sends response verdict GET /api/profile not probed: GET is not a state-changing method POST /api/transfer Origin: https://evil-attacker.com 200 OK HIGH · 0.90 DELETE /api/account Origin: https://evil-attacker.com 403 Forbidden silent PATCH /api/settings Origin: https://evil-attacker.com 204 No Content HIGH · 0.90 Also sent: Referer: https://evil-attacker.com/attack, the scan's auth headers if any, and no request body. Any status from 200 to 299 counts. Nothing else is read from the response.

1Discovery hands over its endpoint list. Only POST, PUT, PATCH and DELETE are kept, because a GET is not supposed to change anything and is not the CSRF surface.

2Each kept endpoint is called once with its own method, Origin: https://evil-attacker.com and a matching forged Referer. The body is empty. Requests are sent three at a time, 0.3 s apart.

3The scanner reads one thing: the status code. A server that validates Origin answers 403. A server that ignores it processes the request and answers normally.

4A real 2xx acceptance is a HIGH finding at confidence 0.90: the endpoint did not refuse a request from a foreign origin, and a control request with its own Origin is accepted the same way. HTML pages and success:false / error bodies do not count, and scans that authenticate with an Authorization or API-key header only are skipped (browsers never send those cross-site). A 4xx stays silent, which also means an endpoint that rejects the empty body with 400 or 422 is not reported.

Cookies and forms, read from the snapshot

The other two checks send nothing. They read the page snapshot captured in phase 1: the cookies the server set, and the HTML of the page. Both are MEDIUM, because a missing flag or a missing token is a weaker signal than a server that actually accepted a forged request.

Snapshot from phase 1 no request is sent here sid; SameSite=None theme sb-token; SameSite=Lax <form method=post> hidden: csrf_token <form method=post> hidden: lang HTML of the page, plus Set-Cookie Cookie SameSite flag if the attribute is absent or None sidSameSite=Noneflag · 0.70 theme(not set)flag · 0.70 sb-tokenSameSite=Laxsilent Strict and Lax both pass POST forms hidden field names against a token list csrf_tokenknown · silent langno token · flag names are compared case-insensitively Findings none yet MEDIUM · 0.70Cookie 'sid' lacks SameSite MEDIUM · 0.70Cookie 'theme' lacks SameSite MEDIUM · 0.85POST form lacks CSRF token sb-token and the csrf_token form: nothing reported

1The snapshot holds every Set-Cookie the first page load produced and the page's HTML. The scanner reads it; the target is not contacted for these two checks.

2Every cookie is looked at. No SameSite, or SameSite=None, is MEDIUM at 0.70, because the browser will attach that cookie to a request started from another site.

3Every <form method=post> has its hidden input names compared with nine known token names (csrf_token, _csrf, authenticity_token, xsrf-token, token and others). No match is MEDIUM at 0.85.

4The guards: Lax and Strict cookies are silent, a form with any listed token name is silent, and a GET form is never matched at all.

What the defenses do to a forged request

Why these checks exist. The attacker never sees the victim's cookie. The victim's browser attaches it by itself, so the defense has to make the request fail anyway: keep the cookie off cross-site requests, or have the server refuse a foreign Origin.

evil-attacker.com hidden form, submits itself POST /api/transfer Victim's browser logged in to the app cookie: session=… app.example.com POST /api/transfer checks: nothingchecks: Origin allowlist POST /api/transfer Cookie: session=… Origin: https://evil-attacker.com 200 OK: the transfer ran. This is what the HIGH finding means. POST /api/transfer (SameSite=Lax: no Cookie header on a cross-site POST) 401: the request arrives with no session. The cookie check goes silent. POST /api/transfer Cookie: session=… Origin: https://evil-attacker.com 403: the origin is not in the allowlist. The forged-origin probe goes silent. The scanner plays the attacker's role in step 3 by sending the same Origin itself.

1No defense. The victim opens the attacker's page. Their browser sends the POST with the session cookie, and the app cannot tell it from a click on its own button.

2SameSite=Lax or Strict. The browser leaves the cookie off a cross-site POST, so the request arrives unauthenticated and fails.

3Origin validation. The server compares Origin with its own allowlist and returns 403, which is exactly the answer the scanner's forged-origin probe looks for.

Why it matters

Why it matters

CSRF tricks a victim's browser into calling your app while the victim is logged in. The attacker's page can submit a form to /api/transfer to move funds, change the victim's email or password, delete their data, or place an order with their saved payment method. The victim sees nothing happen.

Netflix (2006) and ING Direct (2008)

At Netflix, CSRF could change a member's account email, password and DVD queue because the site lacked CSRF tokens. At ING Direct, CSRF let an attacker start fund transfers from a victim's bank account when the victim visited a malicious page while logged in.

Vulnerable vs fixed code

// BAD: no SameSite, so the browser attaches it to cross-site requests
response.cookies.set("session", token, { httpOnly: true, secure: true })

// BAD: mutation handler never looks at where the request came from
export async function POST(request: Request) {
  await transferFunds(await request.json())
  return new Response("ok")
}

Limits and what it needs

CheckReadsReports whenSeverity · confidence
Forged Origin acceptedlive response to each POST, PUT, PATCH and DELETE endpointstatus 200 to 299 with Origin: https://evil-attacker.comHigh · 0.90
Cookie SameSitecookies and the raw Set-Cookie header in the snapshotSameSite is absent or NoneMedium · 0.70
POST form tokenHTML of the snapshot pagea POST form has no hidden input whose name is a known token nameMedium · 0.85
What it deliberately stays quiet about

The probe sends an empty body, so an endpoint that validates input before it checks anything else may answer 400 and be missed. A 2xx is taken as acceptance when it is not an HTML page or a failure body and the control request succeeds too, but the scanner does not read the record back to prove that anything changed. The form and cookie checks cover the snapshot page only, not every page of the app. Credentials are optional: with them, the probe carries the auth headers.