Merge pull request #2 from BlessedRebuS/ptarrant/main

Ptarrant/main
This commit is contained in:
Patrick Di Fazio
2025-12-25 21:31:49 +01:00
committed by GitHub
16 changed files with 1073 additions and 493 deletions

View File

@@ -1,214 +0,0 @@
#!/usr/bin/env python3
"""
Dashboard template for viewing honeypot statistics.
Customize this template to change the dashboard appearance.
"""
def generate_dashboard(stats: dict) -> str:
"""Generate dashboard HTML with access statistics"""
top_ips_rows = '\n'.join([
f'<tr><td class="rank">{i+1}</td><td>{ip}</td><td>{count}</td></tr>'
for i, (ip, count) in enumerate(stats['top_ips'])
]) or '<tr><td colspan="3" style="text-align:center;">No data</td></tr>'
# Generate paths rows
top_paths_rows = '\n'.join([
f'<tr><td class="rank">{i+1}</td><td>{path}</td><td>{count}</td></tr>'
for i, (path, count) in enumerate(stats['top_paths'])
]) or '<tr><td colspan="3" style="text-align:center;">No data</td></tr>'
# Generate User-Agent rows
top_ua_rows = '\n'.join([
f'<tr><td class="rank">{i+1}</td><td style="word-break: break-all;">{ua[:80]}</td><td>{count}</td></tr>'
for i, (ua, count) in enumerate(stats['top_user_agents'])
]) or '<tr><td colspan="3" style="text-align:center;">No data</td></tr>'
# Generate suspicious accesses rows
suspicious_rows = '\n'.join([
f'<tr><td>{log["ip"]}</td><td>{log["path"]}</td><td style="word-break: break-all;">{log["user_agent"][:60]}</td><td>{log["timestamp"].split("T")[1][:8]}</td></tr>'
for log in stats['recent_suspicious'][-10:]
]) or '<tr><td colspan="4" style="text-align:center;">No suspicious activity detected</td></tr>'
return f"""<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Krawl Dashboard</title>
<style>
body {{
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #0d1117;
color: #c9d1d9;
margin: 0;
padding: 20px;
}}
.container {{
max-width: 1400px;
margin: 0 auto;
}}
h1 {{
color: #58a6ff;
text-align: center;
margin-bottom: 40px;
}}
.stats-grid {{
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin-bottom: 40px;
}}
.stat-card {{
background: #161b22;
border: 1px solid #30363d;
border-radius: 6px;
padding: 20px;
text-align: center;
}}
.stat-card.alert {{
border-color: #f85149;
}}
.stat-value {{
font-size: 36px;
font-weight: bold;
color: #58a6ff;
}}
.stat-value.alert {{
color: #f85149;
}}
.stat-label {{
font-size: 14px;
color: #8b949e;
margin-top: 5px;
}}
.table-container {{
background: #161b22;
border: 1px solid #30363d;
border-radius: 6px;
padding: 20px;
margin-bottom: 20px;
}}
h2 {{
color: #58a6ff;
margin-top: 0;
}}
table {{
width: 100%;
border-collapse: collapse;
}}
th, td {{
padding: 12px;
text-align: left;
border-bottom: 1px solid #30363d;
}}
th {{
background: #0d1117;
color: #58a6ff;
font-weight: 600;
}}
tr:hover {{
background: #1c2128;
}}
.rank {{
color: #8b949e;
font-weight: bold;
}}
.alert-section {{
background: #1c1917;
border-left: 4px solid #f85149;
}}
</style>
</head>
<body>
<div class="container">
<h1>&#128375;&#65039; Krawl Dashboard</h1>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value">{stats['total_accesses']}</div>
<div class="stat-label">Total Accesses</div>
</div>
<div class="stat-card">
<div class="stat-value">{stats['unique_ips']}</div>
<div class="stat-label">Unique IPs</div>
</div>
<div class="stat-card">
<div class="stat-value">{stats['unique_paths']}</div>
<div class="stat-label">Unique Paths</div>
</div>
<div class="stat-card alert">
<div class="stat-value alert">{stats['suspicious_accesses']}</div>
<div class="stat-label">Suspicious Accesses</div>
</div>
</div>
<div class="table-container alert-section">
<h2>&#9888;&#65039; Recent Suspicious Activity</h2>
<table>
<thead>
<tr>
<th>IP Address</th>
<th>Path</th>
<th>User-Agent</th>
<th>Time</th>
</tr>
</thead>
<tbody>
{suspicious_rows}
</tbody>
</table>
</div>
<div class="table-container">
<h2>Top IP Addresses</h2>
<table>
<thead>
<tr>
<th>#</th>
<th>IP Address</th>
<th>Access Count</th>
</tr>
</thead>
<tbody>
{top_ips_rows}
</tbody>
</table>
</div>
<div class="table-container">
<h2>Top Paths</h2>
<table>
<thead>
<tr>
<th>#</th>
<th>Path</th>
<th>Access Count</th>
</tr>
</thead>
<tbody>
{top_paths_rows}
</tbody>
</table>
</div>
<div class="table-container">
<h2>Top User-Agents</h2>
<table>
<thead>
<tr>
<th>#</th>
<th>User-Agent</th>
<th>Count</th>
</tr>
</thead>
<tbody>
{top_ua_rows}
</tbody>
</table>
</div>
</div>
</body>
</html>
"""

View File

