#!/usr/bin/env python3
"""
CSRF Lab - Cross-Site Request Forgery
The Hacker's Arsenal - Lesson 8
Practice CSRF attacks:
1. Transfer money (no protection)
2. Change email (weak CSRF token)
3. Transfer money (referer check bypass)
4. Change password (token in cookie)
5. Transfer via GET (bad design)
Run this script and open http://localhost:8888 in your browser.
WARNING: This code is INTENTIONALLY VULNERABLE for educational purposes.
"""
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse, parse_qs
import uuid
# User storage
USERS = {
'alice': {'password': 'alice123', 'balance': 1000, 'email': 'alice@example.com'},
'bob': {'password': 'bob123', 'balance': 500, 'email': 'bob@example.com'},
'admin': {'password': 'admin123', 'balance': 10000, 'email': 'admin@company.com'},
}
# Session storage
SESSIONS = {}
# CSRF token storage
CSRF_TOKENS = {}
HTML_TEMPLATE = '''<!DOCTYPE html>
<html>
<head>
<title>CSRF Lab - SecureBank</title>
<style>
body {{ font-family: Arial; max-width: 900px; margin: 50px auto; background: #1a1a1a; color: #e0e0e0; }}
h1 {{ color: #6EFF24; }}
h2 {{ color: #6EFF24; font-size: 1.3em; }}
.section {{ background: #252525; padding: 20px; margin: 20px 0; border-radius: 8px; border-left: 3px solid #6EFF24; }}
input {{ padding: 10px; margin: 5px; background: #333; border: 1px solid #6EFF24; color: white; width: 200px; }}
button {{ background: #6EFF24; color: black; padding: 10px 20px; border: none; cursor: pointer; font-weight: bold; margin: 5px; }}
.error {{ color: #ff4444; background: #331111; padding: 10px; border-radius: 5px; margin: 10px 0; }}
.success {{ color: #44ff44; background: #113311; padding: 10px; border-radius: 5px; margin: 10px 0; }}
.warning {{ color: #ffaa44; background: #332211; padding: 10px; border-radius: 5px; margin: 10px 0; }}
.hint {{ color: #888; font-size: 0.9em; margin-top: 10px; }}
code {{ background: #333; padding: 2px 6px; color: #6EFF24; display: inline-block; }}
.info {{ background: #1a2a3a; padding: 15px; border-radius: 5px; margin: 10px 0; }}
.dashboard {{ background: #1a3a1a; padding: 15px; border-radius: 5px; }}
pre {{ background: #0a0a0a; padding: 15px; border-radius: 5px; overflow-x: auto; }}
</style>
</head>
<body>
<h1>SecureBank - CSRF Lab</h1>
<div class="section">
<h2>Account Status</h2>
{account_status}
</div>
<div class="section">
<h2>Login</h2>
<form action="/login" method="POST">
<input type="text" name="username" placeholder="Username"><br>
<input type="password" name="password" placeholder="Password"><br>
<button type="submit">Login</button>
</form>
{login_result}
<p class="hint">Users: alice/alice123, bob/bob123, admin/admin123</p>
</div>
<div class="section">
<h2>1. Transfer Money (No CSRF Protection)</h2>
<form action="/transfer1" method="POST">
<input type="text" name="to" placeholder="Recipient"><br>
<input type="text" name="amount" placeholder="Amount"><br>
<button type="submit">Transfer</button>
</form>
{transfer1_result}
<p class="hint">Hint: No CSRF token. Can be attacked from any website.</p>
</div>
<div class="section">
<h2>2. Change Email (Weak CSRF Protection)</h2>
<form action="/change_email" method="POST">
<input type="text" name="email" placeholder="New Email"><br>
<input type="hidden" name="csrf_token" value="{csrf_token}">
<button type="submit">Update Email</button>
</form>
{email_result}
<p class="hint">Hint: There's a CSRF token, but is it validated properly?</p>
</div>
<div class="section">
<h2>3. Transfer Money (Referer Check)</h2>
<form action="/transfer2" method="POST">
<input type="text" name="to" placeholder="Recipient"><br>
<input type="text" name="amount" placeholder="Amount"><br>
<button type="submit">Transfer</button>
</form>
{transfer2_result}
<p class="hint">Hint: Server checks Referer header. Can you bypass it?</p>
</div>
<div class="section">
<h2>4. Change Password (Token in Cookie)</h2>
<form action="/change_password" method="POST">
<input type="password" name="new_password" placeholder="New Password"><br>
<button type="submit">Change Password</button>
</form>
{password_result}
<p class="hint">Hint: CSRF token is stored in a cookie and compared to form value</p>
</div>
<div class="section">
<h2>5. Transfer via GET (Bad Design)</h2>
<p>Quick transfer: <a href="/transfer_get?to=bob&amount=10">Send $10 to Bob</a></p>
{transfer_get_result}
<p class="hint">Hint: State-changing actions via GET are always vulnerable</p>
</div>
<div class="section" style="border-left-color: #ff4444;">
<h2>Challenge: CSRF Attack Pages</h2>
<p>Create HTML pages that exploit each vulnerability when a logged-in user visits them.</p>
<ol>
<li>Auto-submitting form for transfer1</li>
<li>Bypass the weak CSRF token on change_email</li>
<li>Bypass the Referer check on transfer2</li>
<li>Exploit the GET-based transfer</li>
</ol>
</div>
</body>
</html>'''
class CSRFLabHandler(BaseHTTPRequestHandler):
def get_session_user(self):
cookies = self.headers.get('Cookie', '')
for cookie in cookies.split(';'):
cookie = cookie.strip()
if cookie.startswith('session='):
session_id = cookie.split('=')[1]
if session_id in SESSIONS:
return SESSIONS[session_id]
return None
def do_GET(self):
parsed = urlparse(self.path)
params = parse_qs(parsed.query)
user = self.get_session_user()
transfer_get_result = ''
if parsed.path == '/transfer_get' and user:
# VULNERABLE: State change via GET
to_user = params.get('to', [''])[0]
amount = int(params.get('amount', ['0'])[0])
if to_user in USERS and amount > 0:
if USERS[user]['balance'] >= amount:
USERS[user]['balance'] -= amount
USERS[to_user]['balance'] += amount
transfer_get_result = f'<p class="success">Transferred ${amount} to {to_user}</p>'
else:
transfer_get_result = '<p class="error">Insufficient funds</p>'
csrf_token = uuid.uuid4().hex if user else ''
if user:
CSRF_TOKENS[user] = csrf_token
if user:
account_status = f'''
<div class="dashboard">
<strong>Logged in as: {user}</strong><br>
Balance: ${USERS[user]["balance"]}<br>
Email: {USERS[user]["email"]}<br>
<form action="/logout" method="POST" style="display:inline;">
<button type="submit">Logout</button>
</form>
</div>'''
else:
account_status = '<p class="warning">Not logged in</p>'
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
page = HTML_TEMPLATE.format(
account_status=account_status,
login_result='',
transfer1_result='',
email_result='',
transfer2_result='',
password_result='',
transfer_get_result=transfer_get_result,
csrf_token=csrf_token
)
self.wfile.write(page.encode())
def do_POST(self):
parsed = urlparse(self.path)
content_length = int(self.headers.get('Content-Length', 0))
post_data = self.rfile.read(content_length).decode() if content_length > 0 else ''
params = parse_qs(post_data)
user = self.get_session_user()
login_result = ''
transfer1_result = ''
email_result = ''
transfer2_result = ''
password_result = ''
set_cookie = None
if parsed.path == '/login':
username = params.get('username', [''])[0]
password = params.get('password', [''])[0]
if username in USERS and USERS[username]['password'] == password:
session_id = uuid.uuid4().hex
SESSIONS[session_id] = username
set_cookie = f'session={session_id}; Path=/'
login_result = f'<p class="success">Logged in as {username}</p>'
user = username
else:
login_result = '<p class="error">Invalid credentials</p>'
elif parsed.path == '/logout':
cookies = self.headers.get('Cookie', '')
for cookie in cookies.split(';'):
cookie = cookie.strip()
if cookie.startswith('session='):
session_id = cookie.split('=')[1]
SESSIONS.pop(session_id, None)
set_cookie = 'session=; Path=/; Max-Age=0'
user = None
elif parsed.path == '/transfer1' and user:
# VULNERABLE: No CSRF protection
to_user = params.get('to', [''])[0]
amount = int(params.get('amount', ['0'])[0])
if to_user in USERS and amount > 0:
if USERS[user]['balance'] >= amount:
USERS[user]['balance'] -= amount
USERS[to_user]['balance'] += amount
transfer1_result = f'<p class="success">Transferred ${amount} to {to_user}</p>'
else:
transfer1_result = '<p class="error">Insufficient funds</p>'
else:
transfer1_result = '<p class="error">Invalid transfer</p>'
elif parsed.path == '/change_email' and user:
# VULNERABLE: Token not validated properly
email = params.get('email', [''])[0]
token = params.get('csrf_token', [''])[0]
if token: # Only checks if token exists!
USERS[user]['email'] = email
email_result = f'<p class="success">Email changed to {email}</p>'
else:
email_result = '<p class="error">Missing CSRF token</p>'
elif parsed.path == '/transfer2' and user:
# VULNERABLE: Weak referer check
referer = self.headers.get('Referer', '')
if 'localhost' in referer or not referer:
to_user = params.get('to', [''])[0]
amount = int(params.get('amount', ['0'])[0])
if to_user in USERS and amount > 0:
if USERS[user]['balance'] >= amount:
USERS[user]['balance'] -= amount
USERS[to_user]['balance'] += amount
transfer2_result = f'<p class="success">Transferred ${amount} to {to_user}</p>'
else:
transfer2_result = '<p class="error">Insufficient funds</p>'
else:
transfer2_result = '<p class="error">Invalid request origin</p>'
elif parsed.path == '/change_password' and user:
new_password = params.get('new_password', [''])[0]
if new_password:
USERS[user]['password'] = new_password
password_result = '<p class="success">Password changed!</p>'
csrf_token = uuid.uuid4().hex if user else ''
if user:
CSRF_TOKENS[user] = csrf_token
if user:
account_status = f'''
<div class="dashboard">
<strong>Logged in as: {user}</strong><br>
Balance: ${USERS[user]["balance"]}<br>
Email: {USERS[user]["email"]}
</div>'''
else:
account_status = '<p class="warning">Not logged in</p>'
self.send_response(200)
self.send_header('Content-type', 'text/html')
if set_cookie:
self.send_header('Set-Cookie', set_cookie)
self.end_headers()
page = HTML_TEMPLATE.format(
account_status=account_status,
login_result=login_result,
transfer1_result=transfer1_result,
email_result=email_result,
transfer2_result=transfer2_result,
password_result=password_result,
transfer_get_result='',
csrf_token=csrf_token
)
self.wfile.write(page.encode())
def log_message(self, format, *args):
print(f"[{args[0]}] Referer: {self.headers.get('Referer', 'none')}")
if __name__ == '__main__':
server = HTTPServer(('localhost', 8888), CSRFLabHandler)
print("=" * 50)
print(" CSRF LAB RUNNING")
print(" Open http://localhost:8888 in your browser")
print("=" * 50)
server.serve_forever()