#!/usr/bin/env python3
"""
XSS Lab - Cross-Site Scripting Practice
The Hacker's Arsenal - Lesson 4
Multiple XSS challenges with varying difficulty:
1. No filter (easy)
2. Basic filter - blocks <script> tags
3. Better filter - blocks multiple patterns
4. Stored XSS - comment section
5. DOM-based XSS - welcome page
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
COMMENTS = []
HTML_PAGE = '''<!DOCTYPE html>
<html>
<head>
<title>XSS Lab</title>
<style>
body {{ font-family: Arial; max-width: 800px; margin: 50px auto; background: #1a1a1a; color: #e0e0e0; }}
h1 {{ color: #6EFF24; }}
.section {{ background: #252525; padding: 20px; margin: 20px 0; border-radius: 8px; }}
input, textarea {{ width: 100%; padding: 10px; margin: 10px 0; background: #333; border: 1px solid #6EFF24; color: white; }}
button {{ background: #6EFF24; color: black; padding: 10px 20px; border: none; cursor: pointer; font-weight: bold; }}
.comment {{ background: #333; padding: 10px; margin: 10px 0; border-left: 3px solid #6EFF24; }}
.vulnerable {{ border-left-color: red; }}
code {{ background: #333; padding: 2px 6px; color: #6EFF24; }}
</style>
</head>
<body>
<h1>XSS Practice Lab</h1>
<div class="section">
<h2>1. Reflected XSS (No Filter)</h2>
<form action="/search1" method="GET">
<input type="text" name="q" placeholder="Search...">
<button type="submit">Search</button>
</form>
{search1_result}
</div>
<div class="section">
<h2>2. Reflected XSS (Basic Filter)</h2>
<p><em>Blocks: <script> tags</em></p>
<form action="/search2" method="GET">
<input type="text" name="q" placeholder="Search...">
<button type="submit">Search</button>
</form>
{search2_result}
</div>
<div class="section">
<h2>3. Reflected XSS (Better Filter)</h2>
<p><em>Blocks: <script>, <img>, onerror, onload</em></p>
<form action="/search3" method="GET">
<input type="text" name="q" placeholder="Search...">
<button type="submit">Search</button>
</form>
{search3_result}
</div>
<div class="section">
<h2>4. Stored XSS (Comment Section)</h2>
<form action="/comment" method="POST">
<input type="text" name="name" placeholder="Your name">
<textarea name="comment" placeholder="Your comment"></textarea>
<button type="submit">Post Comment</button>
</form>
<h3>Comments:</h3>
{comments}
</div>
<div class="section">
<h2>5. DOM-Based XSS</h2>
<p>Check out our <a href="/welcome#Guest">welcome page</a></p>
<p><em>Hint: The welcome page uses JavaScript to read the URL fragment</em></p>
</div>
<div class="section vulnerable">
<h2>Challenge Mode</h2>
<p>Can you trigger XSS in all 5 sections?</p>
</div>
</body>
</html>'''
WELCOME_PAGE = '''<!DOCTYPE html>
<html>
<head>
<title>Welcome</title>
<style>
body {{ font-family: Arial; max-width: 600px; margin: 100px auto; text-align: center; background: #1a1a1a; color: #e0e0e0; }}
h1 {{ color: #6EFF24; }}
</style>
</head>
<body>
<h1>Welcome!</h1>
<p id="greeting">Loading...</p>
<script>
// DOM XSS Vulnerability - reads from URL fragment without sanitization
var name = decodeURIComponent(location.hash.slice(1));
document.getElementById('greeting').innerHTML = 'Hello, ' + name + '!';
</script>
<p><a href="/" style="color: #6EFF24;">Back to Lab</a></p>
</body>
</html>'''
class XSSLabHandler(BaseHTTPRequestHandler):
def do_GET(self):
parsed = urlparse(self.path)
params = parse_qs(parsed.query)
search1_result = ''
search2_result = ''
search3_result = ''
if parsed.path == '/search1':
q = params.get('q', [''])[0]
# No filter - directly vulnerable
search1_result = f'<p>Results for: {q}</p>'
elif parsed.path == '/search2':
q = params.get('q', [''])[0]
# Basic filter - blocks <script> tags
filtered = q.replace('<script>', '').replace('</script>', '')
search2_result = f'<p>Results for: {filtered}</p>'
elif parsed.path == '/search3':
q = params.get('q', [''])[0]
# Better filter - blocks common payloads
blocked = ['<script', '<img', 'onerror', 'onload', 'javascript:']
filtered = q
for term in blocked:
filtered = filtered.lower().replace(term.lower(), '')
search3_result = f'<p>Results for: {filtered}</p>'
elif parsed.path == '/welcome':
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(WELCOME_PAGE.encode())
return
# Build comments HTML
comments_html = ''
for c in COMMENTS:
comments_html += f'<div class="comment"><strong>{c["name"]}</strong>: {c["comment"]}</div>'
if not comments_html:
comments_html = '<p><em>No comments yet</em></p>'
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
page = HTML_PAGE.format(
search1_result=search1_result,
search2_result=search2_result,
search3_result=search3_result,
comments=comments_html
)
self.wfile.write(page.encode())
def do_POST(self):
parsed = urlparse(self.path)
if parsed.path == '/comment':
content_length = int(self.headers['Content-Length'])
post_data = self.rfile.read(content_length).decode()
params = parse_qs(post_data)
name = params.get('name', ['Anonymous'])[0]
comment = params.get('comment', [''])[0]
# Stored XSS - comments are saved and displayed to everyone
COMMENTS.append({'name': name, 'comment': comment})
# Redirect back to main page
self.send_response(302)
self.send_header('Location', '/')
self.end_headers()
def log_message(self, format, *args):
print(f"[{args[0]}] {self.path}")
if __name__ == '__main__':
server = HTTPServer(('localhost', 8888), XSSLabHandler)
print("=" * 50)
print(" XSS LAB RUNNING")
print(" Open http://localhost:8888 in your browser")
print("=" * 50)
server.serve_forever()