@@ -197,96 +197,114 @@ class Handler(BaseHTTPRequestHandler):
"""Handle POST requests (mainly login attempts)"""
client_ip = self._get_client_ip()
user_agent = self._get_user_agent()
self.tracker.record_access(client_ip, self.path, user_agent)
post_data = ""
print(f"[LOGIN ATTEMPT] {client_ip} - {self.path} - {user_agent[:50]}")
content_length = int(self.headers.get('Content-Length', 0))
if content_length > 0:
post_data = self.rfile.read(content_length).decode('utf-8')
post_data = self.rfile.read(content_length).decode('utf-8', errors="replace")
print(f"[POST DATA] {post_data[:200]}")
# send the post data (body) to the record_access function so the post data can be used to detect suspicious things.
self.tracker.record_access(client_ip, self.path, user_agent, post_data)
time.sleep(1)
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(html_templates.login_error().encode())
try:
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(html_templates.login_error().encode())
except BrokenPipeError:
# Client disconnected before receiving response, ignore silently
pass
except Exception as e:
# Log other exceptions but don't crash
print(f"[ERROR] Failed to send response to {client_ip}: {str(e)}")
def serve_special_path(self, path: str) -> bool:
"""Serve special paths like robots.txt, API endpoints, etc."""
if path == '/robots.txt':
self.send_response(200)
self.send_header('Content-type', 'text/plain')
self.end_headers()
self.wfile.write(html_templates.robots_txt().encode())
return True
if path in ['/credentials.txt', '/passwords.txt', '/admin_notes.txt']:
self.send_response(200)
self.send_header('Content-type', 'text/plain')
self.end_headers()
if 'credentials' in path:
self.wfile.write(credentials_txt().encode())
else:
self.wfile.write(passwords_txt().encode())
return True
if path in ['/users.json', '/api_keys.json', '/config.json']:
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
if 'users' in path:
self.wfile.write(users_json().encode())
elif 'api_keys' in path:
self.wfile.write(api_keys_json().encode())
else:
self.wfile.write(api_response('/api/config').encode())
return True
if path in ['/admin', '/admin/', '/admin/login', '/login', '/wp-login.php']:
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(html_templates.login_form().encode())
return True
if path == '/wp-admin' or path == '/wp-admin/':
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(html_templates.login_form().encode())
return True
if path in ['/wp-content/', '/wp-includes/'] or 'wordpress' in path.lower():
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(html_templates.wordpress().encode())
return True
if 'phpmyadmin' in path.lower() or path in ['/pma/', '/phpMyAdmin/']:
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(html_templates.phpmyadmin().encode())
return True
if path.startswith('/api/') or path.startswith('/api') or path in ['/.env']:
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
self.wfile.write(api_response(path).encode())
return True
if path in ['/backup/', '/uploads/', '/private/', '/admin/', '/config/', '/database/']:
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(directory_listing(path).encode())
return True
try:
if path == '/robots.txt':
self.send_response(200)
self.send_header('Content-type', 'text/plain')
self.end_headers()
self.wfile.write(html_templates.robots_txt().encode())
return True
if path in ['/credentials.txt', '/passwords.txt', '/admin_notes.txt']:
self.send_response(200)
self.send_header('Content-type', 'text/plain')
self.end_headers()
if 'credentials' in path:
self.wfile.write(credentials_txt().encode())
else:
self.wfile.write(passwords_txt().encode())
return True
if path in ['/users.json', '/api_keys.json', '/config.json']:
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
if 'users' in path:
self.wfile.write(users_json().encode())
elif 'api_keys' in path:
self.wfile.write(api_keys_json().encode())
else:
self.wfile.write(api_response('/api/config').encode())
return True
if path in ['/admin', '/admin/', '/admin/login', '/login']:
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(html_templates.login_form().encode())
return True
# WordPress login page
if path in ['/wp-login.php', '/wp-login', '/wp-admin', '/wp-admin/']:
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(html_templates.wp_login().encode())
return True
if path in ['/wp-content/', '/wp-includes/'] or 'wordpress' in path.lower():
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(html_templates.wordpress().encode())
return True
if 'phpmyadmin' in path.lower() or path in ['/pma/', '/phpMyAdmin/']:
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(html_templates.phpmyadmin().encode())
return True
if path.startswith('/api/') or path.startswith('/api') or path in ['/.env']:
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
self.wfile.write(api_response(path).encode())
return True
if path in ['/backup/', '/uploads/', '/private/', '/admin/', '/config/', '/database/']:
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(directory_listing(path).encode())
return True
except BrokenPipeError:
# Client disconnected, ignore silently
pass
except Exception as e:
print(f"[ERROR] Failed to serve special path {path}: {str(e)}")
pass
return False
@@ -302,6 +320,8 @@ class Handler(BaseHTTPRequestHandler):
try:
stats = self.tracker.get_stats()
self.wfile.write(generate_dashboard(stats).encode())
except BrokenPipeError:
pass
except Exception as e:
print(f"Error generating dashboard: {e}")
return
@@ -333,6 +353,9 @@ class Handler(BaseHTTPRequestHandler):
if Handler.counter < 0:
Handler.counter = self.config.canary_token_tries
except BrokenPipeError:
# Client disconnected, ignore silently
pass
except Exception as e:
print(f"Error generating page: {e}")

15
src/templates/__init__.py Normal file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env python3
"""
Templates package for the deception server.
"""
from .template_loader import load_template, clear_cache, TemplateNotFoundError
from . import html_templates
__all__ = [
'load_template',
'clear_cache',
'TemplateNotFoundError',
'html_templates',
]

View File

@@ -39,6 +39,12 @@ def generate_dashboard(stats: dict) -> str:
for ip, paths in stats.get('honeypot_triggered_ips', [])
]) or '<tr><td colspan="3" style="text-align:center;">No honeypot triggers yet</td></tr>'
# Generate attack types rows
attack_type_rows = '\n'.join([
f'<tr><td>{log["ip"]}</td><td>{log["path"]}</td><td>{", ".join(log["attack_types"])}</td><td style="word-break: break-all;">{log["user_agent"][:60]}</td><td>{log["timestamp"].split("T")[1][:8]}</td></tr>'
for log in stats.get('attack_types', [])[-10:]
]) or '<tr><td colspan="4" style="text-align:center;">No attacks detected</td></tr>'
return f"""<!DOCTYPE html>
<html>
<head>
@@ -188,6 +194,24 @@ def generate_dashboard(stats: dict) -> str:
</table>
</div>
<div class="table-container alert-section">
<h2>&#128520; Detected Attack Types</h2>
<table>
<thead>
<tr>
<th>IP Address</th>
<th>Path</th>
<th>Attack Types</th>
<th>User-Agent</th>
<th>Time</th>
</tr>
</thead>
<tbody>
{attack_type_rows}
</tbody>
</table>
</div>
<div class="table-container">
<h2>Top IP Addresses</h2>
<table>

View File

@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head><title>Index of {path}</title>
<style>
body {{ font-family: monospace; background: #fff; padding: 20px; }}
h1 {{ border-bottom: 1px solid #ccc; padding-bottom: 10px; }}
table {{ width: 100%; border-collapse: collapse; }}
th {{ text-align: left; padding: 10px; background: #f0f0f0; }}
td {{ padding: 8px; border-bottom: 1px solid #eee; }}
a {{ color: #0066cc; text-decoration: none; }}
a:hover {{ text-decoration: underline; }}
</style>
</head>
<body>
<h1>Index of {path}</h1>
<table>
<tr><th>Name</th><th>Last Modified</th><th>Size</th></tr>
<tr><td><a href="../">Parent Directory</a></td><td>-</td><td>-</td></tr>
{rows}
</table></body></html>

View File

@@ -0,0 +1 @@
<tr><td><a href="{href}">{name}</a></td><td>{date}</td><td>{size}</td></tr>

View File

@@ -0,0 +1,108 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex, nofollow">
<title>Error</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
padding: 20px;
}
.container {
background: white;
padding: 40px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
width: 100%;
max-width: 400px;
text-align: center;
}
h1 {
font-size: 32px;
margin-bottom: 10px;
color: #d32f2f;
}
.error-message {
background: #ffebee;
border-left: 4px solid #d32f2f;
padding: 15px;
margin: 20px 0;
border-radius: 4px;
color: #c62828;
font-size: 14px;
line-height: 1.5;
}
p {
color: #666;
font-size: 14px;
margin: 15px 0;
line-height: 1.6;
}
.links-section {
margin-top: 20px;
font-size: 13px;
}
.links-section a {
color: #2196f3;
text-decoration: none;
margin: 0 10px;
}
.links-section a:hover {
text-decoration: underline;
}
.back-btn {
display: inline-block;
margin-top: 20px;
padding: 10px 20px;
background: #2196f3;
color: white;
text-decoration: none;
border-radius: 4px;
font-size: 14px;
font-weight: 500;
}
.back-btn:hover {
background: #1976d2;
}
</style>
</head>
<body>
<div class="container">
<h1>⚠ Error</h1>
<div class="error-message">
Login Failed. Please try again.
</div>
<p>If the problem persists, please contact support.</p>
<div class="links-section">
<a href="/forgot-password">Forgot password?</a>
</div>
<a href="/" class="back-btn">← Back to Home</a>
</div>
</body>
</html>

View File

@@ -0,0 +1,156 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex, nofollow">
<title>Admin Login</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: #f5f5f5;
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
padding: 20px;
}
.container {
background: white;
padding: 40px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
width: 100%;
max-width: 400px;
}
h1 {
font-size: 28px;
margin-bottom: 10px;
color: #333;
text-align: center;
}
.subtitle {
text-align: center;
color: #666;
font-size: 14px;
margin-bottom: 30px;
}
label {
display: block;
margin: 15px 0 5px 0;
font-weight: 500;
color: #333;
font-size: 14px;
}
input[type="text"],
input[type="password"],
input[type="email"] {
width: 100%;
padding: 10px;
margin: 5px 0 15px 0;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
transition: border-color 0.3s;
}
input[type="text"]:focus,
input[type="password"]:focus,
input[type="email"]:focus {
outline: none;
border-color: #2196f3;
box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.1);
}
.remember-me {
display: flex;
align-items: center;
margin: 15px 0;
font-size: 13px;
}
.remember-me input {
margin: 0 8px 0 0;
width: auto;
}
button {
width: 100%;
padding: 10px;
background: #2196f3;
color: white;
border: none;
border-radius: 4px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: background 0.3s;
margin-top: 10px;
}
button:hover {
background: #1976d2;
}
button:active {
background: #1565c0;
}
.links {
text-align: center;
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #eee;
font-size: 13px;
}
.links a {
color: #2196f3;
text-decoration: none;
display: block;
margin: 8px 0;
}
.links a:hover {
text-decoration: underline;
}
</style>
</head>
<body>
<div class="container">
<h1>Admin Panel</h1>
<p class="subtitle">Please log in to continue</p>
<form action="/admin/login" method="post">
<label for="username">Username</label>
<input type="text" id="username" name="username" placeholder="Enter your username" autocomplete="username" autofocus required>
<label for="password">Password</label>
<input type="password" id="password" name="password" placeholder="Enter your password" autocomplete="current-password" required>
<div class="remember-me">
<input type="checkbox" id="remember" name="remember" value="1">
<label for="remember" style="margin: 0;">Remember me</label>
</div>
<button type="submit">Sign In</button>
</form>
<div class="links">
<a href="/forgot-password">Forgot your password?</a>
<a href="/">← Back to Home</a>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,167 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex,nofollow">
<title>phpMyAdmin</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, sans-serif;
margin: 0;
background: #f0f0f0;
}
.header {
background: #2979ff;
color: white;
padding: 10px 20px;
}
.header h1 {
margin: 0;
font-size: 24px;
}
.login {
background: white;
width: 400px;
margin: 100px auto;
padding: 30px;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.login h2 {
margin-top: 0;
color: #333;
font-size: 18px;
}
input[type="text"],
input[type="password"],
select {
width: 100%;
padding: 8px;
margin: 8px 0;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
font-size: 14px;
}
input[type="text"]:focus,
input[type="password"]:focus,
select:focus {
border-color: #2979ff;
outline: none;
box-shadow: 0 0 0 2px rgba(41, 121, 255, 0.1);
}
label {
display: block;
margin: 12px 0 5px 0;
font-weight: 500;
color: #333;
font-size: 13px;
}
button {
width: 100%;
padding: 10px 20px;
background: #2979ff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
margin-top: 15px;
}
button:hover {
background: #1565c0;
}
button:active {
background: #0d47a1;
}
.lang-select {
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid #eee;
}
.lang-select label {
display: inline;
margin-right: 10px;
margin: 0 10px 0 0;
}
.lang-select select {
width: auto;
padding: 5px 10px;
margin: 8px 0;
}
.footer {
text-align: center;
margin-top: 20px;
padding-top: 15px;
border-top: 1px solid #eee;
font-size: 12px;
color: #888;
}
.footer a {
color: #2979ff;
text-decoration: none;
}
.footer a:hover {
text-decoration: underline;
}
@media (max-width: 480px) {
.login {
width: 90%;
margin: 50px auto;
padding: 20px;
}
.login h2 {
font-size: 16px;
}
}
</style>
</head>
<body>
<div class="header">
<h1>phpMyAdmin</h1>
</div>
<div class="login">
<h2>MySQL Server Login</h2>
<form action="/phpmyadmin/index.php" method="post" autocomplete="off">
<label for="username">Username:</label>
<input type="text" name="pma_username" id="username" placeholder="Username" autocomplete="username" autofocus>
<label for="password">Password:</label>
<input type="password" name="pma_password" id="password" placeholder="Password" autocomplete="current-password">
<button type="submit">Go</button>
<div class="lang-select">
<label for="lang">Language:</label>
<select name="lang" id="lang">
<option value="en" selected>English</option>
<option value="de">Deutsch</option>
<option value="es">Español</option>
<option value="fr">Français</option>
<option value="it">Italiano</option>
<option value="ja">日本語</option>
<option value="ko">한국어</option>
<option value="nl">Nederlands</option>
<option value="pl">Polski</option>
<option value="pt">Português</option>
<option value="ru">Русский</option>
<option value="zh">中文</option>
</select>
</div>
<input type="hidden" name="token" value="a1b2c3d4e5f6g7h8i9j0">
<input type="hidden" name="set_session" value="1">
</form>
<div class="footer">
<a href="https://www.phpmyadmin.net/docs/" target="_blank">Documentation</a> |
<a href="https://www.phpmyadmin.net/" target="_blank">Official Homepage</a> |
<a href="https://github.com/phpmyadmin/phpmyadmin" target="_blank">Contribute</a>
<br><br>
phpMyAdmin 5.2.1
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,21 @@
User-agent: *
Disallow: /admin/
Disallow: /api/
Disallow: /backup/
Disallow: /config/
Disallow: /database/
Disallow: /private/
Disallow: /uploads/
Disallow: /wp-admin/
Disallow: /login/
Disallow: /admin/login
Disallow: /phpMyAdmin/
Disallow: /admin/login.php
Disallow: /api/v1/users
Disallow: /api/v2/secrets
Disallow: /.env
Disallow: /credentials.txt
Disallow: /passwords.txt
Disallow: /.git/
Disallow: /backup.sql
Disallow: /db_backup.sql

View File

@@ -0,0 +1,73 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>My Blog &#8211; Just another WordPress site</title>
<link rel='dns-prefetch' href='//s.w.org' />
<link rel='stylesheet' id='wp-block-library-css' href='/wp-includes/css/dist/block-library/style.min.css' type='text/css' media='all' />
<link rel='stylesheet' id='twentytwentythree-style-css' href='/wp-content/themes/twentytwentythree/style.css' type='text/css' media='all' />
<link rel='https://api.w.org/' href='/wp-json/' />
<meta name="generator" content="WordPress 6.4.2" />
<style>
body {{ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; margin: 0; padding: 0; background: #fff; }}
.site-header {{ background: #23282d; color: white; padding: 20px; border-bottom: 4px solid #0073aa; }}
.site-header h1 {{ margin: 0; font-size: 28px; }}
.site-header p {{ margin: 5px 0 0; color: #d0d0d0; }}
.site-content {{ max-width: 1200px; margin: 40px auto; padding: 0 20px; }}
.entry {{ background: #fff; margin-bottom: 40px; padding: 30px; border: 1px solid #ddd; border-radius: 4px; }}
.entry-title {{ font-size: 32px; margin-top: 0; color: #23282d; }}
.entry-meta {{ color: #666; font-size: 14px; margin-bottom: 20px; }}
.entry-content {{ line-height: 1.8; color: #444; }}
.site-footer {{ background: #f7f7f7; padding: 20px; text-align: center; color: #666; border-top: 1px solid #ddd; margin-top: 60px; }}
a {{ color: #0073aa; text-decoration: none; }}
a:hover {{ text-decoration: underline; }}
</style>
</head>
<body class="home blog wp-embed-responsive">
<div id="page" class="site">
<header id="masthead" class="site-header">
<div class="site-branding">
<h1 class="site-title">My Blog</h1>
<p class="site-description">Just another WordPress site</p>
</div>
</header>
<div id="content" class="site-content">
<article id="post-1" class="entry">
<header class="entry-header">
<h2 class="entry-title">Hello world!</h2>
<div class="entry-meta">
<span class="posted-on">Posted on <time datetime="2024-12-01">December 1, 2024</time></span>
<span class="byline"> by <span class="author">admin</span></span>
</div>
</header>
<div class="entry-content">
<p>Welcome to WordPress. This is your first post. Edit or delete it, then start writing!</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
</div>
</article>
<article id="post-2" class="entry">
<header class="entry-header">
<h2 class="entry-title">About This Site</h2>
<div class="entry-meta">
<span class="posted-on">Posted on <time datetime="2024-11-28">November 28, 2024</time></span>
<span class="byline"> by <span class="author">admin</span></span>
</div>
</header>
<div class="entry-content">
<p>This is a sample page. You can use it to write about your site, yourself, or anything else you'd like.</p>
</div>
</article>
</div>
<footer id="colophon" class="site-footer">
<div class="site-info">
Proudly powered by <a href="https://wordpress.org/">WordPress</a>
</div>
</footer>
</div>
<script type='text/javascript' src='/wp-includes/js/wp-embed.min.js'></script>
</body>
</html>

View File

@@ -0,0 +1,238 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width">
<meta name="robots" content="max-image-preview:large, noindex, noarchive">
<title>Log In &lsaquo; WordPress &mdash; WordPress</title>
<style type="text/css">
html {
background: #f0f0f1;
min-height: 100%;
}
body {
background: #f0f0f1;
min-height: 100%;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
font-size: 13px;
line-height: 1.4em;
color: #3c434a;
margin: 0;
padding: 0;
}
a {
color: #2271b1;
text-decoration: none;
}
a:hover, a:active {
color: #135e96;
}
#login {
width: 320px;
padding: 8% 0 0;
margin: auto;
}
#login h1 {
text-align: center;
margin: 0 0 20px;
}
#login h1 {
text-align: center;
margin: 0 0 30px;
font-size: 28px;
font-weight: 400;
color: #3c434a;
}
#login h1 a {
color: #2271b1;
text-decoration: none;
font-weight: 600;
font-size: 28px;
display: block;
background: none;
height: auto;
}
#login h1 a:hover {
color: #135e96;
}
.login form {
margin-top: 20px;
margin-left: 0;
padding: 26px 24px 34px;
font-weight: 400;
overflow: hidden;
background: #fff;
border: 1px solid #c3c4c7;
box-shadow: 0 1px 3px rgba(0, 0, 0, .04);
}
.login form .input, .login input[type="text"], .login input[type="password"] {
font-size: 24px;
width: 100%;
padding: 3px;
margin: 2px 6px 16px 0;
background: #fff;
border: 1px solid #8c8f94;
box-shadow: none;
color: #2c3338;
outline: none;
}
.login form .input:focus {
border-color: #2271b1;
box-shadow: 0 0 0 1px #2271b1;
}
.login label {
font-size: 14px;
line-height: 1.5;
display: block;
margin-bottom: 8px;
}
.login .forgetmenot {
margin: 2px 0 24px;
}
.login .forgetmenot label {
font-size: 12px;
display: flex;
align-items: center;
}
.login .forgetmenot input {
margin: 0 4px 0 0;
}
.wp-hide-pw {
position: absolute;
right: 0;
top: 0;
bottom: 0;
padding: 0 15px;
background: transparent;
border: none;
color: #2271b1;
cursor: pointer;
}
.wp-hide-pw:hover {
color: #135e96;
}
.user-pass-wrap {
position: relative;
}
.wp-pwd {
position: relative;
}
#wp-submit {
float: right;
text-decoration: none;
font-size: 13px;
line-height: 2.15384615;
min-height: 32px;
margin: 0;
padding: 0 12px;
cursor: pointer;
border: 1px solid #2271b1;
border-radius: 3px;
white-space: nowrap;
box-sizing: border-box;
background: #2271b1;
color: #fff;
}
#wp-submit:hover {
background: #135e96;
border-color: #135e96;
color: #fff;
}
p.submit {
margin-bottom: 0;
}
#nav {
margin: 24px 0 0 0;
padding: 0;
text-align: center;
}
#nav a {
color: #50575e;
}
#nav a:hover {
color: #135e96;
}
#backtoblog {
margin: 16px 0 0 0;
padding: 0;
text-align: center;
}
#backtoblog a {
color: #50575e;
}
#backtoblog a:hover {
color: #135e96;
}
.privacy-policy-page-link {
text-align: center;
margin-top: 16px;
}
.privacy-policy-page-link a {
color: #50575e;
font-size: 12px;
}
@media screen and (max-width: 400px) {
#login {
width: 100%;
padding: 20px;
box-sizing: border-box;
}
}
</style>
</head>
<body class="login js login-action-login wp-core-ui locale-en-us">
<div id="login">
<h1><a href="https://wordpress.org/">WordPress Login</a></h1>
<form name="loginform" id="loginform" action="/wp-login.php" method="post">
<p>
<label for="user_login">Username or Email Address</label>
<input type="text" name="log" id="user_login" class="input" value="" size="20" autocapitalize="off" autocomplete="username" required>
</p>
<div class="user-pass-wrap">
<label for="user_pass">Password</label>
<div class="wp-pwd">
<input type="password" name="pwd" id="user_pass" class="input password-input" value="" size="20" autocomplete="current-password" spellcheck="false" required>
<button type="button" class="wp-hide-pw hide-if-no-js" data-toggle="0" aria-label="Show password">
<span class="dashicons dashicons-visibility" aria-hidden="true"></span>
</button>
</div>
</div>
<p class="forgetmenot">
<label>
<input name="rememberme" type="checkbox" id="rememberme" value="forever">
Remember Me
</label>
</p>
<p class="submit">
<input type="submit" name="wp-submit" id="wp-submit" class="button button-primary button-large" value="Log In">
<input type="hidden" name="redirect_to" value="/wp-admin/">
<input type="hidden" name="testcookie" value="1">
</p>
</form>
<p id="nav">
<a href="/wp-login.php?action=lostpassword">Lost your password?</a>
</p>
<p id="backtoblog">
<a href="/">&larr; Go to WordPress</a>
</p>
<div class="privacy-policy-page-link">
<a href="/privacy-policy/">Privacy Policy</a>
</div>
</div>
<script>
// Toggle password visibility
document.querySelector('.wp-hide-pw').addEventListener('click', function() {{
var pwdInput = document.getElementById('user_pass');
if (pwdInput.type === 'password') {{
pwdInput.type = 'text';
this.setAttribute('aria-label', 'Hide password');
}} else {{
pwdInput.type = 'password';
this.setAttribute('aria-label', 'Show password');
}}
}});
</script>
</body>
</html>

View File

@@ -2,228 +2,51 @@
"""
HTML templates for the deception server.
Edit these templates to customize the appearance of fake pages.
Templates are loaded from the html/ subdirectory.
"""
from .template_loader import load_template
def login_form() -> str:
"""Generate fake login page"""
return """<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Admin Login</title>
<style>
body { font-family: Arial, sans-serif; background: #f0f0f0; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }
.login-box { background: white; padding: 40px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); width: 300px; }
h2 { margin-top: 0; color: #333; }
input { width: 100%; padding: 10px; margin: 10px 0; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; }
button { width: 100%; padding: 10px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
button:hover { background: #0056b3; }
</style>
</head>
<body>
<div class="login-box">
<h2>Admin Login</h2>
<form action="/admin/login" method="post">
<input type="text" name="username" placeholder="Username" required>
<input type="password" name="password" placeholder="Password" required>
<button type="submit">Login</button>
</form>
</div>
</body>
</html>"""
return load_template("login_form")
def login_error() -> str:
"""Generate fake login error page"""
return """<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Login Failed</title>
<style>
body { font-family: Arial, sans-serif; background: #f0f0f0; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }
.login-box { background: white; padding: 40px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); width: 300px; }
h2 { margin-top: 0; color: #333; }
.error { color: #d63301; background: #ffebe8; border: 1px solid #d63301; padding: 12px; margin-bottom: 20px; border-radius: 4px; }
input { width: 100%; padding: 10px; margin: 10px 0; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; }
button { width: 100%; padding: 10px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
button:hover { background: #0056b3; }
a { color: #007bff; font-size: 14px; }
</style>
</head>
<body>
<div class="login-box">
<h2>Admin Login</h2>
<div class="error"><strong>ERROR:</strong> Invalid username or password.</div>
<form action="/admin/login" method="post">
<input type="text" name="username" placeholder="Username" required>
<input type="password" name="password" placeholder="Password" required>
<button type="submit">Login</button>
</form>
<p style="margin-top: 20px; text-align: center;"><a href="/forgot-password">Forgot your password?</a></p>
</div>
</body>
</html>"""
return load_template("login_error")
def wordpress() -> str:
"""Generate fake WordPress page"""
return """<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>My Blog &#8211; Just another WordPress site</title>
<link rel='dns-prefetch' href='//s.w.org' />
<link rel='stylesheet' id='wp-block-library-css' href='/wp-includes/css/dist/block-library/style.min.css' type='text/css' media='all' />
<link rel='stylesheet' id='twentytwentythree-style-css' href='/wp-content/themes/twentytwentythree/style.css' type='text/css' media='all' />
<link rel='https://api.w.org/' href='/wp-json/' />
<meta name="generator" content="WordPress 6.4.2" />
<style>
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; margin: 0; padding: 0; background: #fff; }
.site-header { background: #23282d; color: white; padding: 20px; border-bottom: 4px solid #0073aa; }
.site-header h1 { margin: 0; font-size: 28px; }
.site-header p { margin: 5px 0 0; color: #d0d0d0; }
.site-content { max-width: 1200px; margin: 40px auto; padding: 0 20px; }
.entry { background: #fff; margin-bottom: 40px; padding: 30px; border: 1px solid #ddd; border-radius: 4px; }
.entry-title { font-size: 32px; margin-top: 0; color: #23282d; }
.entry-meta { color: #666; font-size: 14px; margin-bottom: 20px; }
.entry-content { line-height: 1.8; color: #444; }
.site-footer { background: #f7f7f7; padding: 20px; text-align: center; color: #666; border-top: 1px solid #ddd; margin-top: 60px; }
a { color: #0073aa; text-decoration: none; }
a:hover { text-decoration: underline; }
</style>
</head>
<body class="home blog wp-embed-responsive">
<div id="page" class="site">
<header id="masthead" class="site-header">
<div class="site-branding">
<h1 class="site-title">My Blog</h1>
<p class="site-description">Just another WordPress site</p>
</div>
</header>
<div id="content" class="site-content">
<article id="post-1" class="entry">
<header class="entry-header">
<h2 class="entry-title">Hello world!</h2>
<div class="entry-meta">
<span class="posted-on">Posted on <time datetime="2024-12-01">December 1, 2024</time></span>
<span class="byline"> by <span class="author">admin</span></span>
</div>
</header>
<div class="entry-content">
<p>Welcome to WordPress. This is your first post. Edit or delete it, then start writing!</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
</div>
</article>
<article id="post-2" class="entry">
<header class="entry-header">
<h2 class="entry-title">About This Site</h2>
<div class="entry-meta">
<span class="posted-on">Posted on <time datetime="2024-11-28">November 28, 2024</time></span>
<span class="byline"> by <span class="author">admin</span></span>
</div>
</header>
<div class="entry-content">
<p>This is a sample page. You can use it to write about your site, yourself, or anything else you'd like.</p>
</div>
</article>
</div>
<footer id="colophon" class="site-footer">
<div class="site-info">
Proudly powered by <a href="https://wordpress.org/">WordPress</a>
</div>
</footer>
</div>
<script type='text/javascript' src='/wp-includes/js/wp-embed.min.js'></script>
</body>
</html>"""
return load_template("wordpress")
def phpmyadmin() -> str:
"""Generate fake phpMyAdmin page"""
return """<!DOCTYPE html>
<html>
<head>
<title>phpMyAdmin</title>
<style>
body { font-family: 'Segoe UI', Tahoma, sans-serif; margin: 0; background: #f0f0f0; }
.header { background: #2979ff; color: white; padding: 10px 20px; }
.login { background: white; width: 400px; margin: 100px auto; padding: 30px; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
input { width: 100%; padding: 8px; margin: 8px 0; border: 1px solid #ddd; }
button { padding: 10px 20px; background: #2979ff; color: white; border: none; cursor: pointer; }
</style>
</head>
<body>
<div class="header"><h1>phpMyAdmin</h1></div>
<div class="login">
<h2>MySQL Server Login</h2>
<form action="/phpMyAdmin/index.php" method="post">
<input type="text" name="pma_username" placeholder="Username">
<input type="password" name="pma_password" placeholder="Password">
<button type="submit">Go</button>
</form>
</div>
</body>
</html>"""
return load_template("phpmyadmin")
def wp_login() -> str:
"""Generate fake WordPress login page"""
return load_template("wp_login")
def robots_txt() -> str:
"""Generate juicy robots.txt"""
return """User-agent: *
Disallow: /admin/
Disallow: /api/
Disallow: /backup/
Disallow: /config/
Disallow: /database/
Disallow: /private/
Disallow: /uploads/
Disallow: /wp-admin/
Disallow: /phpMyAdmin/
Disallow: /admin/login.php
Disallow: /api/v1/users
Disallow: /api/v2/secrets
Disallow: /.env
Disallow: /credentials.txt
Disallow: /passwords.txt
Disallow: /.git/
Disallow: /backup.sql
Disallow: /db_backup.sql
"""
return load_template("robots.txt")
def directory_listing(path: str, dirs: list, files: list) -> str:
"""Generate fake directory listing"""
html = f"""<!DOCTYPE html>
<html>
<head><title>Index of {path}</title>
<style>
body {{ font-family: monospace; background: #fff; padding: 20px; }}
h1 {{ border-bottom: 1px solid #ccc; padding-bottom: 10px; }}
table {{ width: 100%; border-collapse: collapse; }}
th {{ text-align: left; padding: 10px; background: #f0f0f0; }}
td {{ padding: 8px; border-bottom: 1px solid #eee; }}
a {{ color: #0066cc; text-decoration: none; }}
a:hover {{ text-decoration: underline; }}
</style>
</head>
<body>
<h1>Index of {path}</h1>
<table>
<tr><th>Name</th><th>Last Modified</th><th>Size</th></tr>
<tr><td><a href="../">Parent Directory</a></td><td>-</td><td>-</td></tr>
"""
row_template = load_template("directory_row")
rows = ""
for d in dirs:
html += f'<tr><td><a href="{d}">{d}</a></td><td>2024-12-01 10:30</td><td>-</td></tr>\n'
rows += row_template.format(href=d, name=d, date="2024-12-01 10:30", size="-")
for f, size in files:
html += f'<tr><td><a href="{f}">{f}</a></td><td>2024-12-01 14:22</td><td>{size}</td></tr>\n'
html += '</table></body></html>'
return html
rows += row_template.format(href=f, name=f, date="2024-12-01 14:22", size=size)
return load_template("directory_listing", path=path, rows=rows)

View File

@@ -0,0 +1,69 @@
#!/usr/bin/env python3
"""
Template loader for HTML templates.
Loads templates from the html/ subdirectory and supports string formatting for dynamic content.
"""
from pathlib import Path
from typing import Dict
class TemplateNotFoundError(Exception):
"""Raised when a template file cannot be found."""
pass
# Module-level cache for loaded templates
_template_cache: Dict[str, str] = {}
# Base directory for template files
_TEMPLATE_DIR = Path(__file__).parent / "html"
def load_template(name: str, **kwargs) -> str:
"""
Load a template by name and optionally substitute placeholders.
Args:
name: Template name (without extension for HTML, with extension for others like .txt)
**kwargs: Key-value pairs for placeholder substitution using str.format()
Returns:
Rendered template string
Raises:
TemplateNotFoundError: If template file doesn't exist
Example:
>>> load_template("login_form") # Loads html/login_form.html
>>> load_template("robots.txt") # Loads html/robots.txt
>>> load_template("directory_listing", path="/var/www", rows="<tr>...</tr>")
"""
# debug
# print(f"Loading Template: {name}")
# Check cache first
if name not in _template_cache:
# Determine file path based on whether name has an extension
if '.' in name:
file_path = _TEMPLATE_DIR / name
else:
file_path = _TEMPLATE_DIR / f"{name}.html"
if not file_path.exists():
raise TemplateNotFoundError(f"Template '{name}' not found at {file_path}")
_template_cache[name] = file_path.read_text(encoding='utf-8')
template = _template_cache[name]
# Apply substitutions if kwargs provided
if kwargs:
template = template.format(**kwargs)
return template
def clear_cache() -> None:
"""Clear the template cache. Useful for testing or development."""
_template_cache.clear()

View File

@@ -3,6 +3,7 @@
from typing import Dict, List, Tuple
from collections import defaultdict
from datetime import datetime
import re
class AccessTracker:
@@ -17,17 +18,35 @@ class AccessTracker:
'scanner', 'nikto', 'sqlmap', 'nmap', 'masscan', 'nessus', 'acunetix',
'burp', 'zap', 'w3af', 'metasploit', 'nuclei', 'gobuster', 'dirbuster'
]
# common attack types such as xss, shell injection, probes
self.attack_types = {
'path_traversal': r'\.\.',
'sql_injection': r"('|--|;|\bOR\b|\bUNION\b|\bSELECT\b|\bDROP\b)",
'xss_attempt': r'(<script|javascript:|onerror=|onload=)',
'common_probes': r'(wp-admin|phpmyadmin|\.env|\.git|/admin|/config)',
'shell_injection': r'(\||;|`|\$\(|&&)',
}
# Track IPs that accessed honeypot paths from robots.txt
self.honeypot_triggered: Dict[str, List[str]] = defaultdict(list)
def record_access(self, ip: str, path: str, user_agent: str = ''):
def record_access(self, ip: str, path: str, user_agent: str = '', body: str = ''):
"""Record an access attempt"""
self.ip_counts[ip] += 1
self.path_counts[path] += 1
if user_agent:
self.user_agent_counts[user_agent] += 1
is_suspicious = self.is_suspicious_user_agent(user_agent) or self.is_honeypot_path(path)
# path attack type detection
attack_findings = self.detect_attack_type(path)
# post / put data
if len(body) > 0:
attack_findings.extend(self.detect_attack_type(body))
is_suspicious = self.is_suspicious_user_agent(user_agent) or self.is_honeypot_path(path) or len(attack_findings) > 0
# Track if this IP accessed a honeypot path
if self.is_honeypot_path(path):
@@ -39,9 +58,20 @@ class AccessTracker:
'user_agent': user_agent,
'suspicious': is_suspicious,
'honeypot_triggered': self.is_honeypot_path(path),
'attack_types':attack_findings,
'timestamp': datetime.now().isoformat()
})
def detect_attack_type(self, data:str) -> list[str]:
"""
Returns a list of all attack types found in path data
"""
findings = []
for name, pattern in self.attack_types.items():
if re.search(pattern, data, re.IGNORECASE):
findings.append(name)
return findings
def is_honeypot_path(self, path: str) -> bool:
"""Check if path is one of the honeypot traps from robots.txt"""
honeypot_paths = [
@@ -91,6 +121,11 @@ class AccessTracker:
suspicious = [log for log in self.access_log if log.get('suspicious', False)]
return suspicious[-limit:]
def get_attack_type_accesses(self, limit: int = 20) -> List[Dict]:
"""Get recent accesses with detected attack types"""
attacks = [log for log in self.access_log if log.get('attack_types')]
return attacks[-limit:]
def get_honeypot_triggered_ips(self) -> List[Tuple[str, List[str]]]:
"""Get IPs that accessed honeypot paths"""
return [(ip, paths) for ip, paths in self.honeypot_triggered.items()]
@@ -110,5 +145,6 @@ class AccessTracker:
'top_paths': self.get_top_paths(10),
'top_user_agents': self.get_top_user_agents(10),
'recent_suspicious': self.get_suspicious_accesses(20),
'honeypot_triggered_ips': self.get_honeypot_triggered_ips()
'honeypot_triggered_ips': self.get_honeypot_triggered_ips(),
'attack_types': self.get_attack_type_accesses(20)
}

20
tests/sim_attacks.sh Executable file
View File

@@ -0,0 +1,20 @@
#!/bin/bash
TARGET="http://localhost:5000"
echo "=== Testing Path Traversal ==="
curl -s "$TARGET/../../etc/passwd"
echo -e "\n=== Testing SQL Injection ==="
curl -s -X POST "$TARGET/login" -d "user=' OR 1=1--"
echo -e "\n=== Testing XSS ==="
curl -s -X POST "$TARGET/comment" -d "msg=<script>alert(1)</script>"
echo -e "\n=== Testing Common Probes ==="
curl -s "$TARGET/.env"
curl -s "$TARGET/wp-admin/"
echo -e "\n=== Testing Shell Injection ==="
curl -s -X POST "$TARGET/ping" -d "host=127.0.0.1; cat /etc/passwd"
echo -e "\n=== Done ==="