Examples for WebApps (22)¶
This page lists every canonical WebApp custom rule example shipped with Escape (22 total), grouped by the vulnerability category it targets. Each example is a complete, anonymized rule that round-trips through the SaaS schema. Copy the rule: block, edit hostnames and users to your scan target, and ship.
Categories¶
- Seeders (3)
- Access Control (3)
- Configuration (3)
- Information Disclosure (4)
- Injection (6)
- Request Forgery (1)
- Sensitive Data (2)
Seeders (3)¶
SSO bootstrap via emailed magic link¶
Navigate to the login page, request a magic link, follow the link from the test inbox, and assert the dashboard rendered.
Magic-link auth is increasingly common (Notion, Slack invites, most B2B tools). The custom-rule action set supports this natively via click_mail_magic_link, which polls the configured test inbox and follows the most recent magic link.
When to use: Apps using passwordless / magic-link sign-in. Pair with a dedicated test mailbox configured in your scan authentication settings.
OWASP: A07:2021 Identification and Authentication Failures · CWE: CWE-1390
Severity rationale: INFO — building block, not a vulnerability rule.
Features used: seed, browser actions (goto, fill, click, click_mail_magic_link, wait_text)
rule:
id: example-webapp-seeder-magic-link
type: WEBAPP
alert:
name: SSO magic-link bootstrap (template)
context: |
Template seeder demonstrating passwordless sign-in via an
emailed magic link.
severity: INFO
category: CUSTOM
seed:
- action: goto
url: https://example.com/login
- action: fill
locator: input[name="email"]
value: tester@example.com
- action: click
locator: button[type="submit"]
- action: click_mail_magic_link
email_address: tester.escape@scan.escape.tech
timeout: 60
- action: wait_text
value: Sign out
timeout: 10
detect:
- if: page_text
contains: Sign out
References:
Two-factor (TOTP) bootstrap during sign-in¶
Sign in with email + password, then complete the TOTP challenge using fill_totp against the configured authenticator secret.
Apps that gate sign-in behind TOTP multi-factor frequently break headless scans. The action set ships fill_totp (HOTP-secret based) and fill_mail_totp (mailbox-delivered code) so the seeder can complete the challenge without human intervention.
When to use: Apps with TOTP MFA enabled for the test account. Configure the HOTP secret in your scan authentication settings; the seeder will compute the current 6-digit code automatically.
OWASP: A07:2021 Identification and Authentication Failures · CWE: CWE-308
Severity rationale: INFO — building block, not a vulnerability rule.
Features used: seed, browser actions (goto, fill, click, fill_totp, wait_text)
rule:
id: example-webapp-seeder-totp
type: WEBAPP
alert:
name: TOTP MFA bootstrap (template)
context: |
Template seeder demonstrating sign-in through a TOTP MFA
challenge using the `fill_totp` action.
severity: INFO
category: CUSTOM
seed:
- action: goto
url: https://example.com/login
- action: fill
locator: input[name="email"]
value: tester@example.com
- action: fill
locator: input[name="password"]
value: tester-password
- action: click
locator: button[type="submit"]
- action: fill_totp
locator: input[name="otp"]
secret: JBSWY3DPEHPK3PXP
- action: click
locator: button[type="submit"]
- action: wait_text
value: Sign out
timeout: 10
detect:
- if: page_text
contains: Sign out
References:
Authenticated session bootstrap via form¶
A seed-only template that performs a full email + password sign-in via the browser form. Subsequent rules can stack on top with their own detect and additional seed actions.
The recurring scaffolding for any authenticated WebApp rule is the same: navigate to the login page, fill the form, submit, take a screenshot for evidence. This file documents the canonical shape so other rules can reference it via examples/webapp/_seeders/auth-bootstrap-form.yaml.
The detector here intentionally only checks that the page rendered "Sign out" — it is the cheapest signal that the session was established.
When to use: As a starting template when authoring a new authenticated WebApp rule. Copy the seed block, append your own seed actions and detector.
OWASP: N/A — building block, not a vulnerability rule · CWE: N/A
Severity rationale: INFO — this is a building block, not a vulnerability rule.
Features used: seed, browser actions (goto, fill, click, wait_text), page_text detector
rule:
id: example-webapp-seeder-auth-form
type: WEBAPP
alert:
name: Authenticated session bootstrap (template)
context: |
Template seeder demonstrating an email + password sign-in
via the browser form. Stack additional seed actions and a
real detector on top to author a complete rule.
severity: INFO
category: CUSTOM
seed:
- action: goto
url: https://example.com/login
- action: fill
locator: input[name="email"]
value: tester@example.com
- action: fill
locator: input[name="password"]
value: tester-password
- action: click
locator: button[type="submit"]
- action: wait_text
value: Sign out
timeout: 10
detect:
- if: page_text
contains: Sign out
References:
Access Control (3)¶
Anonymous /admin route reachable in browser¶
Navigate to /admin and assert the page loads with admin markers — the panel is reachable without authentication.
The browser-side counterpart to examples/api/access_control/public-admin-route.yaml. The seeder hits /admin directly. The detector requires a 200 status AND a page text marker (Sign out, User Management, Admin panel) so a generic redirect to a login page does not fire.
When to use: Any web app with an admin surface. Combine with the API rule for full coverage.
OWASP: A01:2021 Broken Access Control · CWE: CWE-862
Severity rationale: HIGH — anonymous admin access is reliably exploitable.
Features used: seed, detect, page_status_code, page_text contains, logical or
rule:
id: example-webapp-admin-route-anonymous
type: WEBAPP
alert:
name: Anonymous /admin route reachable
context: |
`/admin` returned a 200 page containing administrative UI
markers without requiring authentication.
severity: HIGH
category: ACCESS_CONTROL
seed:
- action: goto
url: https://example.com/admin
detect:
- if: page_status_code
is: 200
- if: or
or:
- if: page_text
contains: Sign out
- if: page_text
contains: User Management
- if: page_text
contains: Admin panel
References:
- https://owasp.org/Top10/A01_2021-Broken_Access_Control/
- https://cwe.mitre.org/data/definitions/862.html
Plaintext credentials persisted in localStorage¶
Sign in via the form, then assert no localStorage entry contains the typed password — credentials must never be persisted client-side.
Even short-lived persistence of plaintext credentials in localStorage is a finding: any XSS on any page of the same origin can read them.
The seeder logs in with a marker password, then the js_assertion detector iterates localStorage keys and returns true if the marker substring appears anywhere.
When to use: Any single-page app that exposes a login form. Adjust the selectors and marker password to your form layout.
OWASP: A02:2021 Cryptographic Failures · CWE: CWE-312
Severity rationale: HIGH — combined with any same-origin XSS, this is direct credential theft.
Features used: seed, detect, browser actions (goto, fill, click), js_assertion
rule:
id: example-webapp-localstorage-credentials
type: WEBAPP
alert:
name: Plaintext credentials persisted in localStorage
context: |
The login form persisted the typed password in
`localStorage`, exposing credentials to any same-origin XSS.
severity: HIGH
category: ACCESS_CONTROL
seed:
- action: goto
url: https://example.com/login
- action: fill
locator: input[name="email"]
value: tester@example.com
- action: fill
locator: input[name="password"]
value: escape-marker-password-9213
- action: click
locator: button[type="submit"]
detect:
- if: js_assertion
command: |
return Object.keys(localStorage).some(k => (localStorage.getItem(k) || '').includes('escape-marker-password-9213'));
References:
- https://owasp.org/Top10/A02_2021-Cryptographic_Failures/
- https://cwe.mitre.org/data/definitions/312.html
Sensitive file exposure (.env, .git, backups) reachable in browser¶
Navigate to a curated list of sensitive paths (.env, .git/config, backup.zip) and alert if any of them returns a 200 page whose DOM contains an OR-list of well-known credential markers.
The webapp counterpart to the API exposed-file rules. Adapted from a real anonymized customer rule (see CSV column 9 for the JSON-encoded pattern) — the seeder loops over a variety of sensitive paths and the detector ORs the most common high-signal credential markers.
When to use: Any web property that may host arbitrary static files (CMS deployments, marketing sites, single-page apps with a static bucket). Run after every deployment.
OWASP: A05:2021 Security Misconfiguration · CWE: CWE-200
Severity rationale: HIGH — the markers chosen are extremely high-signal and a hit almost always means a leaked credential.
Features used: seed, detect, browser action goto, page_status_code, page_text contains, logical and, logical or
rule:
id: example-webapp-sensitive-file-exposure
type: WEBAPP
alert:
name: Sensitive file reachable in browser
context: |
A request to a known sensitive path (`.env`, `.git/config`,
backup archive, ...) returned a 200 page containing a
credential / configuration marker.
severity: HIGH
category: ACCESS_CONTROL
seed:
- action: goto
url: https://example.com/.env
timeout: 10
- action: goto
url: https://example.com/.env.local
timeout: 10
- action: goto
url: https://example.com/.env.production
timeout: 10
- action: goto
url: https://example.com/.git/config
timeout: 10
- action: goto
url: https://example.com/backup.zip
timeout: 10
detect:
- if: and
and:
- if: page_status_code
is: 200
- if: or
or:
- if: page_text
contains: DB_PASSWORD
- if: page_text
contains: DATABASE_URL
- if: page_text
contains: AWS_ACCESS_KEY_ID
- if: page_text
contains: '[core]'
- if: page_text
contains: PRIVATE KEY
References:
- https://owasp.org/Top10/A05_2021-Security_Misconfiguration/
- https://cwe.mitre.org/data/definitions/200.html
Configuration (3)¶
Missing Content-Security-Policy header¶
Navigate to the home page and alert if the response did not carry a Content-Security-Policy header — the browser is left to enforce nothing beyond defaults.
A meaningful CSP is the single highest-leverage browser-side XSS control: it blocks inline scripts, third-party JS sources, and prevents eval-style sinks unless explicitly allowlisted. Pages without one fall back to the default permissive policy.
The detector triggers on any successful HTML page and asserts the Content-Security-Policy header is present (any value). Tighten the assertion if your minimum required policy includes specific directives.
When to use: Any web app rendering HTML. CSP is essentially mandatory on customer-facing properties.
OWASP: A05:2021 Security Misconfiguration · CWE: CWE-1021
Severity rationale: MEDIUM — CSP absence does not directly compromise the page, but its presence is the strongest browser-side XSS mitigation available.
Features used: seed, detect, browser action goto, header detector, logical not
rule:
id: example-webapp-missing-csp-header
type: WEBAPP
alert:
name: Missing Content-Security-Policy header
context: |
The home page response did not carry a
`Content-Security-Policy` header, leaving the browser to
enforce no script-source policy beyond defaults.
severity: MEDIUM
category: CONFIGURATION
seed:
- action: goto
url: https://example.com/
detect:
- if: not
not:
if: header
key:
is: Content-Security-Policy
References:
- https://owasp.org/Top10/A05_2021-Security_Misconfiguration/
- https://owasp.org/www-project-secure-headers/
- https://cwe.mitre.org/data/definitions/1021.html
Non-production build exposed in production¶
Navigate to the home page and alert when the rendered DOM contains a staging / dev / qa build banner — a non-production build was promoted to the production hostname.
Non-production builds typically include verbose logging, debug routes, mock authentication, and feature flags that bypass real authorization. When a staging build accidentally lands on production.example.com, every weakness becomes internet-facing.
The detector OR's three common banner strings ("STAGING BUILD", "DEV BUILD", "QA ENVIRONMENT") that build pipelines often inject into the page header. Tailor to your build's actual marker.
When to use: Any web app with a staging/QA pipeline. Run after every deploy as a deployment-correctness gate.
OWASP: A05:2021 Security Misconfiguration · CWE: CWE-1188
Severity rationale: HIGH — non-prod builds bring debug routes and weakened auth that typically chain into deeper compromise.
Features used: seed, detect, page_text, logical or
rule:
id: example-webapp-non-prod-build-banner
type: WEBAPP
alert:
name: Non-production build banner on production host
context: |
The production page rendered a staging / dev / QA build
banner, indicating a non-production build was promoted to
the production hostname.
severity: HIGH
category: CONFIGURATION
seed:
- action: goto
url: https://example.com/
detect:
- if: or
or:
- if: page_text
contains: STAGING BUILD
- if: page_text
contains: DEV BUILD
- if: page_text
contains: QA ENVIRONMENT
References:
- https://owasp.org/Top10/A05_2021-Security_Misconfiguration/
- https://cwe.mitre.org/data/definitions/1188.html
Subdomain takeover via parking-page marker¶
Visit a subdomain and flag if the page renders a known third-party "no such app" or parking marker, indicating an unclaimed external resource that an attacker could register.
Subdomain takeover happens when a CNAME points to a third-party service (S3, Heroku, GitHub Pages, Azure, …) that no longer hosts a resource for that name. An attacker who registers the resource on the third-party service inherits the subdomain and can host arbitrary content under your origin.
The deterministic detection is to fetch the subdomain and look for the platform-specific "this resource is not configured" marker. The marker list below covers the most common providers; extend or with the providers you actually use.
When to use: Run against every subdomain you own. Particularly important after decommissioning a marketing campaign, microsite, or staging environment, when CNAME records often outlive the underlying resource.
OWASP: A05:2021 Security Misconfiguration · CWE: CWE-1104
Severity rationale: HIGH — successful takeover lets an attacker host phishing or malware pages under a trusted hostname, defeating SPF / DKIM / DMARC trust chains and bypassing user suspicion.
Features used: seed, detect, browser action goto, page_text detector, logical or
rule:
id: example-webapp-subdomain-takeover-marker
type: WEBAPP
alert:
name: Possible subdomain takeover
context: |
The subdomain renders a known third-party parking marker,
indicating an unclaimed external resource that an attacker could
register and use to host arbitrary content under your origin.
severity: HIGH
category: CONFIGURATION
seed:
- action: goto
url: https://example.com/
detect:
- if: or
or:
- if: page_text
contains: NoSuchBucket
- if: page_text
contains: There is no app configured at that hostname
- if: page_text
contains: There isn't a GitHub Pages site here
- if: page_text
contains: The specified bucket does not exist
- if: page_text
contains: Sorry, this shop is currently unavailable
References:
- https://owasp.org/Top10/A05_2021-Security_Misconfiguration/
- https://github.com/EdOverflow/can-i-take-over-xyz
Information Disclosure (4)¶
Spring Boot actuator UI publicly exposed¶
Navigate to /actuator and assert the page returns a successful response containing the health index — the actuator landing page is reachable without authentication.
The Spring Boot actuator landing page lists every enabled endpoint. Confirming the index is reachable is enough to know that more dangerous endpoints (/actuator/env, /actuator/heapdump) are likely also accessible.
See examples/api/information_disclosure/spring-boot-actuator-env.yaml for the API-side counterpart that probes the heavy endpoints directly.
When to use: Any Spring Boot app reachable via a browser. Combine with the API rule for full coverage.
OWASP: A05:2021 Security Misconfiguration · CWE: CWE-200
Severity rationale: MEDIUM as standalone exposure (the index alone leaks little); HIGH once paired with the env / heapdump probes.
Features used: seed, detect, page_status_code, page_text contains
rule:
id: example-webapp-exposed-actuator-ui
type: WEBAPP
alert:
name: Spring Boot actuator UI exposed
context: |
The `/actuator` landing page is publicly reachable and lists
enabled actuator endpoints.
severity: MEDIUM
category: INFORMATION_DISCLOSURE
seed:
- action: goto
url: https://example.com/actuator
detect:
- if: page_status_code
is: 200
- if: page_text
contains: health
References:
- https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html
- https://cwe.mitre.org/data/definitions/200.html
OAuth client secret leaked via source map¶
Navigate to a JavaScript bundle source map and alert when the body contains client_secret or clientSecret — the OAuth client secret should never reach the browser.
OAuth flows that put client_secret in front-end source are misconfigured by definition: confidential clients run only in server-side processes. When the secret leaks via a source map, any attacker can impersonate the OAuth client and request tokens with its identity.
When to use: Any front-end that integrates a third-party OAuth provider (Google, GitHub, LinkedIn, Slack, Microsoft). Adjust the seed URLs to your bundle layout.
OWASP: A05:2021 Security Misconfiguration · CWE: CWE-540
Severity rationale: HIGH — leaked client secrets enable token impersonation and sometimes phishing pages that look identical to the legitimate OAuth consent screen.
Features used: seed, detect, browser action goto, page_text contains, logical or
rule:
id: example-webapp-source-map-oauth-secret
type: WEBAPP
alert:
name: OAuth client secret leaked via source map
context: |
A production source map contains a `client_secret` value,
indicating the OAuth confidential client secret has been
shipped to the browser.
severity: HIGH
category: INFORMATION_DISCLOSURE
seed:
- action: goto
url: https://example.com/static/js/main.js.map
- action: goto
url: https://example.com/assets/index.js.map
detect:
- if: or
or:
- if: page_text
contains: client_secret
- if: page_text
contains: clientSecret
References:
- https://datatracker.ietf.org/doc/html/rfc6749#section-2.1
- https://cwe.mitre.org/data/definitions/540.html
Firebase config leaked via source map¶
Probe /static/js/main.js.map and assert the body contains firebaseConfig — production source maps are downloadable and leak the embedded Firebase configuration.
Source maps deployed to production give attackers your pre-minification source. Common leaks include:
firebaseConfig(API key, project id, app id) — used to brute-force project paths and email enumeration via the Identity Toolkit;- hardcoded OAuth client secrets;
- private API tokens passed to JS at build time.
The detector requires both a successful response AND a body containing the canonical firebaseConfig marker.
When to use: Any single-page app built with React / Vue / Angular and deployed via a static-site pipeline. Tighten the seed paths to the actual bundle filenames your build emits.
OWASP: A05:2021 Security Misconfiguration · CWE: CWE-540
Severity rationale: HIGH — Firebase project credentials are reliably weaponized into account enumeration and sometimes data exfiltration via over-permissive Firestore rules.
Features used: seed, detect, browser action goto, page_text contains
rule:
id: example-webapp-source-map-firebase-config
type: WEBAPP
alert:
name: Firebase config leaked via source map
context: |
A production JavaScript source map exposed the embedded
`firebaseConfig` object, leaking the project's API key and
identifier.
severity: HIGH
category: INFORMATION_DISCLOSURE
seed:
- action: goto
url: https://example.com/static/js/main.js.map
detect:
- if: page_text
contains: firebaseConfig
- if: page_text
contains: apiKey
References:
- https://owasp.org/Top10/A05_2021-Security_Misconfiguration/
- https://cwe.mitre.org/data/definitions/540.html
Verbose error page leaks server paths and stack trace¶
Navigate to a non-existent path that triggers a server error and alert if the page text exposes a stack trace, framework banner, or absolute filesystem path.
Error pages are an easy-but-useful information disclosure: they leak the framework (Werkzeug, Whitelabel Error Page, RailsErrorBacktrace), absolute paths (/var/www/app/...), and sometimes the exact line of code that failed.
The seeder asks for a clearly-non-existent path to force an error. The detector OR's three high-confidence markers.
When to use: Any web application. Particularly important on staging deployments accidentally promoted to production.
OWASP: A09:2021 Security Logging and Monitoring Failures · CWE: CWE-209
Severity rationale: LOW — exposure aids reconnaissance but is rarely directly exploitable.
Features used: seed, detect, browser action goto, page_text contains, logical or
rule:
id: example-webapp-verbose-error-stack-trace
type: WEBAPP
alert:
name: Verbose error page exposes server internals
context: |
A non-existent path returned a verbose error page exposing
a framework banner, stack trace, or absolute filesystem
path.
severity: LOW
category: INFORMATION_DISCLOSURE
seed:
- action: goto
url: https://example.com/__escape_does_not_exist__
detect:
- if: or
or:
- if: page_text
contains: Whitelabel Error Page
- if: page_text
contains: Werkzeug
- if: page_text
contains: Traceback (most recent call last)
- if: page_text
contains: at com.
References:
- https://owasp.org/Top10/A09_2021-Security_Logging_and_Monitoring_Failures/
- https://cwe.mitre.org/data/definitions/209.html
Injection (6)¶
HTML injection in search input field¶
Submit <u>escape-html</u> in a search input and assert a rendered <u> element appears in the DOM via a CSS selector — proving raw HTML reaches the page.
HTML injection is the precursor to XSS: the page renders user-supplied tags but the specific tag does not execute script. It still enables defacement, phishing overlays, and sometimes link injection that bypasses link-only filters.
The deterministic test searches for the injected <u> element via the page_selector detector. A match means the tag was parsed by the browser as real HTML.
When to use: Pages with search forms or any user-supplied text rendered inline. Pair with dialog-popup-xss.yaml to detect actual script execution as a follow-up.
OWASP: A03:2021 Injection · CWE: CWE-79
Severity rationale: MEDIUM — direct injection of arbitrary HTML, but limited to non-script tags here.
Features used: seed, detect, browser actions (goto, fill, click), page_selector detector
rule:
id: example-webapp-html-injection-search
type: WEBAPP
alert:
name: HTML injection in search input
context: |
The search input rendered an injected `<u>` element verbatim
in the DOM, indicating user-supplied HTML is parsed and
attached to the page.
severity: MEDIUM
category: INJECTION
seed:
- action: goto
url: https://example.com/
- action: fill
locator: input[type="search"]
value: <u>escape-html</u>
- action: click
locator: button[type="submit"]
detect:
- if: page_selector
contains: u
- if: page_text
contains: escape-html
References:
- https://owasp.org/www-community/attacks/Code_Injection
- https://cwe.mitre.org/data/definitions/79.html
DOM XSS via document.cookie reflection¶
Navigate to a page with an XSS payload in the URL fragment, then assert via JS that document.cookie was modified — proving the payload executed in the DOM.
DOM XSS is rendering-layer XSS that the server never sees: the page reads location.hash (or another DOM source) and writes it to innerHTML without sanitization. Server-side reflection detection misses this entirely.
The deterministic test loads a URL with an attacker-controlled fragment that runs document.cookie='escape-dom-xss=1'. The js_assertion detector returns true if the cookie was set, proving the script executed in the DOM context.
When to use: Any single-page app or page that consumes URL fragments. The technique works equally for query parameter sinks if you adjust the URL accordingly.
OWASP: A03:2021 Injection (DOM XSS) · CWE: CWE-79
Severity rationale: HIGH — same impact as reflected XSS; harder to detect with server-side scanning, which is why this rule matters.
Features used: seed, detect, browser action goto, js_assertion detector
rule:
id: example-webapp-dom-xss-cookie-marker
type: WEBAPP
alert:
name: DOM XSS via URL fragment
context: |
The page set a marker cookie when navigated to with an XSS
payload in the URL fragment, indicating a DOM-based XSS sink.
severity: HIGH
category: INJECTION
seed:
- action: goto
url: https://example.com/page#<img src=x onerror=document.cookie='escape-dom-xss=1'>
detect:
- if: js_assertion
command: return document.cookie.indexOf('escape-dom-xss=1') !== -1;
References:
- https://owasp.org/www-community/attacks/DOM_Based_XSS
- https://cwe.mitre.org/data/definitions/79.html
XSS detected via alert() dialog popup¶
Navigate with <script>alert("escape-xss")</script> in the URL and assert a dialog appeared whose message contains the marker — fires regardless of where the payload was reflected.
The dialog detector is the most ergonomic XSS proof in headless-browser scanning: it captures any alert(), confirm(), or prompt() dialog the page emits during navigation. If the captured message contains your marker, you have execution.
Unlike the page_text detector, this rule fires regardless of where in the DOM the payload was rendered (SVG, attribute handler, embedded iframe, ...).
When to use: Any web target. This is the closest thing to a universal XSS test for headless-browser scanning.
OWASP: A03:2021 Injection (XSS) · CWE: CWE-79
Severity rationale: HIGH — direct script execution.
Features used: seed, detect, browser action goto, dialog.message detector
rule:
id: example-webapp-dialog-popup-xss
type: WEBAPP
alert:
name: XSS detected via dialog popup
context: |
The page emitted an `alert()` dialog containing the unique
marker `escape-xss`, proving script execution.
severity: HIGH
category: INJECTION
seed:
- action: goto
url: https://example.com/?q=%3Cscript%3Ealert(%22escape-xss%22)%3C%2Fscript%3E
detect:
- if: dialog.message
contains: escape-xss
References:
Reflected XSS in search page¶
Navigate to a search page with an XSS payload in the query string and flag if the payload is rendered verbatim into the page text — a deterministic signal of reflected XSS.
Reflected XSS happens when user input is echoed into the HTML response without escaping. The cleanest deterministic test is to submit a uniquely-identifiable payload via the URL and look for that exact string in the rendered page, which proves both reflection and lack of escaping.
The seeder navigates the headless browser to a search URL containing <script>console.log("xss")</script>. The detector asserts that string is present in the rendered page text. If it is, the payload survived the server-side escape filter.
When to use: Public search / filter pages that take user-controlled query parameters and reflect them in the rendered HTML. Adjust the URL, the parameter name, and the marker payload to your target.
OWASP: A03:2021 Injection (XSS) · CWE: CWE-79
Severity rationale: HIGH — reflected XSS allows session hijacking, account takeover, and credential theft against any authenticated visitor who follows a crafted link.
Features used: seed, detect, browser action goto, page_text detector
rule:
id: example-webapp-reflected-xss-search
type: WEBAPP
alert:
name: Reflected XSS in search query
context: |
A unique XSS payload submitted via the search query parameter was
rendered verbatim in the page HTML, indicating reflected
cross-site scripting.
severity: HIGH
category: INJECTION
seed:
- action: goto
url: https://example.com/search?q=%3Cscript%3Econsole.log(%22xss%22)%3C%2Fscript%3E
detect:
- if: page_text
contains: <script>console.log("xss")</script>
References:
- https://owasp.org/Top10/A03_2021-Injection/
- https://cwe.mitre.org/data/definitions/79.html
- https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html
Stored XSS — payload survives a form submit + reload¶
Submit an XSS payload through a form, navigate back to the page, and alert if the rendered DOM contains the payload verbatim — a deterministic stored-XSS test.
Stored XSS is more dangerous than reflected XSS because it persists for every visitor. The deterministic test:
- Navigate to the form page.
- Fill the textarea with a unique XSS payload.
- Click submit.
- Navigate back to the same page (forcing a fresh fetch).
- Assert the payload appears verbatim in the page text.
A 1:1 reflection on a fresh load proves the payload was stored server-side and is rendered without escaping.
When to use: Any web app with user-generated content (comments, profiles, titles, descriptions, board names). Adapt the locator selectors and submit URL to your form layout.
OWASP: A03:2021 Injection (XSS) · CWE: CWE-79
Severity rationale: HIGH — stored XSS hits every visitor of the affected page; it is one of the most reliably weaponized web vulnerabilities.
Features used: seed, detect, browser actions (goto, fill, click), page_text detector
rule:
id: example-webapp-stored-xss-form-roundtrip
type: WEBAPP
alert:
name: Stored XSS in user-generated form field
context: |
A unique XSS payload submitted through a form was rendered
verbatim in the page after a fresh navigation, indicating
the payload was stored server-side and the rendering layer
does not escape user input.
severity: HIGH
category: INJECTION
seed:
- action: goto
url: https://example.com/comments/new
- action: fill
locator: textarea[name="comment"]
value: <script>console.log("escape-stored-xss")</script>
- action: click
locator: button[type="submit"]
- action: goto
url: https://example.com/comments
detect:
- if: page_text
contains: <script>console.log("escape-stored-xss")</script>
References:
- https://owasp.org/Top10/A03_2021-Injection/
- https://cwe.mitre.org/data/definitions/79.html
- https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html
ASP.NET stack trace via malformed input¶
Navigate to a search-style endpoint with a malformed URL parameter and assert the rendered page exposes an ASP.NET stack trace, indicating customErrors is not configured.
ASP.NET applications with customErrors mode="Off" (or default in development) render a yellow stack-trace page on unhandled exceptions. The page exposes:
- the .NET version,
- file paths,
- the request handler chain,
- frequently DB connection strings via
SqlException.
The deterministic test fetches /?id=null and looks for the canonical Server Error in '/' Application heading.
When to use: Any ASP.NET (Framework or Core) application. Particularly important in production after refactors that may have re-enabled developer mode.
OWASP: A05:2021 Security Misconfiguration · CWE: CWE-209
Severity rationale: LOW — exposure aids reconnaissance; elevate when stack traces contain credentials.
Features used: seed, detect, browser action goto, page_text contains
rule:
id: example-webapp-asp-net-stack-trace
type: WEBAPP
alert:
name: ASP.NET stack trace exposed
context: |
A malformed request rendered an ASP.NET error page exposing
stack trace, server paths, and framework version.
severity: LOW
category: INJECTION
seed:
- action: goto
url: https://example.com/?id=null
detect:
- if: page_text
contains: Server Error in '/' Application
- if: page_text
contains: 'Stack Trace:'
References:
- https://owasp.org/Top10/A05_2021-Security_Misconfiguration/
- https://learn.microsoft.com/en-us/troubleshoot/developer/webapps/aspnet/development/custom-error-page-displayed
- https://cwe.mitre.org/data/definitions/209.html
Request Forgery (1)¶
POST form rendered without CSRF token¶
Navigate to a form page and assert at least one <form method="post"> carries a hidden CSRF token input — its absence means the form is exploitable cross-origin.
Server-rendered apps embed CSRF tokens as hidden inputs in <form method="post">. The deterministic test is to load a known form page and use js_assertion to scan all POST forms for at least one hidden input whose name implies a CSRF token.
The detector returns true when no form passes the check — so the rule alerts on the absence.
When to use: Any server-rendered web app with <form method="post"> POSTs. SPAs that POST via fetch use a different mechanism — see the API rule examples/api/access_control/missing-csrf-token-on-state-change.yaml.
OWASP: A01:2021 Broken Access Control (CSRF) · CWE: CWE-352
Severity rationale: MEDIUM — exploitation requires luring a logged-in victim, but the impact is direct.
Features used: seed, detect, js_assertion
rule:
id: example-webapp-csrf-on-post-form
type: WEBAPP
alert:
name: POST form missing CSRF token
context: |
A POST form on the page does not carry a hidden CSRF token
input, leaving it exploitable from any origin a logged-in
victim visits.
severity: MEDIUM
category: REQUEST_FORGERY
seed:
- action: goto
url: https://example.com/account/settings
detect:
- if: js_assertion
command: |
return Array.from(document.querySelectorAll('form[method="post" i]'))
.some(f => !f.querySelector('input[type="hidden"][name*="csrf" i],input[type="hidden"][name*="token" i]'));
References:
- https://owasp.org/Top10/A01_2021-Broken_Access_Control/
- https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html
- https://cwe.mitre.org/data/definitions/352.html
Sensitive Data (2)¶
Session token persisted in browser URL¶
Sign in, navigate to the dashboard, and alert via js_assertion if window.location.search contains a JWT-shaped value — the session token is being kept in the URL.
Tokens in URLs are persisted in browser history, leak via Referer to third-party scripts on the page, and are recorded by intermediate proxies. They belong in Authorization headers or HttpOnly cookies.
The deterministic test logs in via the form, navigates forward, and asserts the URL search parameters contain no JWT-shaped value.
When to use: Any web app where the auth flow may fall back to URL-encoded tokens (legacy SSO callbacks, magic-link flows).
OWASP: A02:2021 Cryptographic Failures · CWE: CWE-598
Severity rationale: MEDIUM — exposure surface is broad but exploitation requires reading the leaked logs / Referer.
Features used: seed, detect, browser actions (goto, fill, click), js_assertion
rule:
id: example-webapp-session-token-in-url
type: WEBAPP
alert:
name: Session token persisted in URL
context: |
After login, `window.location.search` contains a JWT-shaped
value, indicating the session token is being persisted in
the URL.
severity: MEDIUM
category: SENSITIVE_DATA
seed:
- action: goto
url: https://example.com/login
- action: fill
locator: input[name="email"]
value: tester@example.com
- action: fill
locator: input[name="password"]
value: tester-password
- action: click
locator: button[type="submit"]
detect:
- if: js_assertion
command: |
return /eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/.test(window.location.search);
References:
- https://datatracker.ietf.org/doc/html/rfc6750#section-5.3
- https://cwe.mitre.org/data/definitions/598.html
PII (email addresses) leaked in unauthenticated public page¶
Navigate to a public listing page and assert via js_assertion that the rendered DOM contains no email addresses — public pages should not expose user emails.
The webapp counterpart to examples/api/sensitive_data/pii-email-in-response.yaml. Public marketing / listing pages frequently leak member emails through Mongoose / Sequelize default serializers when the backend forgets the field allow-list.
The detector returns true when at least one email-shaped string appears in the page text.
When to use: Any public-facing page that renders user-generated content (forums, member directories, comment threads, profiles).
OWASP: A04:2021 Insecure Design · CWE: CWE-359
Severity rationale: MEDIUM — leaked email lists fuel phishing and credential stuffing.
Features used: seed, detect, js_assertion
rule:
id: example-webapp-pii-emails-in-dom
type: WEBAPP
alert:
name: User emails leaked in public page
context: |
The public page contains email addresses in its rendered DOM,
indicating user PII is exposed to anonymous visitors.
severity: MEDIUM
category: SENSITIVE_DATA
seed:
- action: goto
url: https://example.com/members
detect:
- if: js_assertion
command: |
return /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/.test(document.body.innerText);
References: